UTC Everywhere: How to Stop Date Bugs in Laravel + Vue Apps
Date bugs are the kind that don’t crash your app.
They quietly ruin trust.
A meeting shows up on the wrong day.
A recurring event fires twice.
A user in another timezone swears your app is broken.
And the worst part?
Everything looks correct in the database.
If you’ve built real Laravel + Vue apps, you’ve probably fought this war already. I have too. Multiple times. At companies. On personal projects. Each time thinking, “Surely this time we’ve solved it.”
Spoiler: you haven’t… until you go UTC everywhere.
This article isn’t theory. It’s the hard-earned rulebook that finally stops date bugs for good.
The Core Rule (Burn This Into Your Brain)
Store everything in UTC. Always. No exceptions.
Not “mostly UTC.”
Not “UTC except for display.”
Not “UTC but this one column is local.”
Everything.
Dates. Datetimes. Timestamps. Recurring events. Logs. Schedules. Tokens. Expirations.
UTC is the language your system speaks internally.
Local time is just a translation layer.
Once you truly commit to that idea, everything else gets simpler.
Why Date Bugs Keep Happening
Most apps fail because they mix responsibilities.
- The backend sometimes assumes local time
- The frontend sometimes sends local time pretending it’s UTC
- The database stores a mix of both
- Developers “fix” bugs by adding offsets in random places
This creates time drift. Invisible drift. The kind that only shows up:
- during DST changes
- for users in other timezones
- with recurring events
- when editing existing records
By the time you notice, production data is already poisoned.
The Laravel Side: Make UTC Non-Negotiable
1. Set Laravel’s App Timezone to UTC
In config/app.php:
'timezone' => 'UTC',
This ensures:
- now()
- Carbon::now()
- model timestamps
- queue jobs
- scheduled tasks
all speak UTC by default.
2. Store Datetimes as UTC in the Database
Your database should never know what timezone a user lives in.
That means:
- DATETIME or TIMESTAMP values are UTC
- no offsets
- no “local” columns
- no magic conversions
If you ever see a column named something like event_time_local, that’s a warning sign.
3. Normalize Dates at the Boundary (Requests In)
Any time the frontend sends a date:
- assume it is local to the user
- convert it to UTC immediately
- store only the UTC version
Example mental model:
“The frontend speaks local. The backend translates once, then forgets.”
If you delay conversion, bugs creep in.
4. Always Return ISO 8601 UTC Strings
When sending dates back to the frontend:
{
"starts_at": "2026-02-05T16:00:00Z"
}
ISO 8601 with Z is your contract.
The Vue Side: Timezones Are a UI Concern
Vue should never guess what the backend meant.
1. Treat All API Dates as UTC
When data enters your frontend:
- assume it is UTC
- parse it explicitly as UTC
- never assume browser locale magically matches intent
2. Convert to Local Time Only for Display
The only place local time belongs is:
- UI rendering
- date pickers
- human-readable labels
The moment a user edits or submits a date:
- convert it back to UTC
- send UTC to the API
3. Recurring Events Are Where Apps Go to Die
Recurring events expose every flaw in your system.
The fix:
- recurrence rules are defined in UTC
- recurrence IDs are derived from UTC dates
- conversions only happen when displaying or selecting
The One Pattern That Actually Works
UTC is storage and logic.
Local time is presentation only.
The “We’ll Just Fix It Later” Trap
Once mixed-timezone data exists:
- migrations become dangerous
- historical data becomes ambiguous
- bug fixes turn into guesswork
Stop the bleeding early.
Final Takeaway
UTC everywhere isn’t optional. It’s survival.
Member discussion