DEV Community

Cover image for Timezone setup for Forms, Tables, and Filters in Filament
yebor974 for Filament Mastery

Posted on • Originally published at filamentmastery.com

1 1 1 1 1

Timezone setup for Forms, Tables, and Filters in Filament

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'));
        }
    });
}
Enter fullscreen mode Exit fullscreen mode

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')),
        ]);
}
Enter fullscreen mode Exit fullscreen mode

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')),
        ]);
}
Enter fullscreen mode Exit fullscreen mode

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'))
                            )
                        );
                }),
        ]);
}
Enter fullscreen mode Exit fullscreen mode

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.

Tutorial image

Next.js Tutorial 2025 - Build a Full Stack Social App

In this 4-hour hands-on tutorial, Codesistency walks you through the process of building a social platform from scratch with Next.js (App Router), React, Prisma ORM, Clerk for authentication, Neon for PostgreSQL hosting, Tailwind CSS, Shadcn UI, and UploadThing for image uploads.

Watch the full video ➡

Top comments (0)

Tiger Data image

🐯 🚀 Timescale is now TigerData: Building the Modern PostgreSQL for the Analytical and Agentic Era

We’ve quietly evolved from a time-series database into the modern PostgreSQL for today’s and tomorrow’s computing, built for performance, scale, and the agentic future.

So we’re changing our name: from Timescale to TigerData. Not to change who we are, but to reflect who we’ve become. TigerData is bold, fast, and built to power the next era of software.

Read more

👋 Kindness is contagious

Explore this compelling article, highly praised by the collaborative DEV Community. All developers, whether just starting out or already experienced, are invited to share insights and grow our collective expertise.

A quick “thank you” can lift someone’s spirits—drop your kudos in the comments!

On DEV, sharing experiences sparks innovation and strengthens our connections. If this post resonated with you, a brief note of appreciation goes a long way.

Get Started