Eloquent date cast limits when app.timezone changes after naive timestamp storage
0 reputation · 15 Jan 2024, 01:46 UTC
0 reputation · 15 Jan 2024, 01:46 UTC
Laravel's Eloquent date casts serialize attributes to naive Y-m-d H:i:s strings by default, while config('app.timezone') supplies a single application-level default zone for Carbon instances. When that default zone is changed after rows have already been stored, existing database values are not automatically converted; instead, they are reinterpreted in the context of the new default. Because Laravel does not provide a built-in per-request user timezone context for model dates, teams must choose between storing UTC and converting at the edges or storing local civil time and accepting the ambiguity that comes with it.
Named IANA zones handle daylight saving time rules from the tz database, but naive local strings recorded near DST transitions can represent two distinct instants or none at all. Without offset-bearing storage, the framework cannot distinguish whether a stored value was meant as an absolute moment or a wall-clock time. This leaves an unresolved design decision about how model dates should behave when the application default or a user's regional zone shifts.
Should existing naive datetime columns be treated as immutable civil times or as zone-relative values that move when app.timezone changes? What is the supported boundary for converting user input to the storage zone when the column has no offset? How should ambiguous local strings during DST transitions be resolved when the user zone differs from the application default?
35275 reputation · 15 Jan 2024, 06:15 UTC
When Laravel's Eloquent model casts a naive database column (Y-m-d H:i:s) to a Carbon instance, it assumes the string exists in the application's current timezone defined by config('app.timezone'). If that default zone is altered after data has been persisted, the framework does not migrate existing rows; instead, it reinterprets the stored civil time under the new zone rules. This means the absolute instant represented by the data can shift, sometimes by hours, especially across daylight saving time transitions.
Because naive columns carry no offset, Laravel cannot distinguish whether a value was intended as wall-clock time or an absolute UTC moment. Daylight saving time folds (repeated hours) and gaps (missing hours) further complicate resolution without explicit metadata.
To verify whether your records have shifted, run the following scoped command:
config(['app.timezone' => 'UTC']); php artisan config:clear; $record = Model::first(); echo $record->created_at; config(['app.timezone' => 'America/New_York']); php artisan config:clear; $record = Model::first(); echo $record->created_atIf you can share your current app.timezone and whether the column was originally intended to store UTC or local civil time, I can determine whether a data migration or a change in input handling is required. This detail changes the recommendation from accept the shift to migrate existing values.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.