Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nova Persian Date Filter Laravel Package

dpsoft/nova-persian-date-filter

A Laravel Nova filter that lets you filter date fields using the Persian (Jalali) calendar. Pick dates with a familiar Shamsi datepicker while Nova queries the underlying Gregorian values, making admin panels friendlier for Persian-speaking users.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a small Nova filter package that lets you put a Jalali (Persian) date picker in front of a Nova resource index and have the chosen dates translated into Gregorian ranges for the underlying query. It is a thin wrapper — there is no service provider magic to learn, no migrations, and no UI beyond the filter menu.

  1. Install it next to Nova:

    composer require dpsoft/nova-persian-date-filter
    
  2. Before you install, read composer.json in the package. The last tagged release is February 2020, so the Nova constraint is almost certainly ^1.0|^2.0|^3.0. If you are on Nova 4 or 5, expect to either fork it or lift the apply() logic into a filter of your own (see Gotchas).

  3. Look at src/ first. The package is a single base filter class (something like PersianDateFilter / DateFilter) extending Laravel\Nova\Filters\Filter. Read its apply() method — that is the entire contract you inherit, and it tells you exactly which $value shape the front-end sends (usually an array of two Jalali date strings, or a single string depending on whether it is a range or a single-date filter).

  4. Create your own filter:

    php artisan nova:filter CreatedAtFilter
    

    Then change the parent class from Laravel\Nova\Filters\Filter to the package's base filter, and point it at your column.

  5. Register it in the resource and you have your first use case — a Jalali date-range filter on created_at:

    public function filters(Request $request)
    {
        return [new CreatedAtFilter];
    }
    

If the package ships a config file (config/persian-date-filter.php or similar), publish it with php artisan vendor:publish --provider="Dpsoft\...\FilterServiceProvider" — check vendor/dpsoft/nova-persian-date-filter/ for the exact provider name rather than guessing.

Implementation Patterns

Subclass-and-configure (the intended path). The package is designed so you extend its filter and only declare the column and label. Typical shape:

class CreatedAtFilter extends PersianDateFilter
{
    public $name = 'تاریخ ایجاد';

    public function apply(Request $request, $query, $value)
    {
        // $value is Jalali input from the picker
        return parent::apply($request, $query, $value);
    }
}

If the parent already handles conversion, your override should only add scoping (e.g. a where('tenant_id', ...)) rather than re-parsing dates.

Do the conversion explicitly when you own the query. The most maintainable pattern with a package this small is to let it own only the input widget and keep the query logic in your filter:

public function apply(Request $request, $query, $value)
{
    if (blank($value)) {
        return $query;
    }

    [$from, $to] = array_pad((array) $value, 2, null);

    $from = $from ? Jalalian::fromFormat('Y-m-d', $from)->toCarbon()->startOfDay() : null;
    $to   = $to   ? Jalalian::fromFormat('Y-m-d', $to)->toCarbon()->endOfDay()   : null;

    return $query
        ->when($from, fn ($q) => $q->where('created_at', '>=', $from))
        ->when($to,   fn ($q) => $q->where('created_at', '<=', $to));
}

This is what you fall back to on Nova 4+ or if the package's apply() hard-codes a column name.

Reuse across resources. Because filters are instantiated per-resource, extract your conversion into a trait (ConvertsJalaliDates) or an abstract JalaliDateRangeFilter in app/Nova/Filters/. Then each resource filter is three lines: $name, $column, done. This also gives you one place to fix when the package is abandoned.

Pair it with a quick-select. Nova filters support ->default() and options; a common day-to-day combo is the Jalali range picker plus a preset menu ("امروز / این هفته / این ماه") that sets the same $value shape. Keep the value shape identical so apply() does not branch.

Test the filter, not the UI. Nova filters are easy to unit test without a browser:

$filter = new CreatedAtFilter;
$query = User::query();
$filter->apply(request(), $query, ['1402-01-01', '1402-01-31']);
$this->assertStringContainsString('where', $query->toSql());

Assert on the bound dates (Gregorian) rather than the SQL string — it documents the conversion contract and catches off-by-one-day bugs on range ends.

Gotchas and Tips

  • Nova version drift is the headline risk. A 2020 release predates Nova 4's filter API changes (notably the apply(Request $request, $query, $value) signature and the switch to nova:filter generating Inertia/Vue components that differ from Nova 2/3's). If composer require conflicts, don't fight it — vendor the ~50 lines of apply() into your own filter. With 6 stars and no dependents, assume you are the maintainer.
  • Timezone boundaries are where this breaks. Jalali dates are calendar days in Asia/Tehran, but your column is almost certainly UTC. startOfDay() on a Carbon date built from a Jalali string uses the app timezone, not the user's. Set the timezone explicitly (->setTimezone('Asia/Tehran') for display, UTC for the query) or your "today" filter will silently drop the first and last few hours of records.
  • Persian digits vs ASCII. Users and the picker may submit ۱۴۰۲-۰۱-۰۱ while Jalalian::fromFormat('Y-m-d', ...) expects ASCII. Normalize on the way in (strtr($value, ['۰'=>'0', ...])) or you get a null date and a query that matches everything.
  • Guard against empty values. The filter is active whenever it appears in $filters, so apply() runs with null/[] on every index load. Always early-return $query on blank input, otherwise you'll append whereBetween with nulls and empty the table.
  • Inclusive end dates. A range ending 1402-01-31 should be <= 1402-01-31 23:59:59, not <= 1402-01-31 00:00:00. Use endOfDay(), or better, use an exclusive upper bound (< nextDay) to avoid microsecond precision surprises.
  • Front-end assets. Older Nova filters ship compiled JS in dist/ and expect it via nova.mix or a script()/fields() method. If the picker renders as a plain text input, the package's JS wasn't loaded — check whether it needs php artisan vendor:publish --tag=nova-persian-date-filter-assets or a ->withMeta() call.
  • Debugging tip: drop dd($value, $query->toSql(), $query->getBindings()) at the top of apply() and hit the index once. It answers, in one shot, what shape the picker sends and whether your conversion produced the dates you expected.
  • Extension points to lean on: $name/$column properties, the apply() override, and Nova's ->default() / ->canSee() for permission-gating. There is nothing deeper to hook into, so treat the class as a template rather than a dependency — copy it into app/Nova/Filters/ and delete the composer requirement if you want long-term stability.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log