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 Timezones Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

  • 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).
  • The package's premise is simple and worth internalising before writing code: the database keeps UTC, the request renders in a "current timezone". config('app.timezone') stays UTC; the package holds the per-request timezone and converts model attributes at the boundary.
  • Where to look first, in this order: the published config file (defaults and any user/column mapping), the package service provider (to see what's bound as a singleton and whether a middleware alias is registered), then the model concern/cast it ships (that's the class you'll actually be attaching to Eloquent models).
  • First use case: give 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.
  • v1.4.0 itself is a compatibility release only (Laravel 13 support, plus a Laravel Shift compatibility PR). It widens the framework constraint and adds no API surface, so there's nothing new to learn — but it does confirm the package is actively maintained and safe to adopt on current Laravel. If you're on Laravel 10–12, upgrading is a no-op for your code.

Implementation Patterns

  • Set the timezone once per request. A middleware in the 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.
  • Keep a single source of truth for "who is the user". Rather than sprinkling lookups, add a small 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.
  • Attribute-by-attribute opt-in beats global magic. Apply the package's concern/cast only to the attributes that users actually read as wall-clock times (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.
  • Convert on the way out of the request boundary, too. If a user submits 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.
  • Queries stay in UTC. Date-range filters, 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.
  • Serialise with an offset, not a naive string. For APIs, prefer ISO-8601 with offset (2025-03-30T14:00:00+09:00) or a separate timezone field describing interpretation. Naive local strings are ambiguous and clients will guess wrong.
  • Jobs and commands need explicit context. Queue workers and artisan commands have no user, so don't rely on the middleware having run. Pass the timezone (or the user id) on the job payload, set it at the top of handle(), reset in finally, and remember ShouldQueue mails/notifications inherit the dispatching context only until they're serialised.
  • Console scheduling stays in UTC — schedule in app.timezone and let each consumer localise for display. Don't set a "current timezone" globally in AppServiceProvider::boot().
  • Testing: freeze time (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.
  • Validation: timezone validation rule (or an in: list from DateTimeZone::listIdentifiers()) on the profile form, nullable with a default. Store the identifier, not the offset.

Gotchas and Tips

  • Double conversion is the classic bug. Once an attribute is handled by the package, do not also call ->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.
  • Carbon instances are mutable. Formatting a timezoned attribute in place can poison the same instance elsewhere in the request (especially on models reused across a collection). Use ->copy() or ->toImmutable() before mutating.
  • Octane / long-running workers retain singleton state. The current timezone lives in the container, so it survives between requests under Octane. Register a reset in the middleware's terminate() (or an Octane RequestTerminated listener) — otherwise user A's zone leaks into user B's response.
  • Queue workers inherit the last set timezone, including from a web request that dispatched the job in the same process. Always reset in finally, and never assume UTC in a job without setting it.
  • Column types matter. MySQL 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.
  • Casts vs $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.
  • Bad user input must not 500. A stale or misspelled identifier (Europe/Bruxelles) throws in DateTimeZone/Carbon. Validate on write, and wrap the resolver in a try/catch that falls back to config('app.timezone').
  • DST edges are real: 02:30 doesn't exist on spring-forward day and occurs twice on fall-back day. If users can pick times, decide the policy (shift forward / reject ambiguous) and note it — most "timezone package is broken" reports are this.
  • Config caching: after editing the published config, php artisan config:cache (and clear it in deploy). Forgetting this in CI is a common "works locally" failure.
  • Extension points to reach for before forking: bind your own resolver into the container, wrap the facade in your own application-level service (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.
  • Debugging recipe: when a time looks wrong, print (a) the raw DB value, (b) the value right after hydration via 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.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource