juniora/nova-persian-date-field
A Laravel Nova field package for Persian (Jalali) dates. It provides a form field for selecting and displaying dates in the Persian calendar, making date input and presentation easier in Nova admin panels for Persian-speaking users.
Install and publish assets. composer require juniora/nova-persian-date-field then run php artisan nova:assets (or nova:publish on older Nova) so the Vue component is copied into public/vendor/nova. Without this the field renders an empty cell.
Look first at two places in the repo: src/ for the field class (it will extend Laravel\Nova\Fields\Field or DateTime) to see the exact method names and default format props, and resources/js/components/ to see which JS picker library it wraps (e.g. vue-persian-datetime-picker / persian-datepicker). Those two files tell you the real API faster than the README on a 2‑star package.
First use case: swap a plain date field on an admin-heavy resource where the operators think in Jalali. In the resource's fields():
use JunioraTeam\NovaPersianDateField\PersianDate; // verify exact namespace
PersianDate::make('Published At')
->rules('nullable', 'date'),
Keep the DB column as a normal datetime/date (Gregorian, UTC). The field should only change the display and input layer — that is the whole value proposition, and the boundary you want to protect.
Keep conversion at the field boundary. The package's job is Gregorian-in-DB ↔ Jalali-in-Nova. Do not add Jalali columns or Jalali casts to your models. Store with Eloquent's normal datetime cast so ->sortable(), query scopes, and reporting all keep working.
Wrap it in a small factory so the format/timezone is consistent across resources:
// app/Nova/Fields/PersianDate.php
class PersianDate extends \JunioraTeam\NovaPersianDateField\PersianDate
{
public function __construct($name, $attribute = null, $resolveCallback = null)
{
parent::__construct($name, $attribute, $resolveCallback);
$this->format('YYYY-MM-DD HH:mm:ss'); // confirm prop names in src/
}
}
This gives you one place to fix when you upgrade Nova or swap the underlying picker.
Detail vs. index formatting. Use the field's display-format prop for the detail view and be explicit on index — index columns are often too narrow for a full Jalali datetime, and the picker component may not be loaded in that context. Test index rendering separately.
Pair with ->sortable() and ->nullable() as you would a normal DateTime. Sorting happens on the raw column server-side, so it stays correct — but only if your field does not override resolveForIndex with a formatted string.
Use it where humans type, not where machines read. Good fits: resource forms, inline create, action modals with a date input, lenses that only display. Poor fits: Nova filters and metrics — those usually need numeric comparisons, and a Jalali string in a filter becomes a footgun. Keep filters on plain DateTime or a custom filter that parses to Gregorian before querying.
Timezone is your responsibility, not the package's. If your app stores UTC and your admins are in Tehran (+03:30), decide once: either set App\Nova::usesTimezone()-style handling if the field honours it, or normalise in a model accessor. Verify by saving ۱۴۰۳-۰۱-۰۱ ۰۰:۰۰ and checking the raw DB value — a half-hour offset bug is the classic failure here and is easy to miss in a demo.
Testing. Assert on the HTTP round-trip (submit a Jalali string, assert the Gregorian column), not on the Vue component. Nova's field tests won't exercise the JS picker, so a passing PHPUnit suite does not prove the field renders.
composer.json, and be prepared to fork into packages/ with a path repository. The 2025‑12 release date is recent, but a single-commit fix is likely to be your commit.composer.json constraints and smoke-test one form after every Nova upgrade.composer update, re-run nova:assets. A cached compiled component is the #1 cause of "the field is there but the picker doesn't open."->rules('date') and after_or_equal rules run against whatever the component submits. Confirm the component submits ISO/Gregorian; if it submits Jalali strings, you need a custom rule or a prepareForValidation step, or validation will pass/fail incorrectly.date() syntax. Y-m-d vs YYYY-MM-DD mismatch produces Invalid Date or a silently wrong year. Copy the format string from the JS library's docs, not from your PHP code.dir="rtl" styling. Check both themes on the detail view before shipping to admins.resolve() / fill() to control the JS↔DB marshalling, override component to point at your own patched Vue file, and use ->withMeta([...]) to pass picker options through rather than editing vendor JS. Prefer these over editing resources/js in vendor/.public/vendor/nova for the compiled component → src/ field class → the picker library's own issues. If the field is blank, it is almost always assets; if it renders but won't save, it is marshalling or validation.How can I help you explore Laravel packages today?