· yebor974 · Getting Started, Advanced Techniques, Tutorials

Timezone setup for Forms, Tables, and Filters in Filament

Learn how to configure timezones for forms, tables, and filters in Filament Resources, ensuring accurate date handling based on user preferences.

Timezone setup for Forms, Tables, and Filters in Filament - picture

Managing timezones effectively is crucial for any application dealing with global users. In Filament, you can simplify timezone handling by leveraging built-in methods like timezone() for your components. This article demonstrates how to configure timezone management for forms, tables, and filters, ensuring your users see dates in their preferred timezone.

Prerequisite: Adding a string timezone attribute We assume that your User model includes a timezone attribute to store the user's preferred timezone. If it doesn’t, you can add this attribute and configure it to default to the application’s timezone (e.g., config('app.timezone')) when not set. For guidance on adding it during registration, check out this article.

Case 1: Global timezone configuration

Instead of setting the timezone for each field individually, you can declare a global configuration in the ServiceProvider for components like DateTimePicker and TextColumn.

Global configuration in AppServiceProvider

Add the following code to your AppServiceProvider in the boot() method:

use Filament\Forms\Components\DateTimePicker;
use Filament\Tables\Columns\TextColumn;

public function boot(): void
{
    // Set default timezone for DateTimePicker
    DateTimePicker::configureUsing(function (DateTimePicker $component): void {
        $component->timezone(auth()->user()?->timezone ?? config('app.timezone'));
    });

    // Set default timezone for TextColumn for datetime attibutes
    TextColumn::configureUsing(function (TextColumn $component): void {
        if (in_array($component->getName(), ['created_at', 'updated_at', 'published_at', 'email_verified_at'])) {
            $component->timezone(auth()->user()?->timezone ?? config('app.timezone'));
        }
    });
}

Explanation:

  • The DateTimePicker component will now always display dates using the user's timezone or fall back to the application's default timezone. Dates are automatically saved using the application's default timezone.
  • The TextColumn will apply the timezone to commonly used datetime fields like created_at, updated_at, and published_at.

Case 2: Applying timezone configuration in a Filament Resource

For further customization, you can configure a resource to use timezone-aware components. Below is an example using a BlogPostResource with a published_at attribute.

Table columns

For TextColumn, you can directly use the timezone() method to ensure the displayed published_at respects the user's timezone.

use Filament\Tables;

protected function table(Table $table): Table
{
    return $table
        ->columns([
            Tables\Columns\TextColumn::make('published_at')
                ->label('Published At')
                ->dateTime()
                ->timezone(auth()->user()?->timezone ?? config('app.timezone')),
        ]);
}

Form components

For DateTimePicker, simply use the timezone() method to manage timezones dynamically.

use Filament\Forms;

protected function form(Form $form): Form
{
    return $form
        ->schema([
            Forms\Components\DateTimePicker::make('published_at')
                ->label('Published At')
                ->timezone(auth()->user()?->timezone ?? config('app.timezone')),
        ]);
}

Manage Table filters

For filtering data using Tables\Filters, ensure that the date values are converted to the correct timezone during the query.

Here’s an example of a filter for created_at with support for user-specific timezones:

use Filament\Tables;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Filters\Components\DatePicker;
use Illuminate\Database\Eloquent\Builder;
use Carbon\Carbon;

protected function table(Table $table): Table
{
    return $table
        ->filters([
            Filter::make('created_at')
                ->form([
                    DatePicker::make('created_from')
                        ->label(__('users.filters.created_from'))
                        ->native(false)
                        ->displayFormat('d/m/Y'),
                    DatePicker::make('created_until')
                        ->label(__('users.filters.created_until'))
                        ->native(false)
                        ->displayFormat('d/m/Y'),
                ])
                ->indicateUsing(function (array $data): array {
                    $indicators = [];

                    if ($data['created_from'] ?? null) {
                        $indicators[] = Tables\Filters\Indicator::make(
                            __('users.filters.created_from') . ' ' . Carbon::parse($data['created_from'])->toFormattedDateString()
                        )->removeField('created_from');
                    }

                    if ($data['created_until'] ?? null) {
                        $indicators[] = Tables\Filters\Indicator::make(
                            __('users.filters.created_until') . ' ' . Carbon::parse($data['created_until'])->toFormattedDateString()
                        )->removeField('created_until');
                    }

                    return $indicators;
                })
                ->query(function (Builder $query, array $data): Builder {
                    return $query
                        ->when(
                            $data['created_from'],
                            fn (Builder $query, $date): Builder => $query->where(
                                'created_at',
                                '>=',
                                Carbon::parse($date, auth()->user()?->timezone ?? config('app.timezone'))
                                    ->startOfDay()
                                    ->timezone(config('app.timezone'))
                            )
                        )
                        ->when(
                            $data['created_until'],
                            fn (Builder $query, $date): Builder => $query->where(
                                'created_at',
                                '<=',
                                Carbon::parse($date, auth()->user()?->timezone ?? config('app.timezone'))
                                    ->endOfDay()
                                    ->timezone(config('app.timezone'))
                            )
                        );
                }),
        ]);
}

Explanation

  1. Filter Form:

    • DatePicker is used for selecting created_from and created_until.
    • It displays dates in a d/m/Y format, ensuring clarity for users.
  2. Indicators:

    • When a user applies filters, indicators show the selected range using Carbon::toFormattedDateString().
  3. Query Logic:

    • The created_from and created_until values are parsed to the user's timezone and then converted to the platform's timezone (config('app.timezone')) for consistent querying.
  4. Timezone Conversion:

    • Using Carbon::parse($date, auth()->user()->timezone) ensures the dates are interpreted correctly based on the user's timezone.
    • The startOfDay() and endOfDay() methods ensure proper inclusivity of the date range.

Benefits of global configuration

With the AppServiceProvider setup, the following benefits apply:

  1. Reduced Redundancy: No need to manually configure timezone() for each field.
  2. Consistency: All components handle timezones uniformly.
  3. Easy Maintenance: Adding or changing timezone behavior is centralized.

By using Filament’s built-in methods like timezone() and setting global defaults in the AppServiceProvider, you can efficiently manage timezone behavior across your application. This setup ensures a consistent and user-friendly experience, especially for date and time fields in resources.

Avatar of yebor974
yebor974
Freelance - French IT Engineer passionate about Laravel and Filament PHP, creating innovative solutions to simplify web development.
React
Share post

Stay ahead in the Filament Mastery adventure

Join our community of Filament PHP enthusiasts! Get exclusive tips, tutorials, and updates delivered straight to your inbox.