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

Laravel Timezone Laravel Package

jamesmills/laravel-timezone

Laravel Timezone detects each visitor's timezone in the browser and stores it in the session, automatically applying it to your app for accurate date and time display. Includes a Blade directive, middleware, and configurable settings.

Deep Wiki
Context7

Getting Started

The package does one job: detect a visitor's timezone in the browser, persist it, and switch the application timezone for the current HTTP request so now()/Carbon formatting is correct.

Install and publish:

composer require jamesmills/laravel-timezone
php artisan vendor:publish --provider="JamesMills\LaravelTimezone\LaravelTimezoneServiceProvider"

Wire up the middleware in app/Http/Kernel.php inside the web group, after session/auth middleware:

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Session\Middleware\StartSession::class,
        // ...
        \JamesMills\LaravelTimezone\Middleware\Timezone::class,
    ],
];

Add the trait and column. On your User model:

use JamesMills\LaravelTimezone\Traits\HasTimezone;

class User extends Authenticatable
{
    use HasTimezone;
}

Then a migration with a string column (64 chars covers identifiers like America/Argentina/ComodRivadavia):

$table->string('timezone', 64)->nullable()->after('email');

Emit the detection script. The package ships a Blade directive/view that writes a timezone cookie via Intl.DateTimeFormat().resolvedOptions().timeZone. Put it in your main layout, inside <head>:

@timezone

First use case: render a timestamp in the user's zone without ever passing it explicitly:

// After middleware has run for an authenticated user:
now()->format('g:i A');                 // already in the user's timezone
$order->created_at->format('M j, Y');   // Carbon uses app timezone

Verify with php artisan tinker: config('app.timezone'), date_default_timezone_get(), and auth()->user()->timezone while logged in with a timezone cookie present.

Note: the last release is from 2018. Before installing, confirm it resolves against your Laravel/Carbon version; if it doesn't, the middleware is small enough to copy into app/Http/Middleware (read cookie → validate against DateTimeZone::listIdentifiers() → config(['app.timezone' => $tz]) and date_default_timezone_set($tz)).

Implementation Patterns

  • Request-scoped timezone override. The core pattern is mutating config('app.timezone') and PHP's default timezone at the start of the request. Everything downstream — now(), Carbon::parse(), Eloquent date casts, Blade ->format() — inherits it. Treat this as the contract: nothing should need to call ->timezone() explicitly inside a request.

  • Cookie-on-login backfill. The cookie is set by JS after the first page load, so on the very first request (or a fresh login) the middleware may have no value. Add a small listener/controller step that copies the cookie into users.timezone and returns it on profile update:

    $user->update(['timezone' => $request->cookie('timezone')]);
    
  • Guests. Unauthenticated visitors have no row to persist to; the cookie is the only source. Decide explicitly whether guests get their local zone (nice for marketing sites) or the app default (safer for cached pages).

  • Explicit conversion at the edges. For anything user-facing outside the request (mailables, notifications, PDFs, exports), don't rely on the middleware — it never ran in the queue. Pass the timezone and convert at render time:

    $user->notify((new OrderShipped($order))->timezone($user->timezone));
    // or in the mailable view:
    $order->created_at->timezone($user->timezone)->format('M j, Y g:i A');
    
  • Storing and validating. Always persist UTC; the timezone column is a presentation preference, not a timestamp zone. Use Laravel's timezone validation rule on profile forms and DateTimeZone::listIdentifiers() to populate a <select>:

    $request->validate(['timezone' => ['required', 'timezone']]);
    
  • Scheduling. Keep config/app.php at UTC, store UTC, and schedule jobs with ->timezone($user->timezone) on the event rather than flipping the app timezone — this keeps cron/log semantics consistent.

  • Integrating with the middleware pipeline. Place it after StartSession (it may read session) and after auth if you want auth()->user()->timezone. On API routes with a stateless client, skip the cookie approach and read an X-Timezone header instead; the middleware logic is trivial to extend.

  • Inertia / Livewire. Livewire's follow-up requests are still HTTP requests, so the middleware runs, but a partial re-render driven by an X-Livewire request without cookies can fall back to the app default. If you see flicker, read the timezone from the authenticated user rather than the cookie in those paths.

Gotchas and Tips

  • Global mutation leaks in long-running workers. date_default_timezone_set() and config(['app.timezone' => ...]) persist for the life of the PHP process. Under Octane, Swoole, or queue workers, one user's timezone can bleed into the next job/request. Always reset to config('app.timezone') or the default in a finally/terminating callback, and re-run detection per request.

  • Never trust the cookie raw. date_default_timezone_set() with an invalid identifier emits a warning and leaves the previous zone in place — or worse, silently masks the bug. Validate against DateTimeZone::listIdentifiers() before applying, and fall back to the app default.

  • First-page-load gap. Detection happens in JavaScript after the response is rendered, so server-side times on the initial page can be in UTC while subsequent pages are local. Either accept it, render times via JS/moment on first paint, or set the cookie server-side for logged-in users whose timezone column is already populated.

  • Cookie scope and path. The middleware only sees the cookie if it's set for the same domain/path and not HttpOnly. If you serve multiple subdomains or an API on a different host, set the cookie domain explicitly or fall back to the user column.

  • Middleware ordering matters. Registering it before StartSession or before auth means the cookie/session/user lookup silently no-ops and every request stays UTC. Add a test asserting now()->timezoneName differs for a user with a timezone value.

  • Trait collisions. The HasTimezone trait defines accessors for a timezone attribute. If your User model already has a timezone column, an accessor, or a TimeZone cast, you'll get conflicts — check $user->timezone returns the stored identifier, not a Carbon instance.

  • Model casts. Don't datetime-cast the timezone column. It's a string identifier.

  • Testing. Set the zone explicitly instead of depending on the cookie: $this->actingAs($user); config(['app.timezone' => 'Europe/Berlin']); or freeze time with Carbon::setTestNow(). For middleware tests, $this->withCookie('timezone', 'Asia/Tokyo') and assert config('app.timezone') after the request. Remember to restore the config in tearDown(); otherwise the leak noted above breaks later tests.

  • Queue and console are unaffected. Artisan commands, scheduled tasks, and jobs do not run web middleware. Any reporting or aggregation using whereDate()/startOfDay() must pass the target timezone explicitly:

    $orders->whereBetween('created_at', [
        now($user->timezone)->startOfDay()->utc(),
        now($user->timezone)->endOfDay()->utc(),
    ]);
    
  • Unmaintained dependency risk. Carbon 2→3 and Laravel 8→11 changed date handling and middleware resolution. If composer require conflicts, vendor the middleware class into your app (roughly 20 lines) and keep the package's Blade snippet for cookie detection — that preserves the behavior with zero upgrade risk.

  • Extension points. The published config controls the cookie name and detection toggle; the Blade view can be published and overridden to add a fetch POST instead of a cookie; and the middleware is a normal class you can subclass or replace in Kernel.php. A UserTimezoneUpdated listener is the cleanest place to normalize/validate zones coming from a settings form.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource