whitecube/laravel-timezones
Laravel Timezones offers a curated list of timezone identifiers plus validation, select options, and browser-based detection helpers. Easily set user timezones, localize dates, and generate timezone dropdowns in Laravel apps.
composer require whitecube/laravel-timezones, then publish the package config with php artisan vendor:publish --tag=timezones (check vendor/whitecube/laravel-timezones/config/ for the exact tag/keys shipped in v1.4.0).config('app.timezone') stays UTC; the package holds the per-request timezone and converts model attributes at the boundary.users a nullable timezone string column (IANA identifiers like Europe/Brussels, never +02:00), let the user pick it in their profile, set it early in the request lifecycle, and mark one model's datetime attributes as timezoned. Load a record and you should see it rendered in the user's zone while ->getRawOriginal() still shows the UTC value — that single assertion proves the wiring end to end.web group, placed after session/auth so $request->user() is available, is the canonical spot: resolve from the authenticated user, fall back to a request header (e.g. X-Timezone sent by the SPA), then to the app default. Wrap the rest of the request in a try/finally that resets the state so a long-lived worker can't leak it.TimezoneResolver class and bind it in a service provider; the middleware becomes three lines and you can swap cookie/header/profile/geo strategies without touching HTTP code.starts_at, published_at, due_at). Leave audit columns (created_at, updated_at) alone unless the product genuinely needs them localised — mixing them makes diffs and support tickets confusing.2025-03-30 14:00 in Asia/Tokyo, either let the cast handle the round-trip or explicitly convert to UTC before persisting — never pass a naive local string to a where() clause.whereBetween, cursor pagination and ->latest() all operate on stored values. Convert the user's submitted range to UTC in a form request or query scope, then query normally. This is the number-one place timezone bugs appear, and the package can't help you there.2025-03-30T14:00:00+09:00) or a separate timezone field describing interpretation. Naive local strings are ambiguous and clients will guess wrong.handle(), reset in finally, and remember ShouldQueue mails/notifications inherit the dispatching context only until they're serialised.app.timezone and let each consumer localise for display. Don't set a "current timezone" globally in AppServiceProvider::boot().Carbon::setTestNow() / $this->travelTo()), set a non-UTC zone, and assert both surfaces in one test — $model->fresh()->getRawOriginal('starts_at') is UTC, $model->starts_at->toIso8601String() carries the expected offset. Add a DST-transition case (a Europe/Brussels spring-forward date) to your suite; it's cheap and catches most regressions.timezone validation rule (or an in: list from DateTimeZone::listIdentifiers()) on the profile form, nullable with a default. Store the identifier, not the offset.->setTimezone(...) or ->tz(...) in the view/accessor/resource. Pick one boundary and stick to it; if a blade template needs a different zone, reach for ->copy()->tz($other) on a clone, explicitly.->copy() or ->toImmutable() before mutating.terminate() (or an Octane RequestTerminated listener) — otherwise user A's zone leaks into user B's response.finally, and never assume UTC in a job without setting it.TIMESTAMP columns are converted by the connection's session timezone; DATETIME are not. Keep the connection timezone at +00:00 and prefer datetime/timestamp consistently — a mismatch produces an off-by-hours bug the package cannot see.$dates/serializeDate. If you also override serializeDate() or use $dateFormat, you can end up converting twice on JSON output. Test ->toArray() explicitly, not just attribute access.Europe/Bruxelles) throws in DateTimeZone/Carbon. Validate on write, and wrap the resolver in a try/catch that falls back to config('app.timezone').php artisan config:cache (and clear it in deploy). Forgetting this in CI is a common "works locally" failure.App\Support\Clock) so the rest of the codebase never imports the vendor namespace, and add a custom cast if you need a different storage/display strategy for a specific attribute. Keeping the package behind one thin seam makes the next major-version upgrade — or a swap — a one-file change.getRawOriginal(), (c) the current timezone the package reports, and (d) config('app.timezone'). Whichever pair disagrees tells you whether the fault is storage, hydration, or context.How can I help you explore Laravel packages today?