Profile    Mohammed Shiroz Status   Loading  
Logo
Share This
Back to blog
Filter by:
Tags
//Article title

Timezones: The Bug That Waits Patiently for Daylight Saving Time

About Post

Timezone bugs are patient. They don't show up in code review, they don't fail your tests, and they don't appear when you deploy. They wait, sometimes for months, for the one night the clocks change. Then a reminder goes out an hour late, a report drops a payment from "today", or a scheduled job runs twice.

I work from Doha, where there's no daylight saving time at all. That makes it very easy to forget that much of the world moves its clocks twice a year. Europe and the US went back an hour only a few weeks ago, and somewhere a bug that slept peacefully since March just woke up.

Here are the mistakes that cause most of them, why each one bites, and the fix.

Mistake 1: storing local time

The mistake: saving timestamps in the server's time, or the user's, without saying which. Then the server moves region, a second office opens, or DST kicks in, and half your data means something different from the other half.

Why it bites: a local time like 2025-10-26 01:30 in London happened twice this year, once before the clocks went back and once after. Without the timezone, you can't tell which.

The fix: store everything in UTC. Keep the app timezone set to UTC in config/app.php and make sure the database connection doesn't quietly convert values. UTC has no daylight saving, no ambiguity and no surprises.

Mistake 2: converting in the wrong place

The rule: UTC everywhere inside the system, local time only at the edges, when data comes in from a person and when it goes out to one.

// In: the user typed a local time
$startsAt = Carbon::parse($request->input('starts_at'), $user->timezone)->utc();

// Out: show it in the viewer's timezone
$booking->starts_at->setTimezone($viewer->timezone)->format('d M Y, H:i');

Notice it's the viewer's timezone on the way out, which isn't always the creator's. A maintenance visit booked by staff in Doha and viewed by an owner in London should show each of them their own clock.

Mistake 3: storing an offset instead of a timezone

The mistake: saving the user's timezone as +00:00 or +01:00.

Why it bites: an offset is a snapshot, not a place. London is +00:00 in winter and +01:00 in summer. Store the offset in July and every winter date you calculate with it is an hour off.

The fix: store the IANA timezone name, like Europe/London, Asia/Qatar or America/New_York. PHP and Carbon know the rules for each, including when they change.

Mistake 4: "a day" is not always 24 hours

On the day the clocks go forward, a local day has 23 hours. On the day they go back, it has 25. That breaks any code that treats "tomorrow" as "plus 86,400 seconds".

$t = Carbon::parse('2025-03-29 09:00', 'Europe/London');

$t->copy()->addDay();      // 2025-03-30 09:00 BST: same wall-clock time
$t->copy()->addHours(24);  // 2025-03-30 10:00 BST: 24 real hours later

Both are correct; they answer different questions. "Remind me at the same time tomorrow" wants addDay(). "Expire this link in 24 hours" wants addHours(24). Decide which one you mean.

Mistake 5: "today" in the wrong timezone

A dashboard shows "payments received today". The query uses whereDate('paid_at', today()), and today() is a UTC day. For a team three hours ahead of UTC, the first three hours of their morning belong to "yesterday".

The fix: work out the day's boundaries in the user's timezone, then convert them to UTC for the query:

$tz = $user->timezone; // e.g. 'Asia/Qatar'

$start = now($tz)->startOfDay()->utc();
$end   = now($tz)->endOfDay()->utc();

$total = Payment::whereBetween('paid_at', [$start, $end])->sum('amount');

As a bonus, this keeps the query index-friendly, unlike wrapping the column in a date function.

Mistake 6: scheduling in local time

Laravel's scheduler lets you run a task in a specific timezone, like ->dailyAt('01:30')->timezone('Europe/London'). It's handy, but think about what happens at the transitions: in spring, UK clocks jump from 01:00 to 02:00, so 01:30 never happens that night; in autumn, 01:30 happens twice. The Laravel docs themselves recommend avoiding timezone scheduling when you can for exactly this reason.

The fix: schedule system jobs in UTC. For per-user schedules ("send each tenant a reminder at 9 AM their time"), run a job every few minutes or every hour in UTC that finds the users whose local time has just passed 9 AM and who haven't been sent today's reminder yet. Store the "already sent" fact so a repeated hour can't send twice.

Mistake 7: treating dates as timestamps

Some values aren't moments in time. A birthday, a contract start date, a rent due date: these are calendar dates, and they should be the same date everywhere in the world.

Why it bites: store 2025-12-01 as midnight UTC, convert it to a timezone behind UTC, and it becomes 30 November. JavaScript does this to you for free: new Date('2025-12-01') is parsed as midnight UTC, so in the Americas it displays as the previous day.

The fix: use a DATE column, send it in your API as a plain "2025-12-01" string, never convert it between timezones, and be careful about passing it to new Date() on the front end.

Three rules that prevent most timezone bugs: store moments in UTC, store places as IANA timezone names, and keep calendar dates as plain dates. Convert only when talking to humans.

Test the dangerous days on purpose

Don't wait for March. Freeze time at the transitions and see what your code does (a simplified test; ReminderScheduler stands in for your own class):

public function test_reminder_time_survives_the_clock_change(): void
{
    $this->travelTo(Carbon::parse('2025-10-25 09:00', 'Europe/London'));

    $next = app(ReminderScheduler::class)->nextRunFor($this->londonUser);

    $this->assertSame('2025-10-26 09:00',
        $next->setTimezone('Europe/London')->format('Y-m-d H:i'));
}

Write a few tests like this around the spring and autumn transitions in the timezones your users live in. They're cheap, and they catch the bugs that otherwise only appear twice a year.

What's the strangest timezone bug you've chased? Mine always seem to involve a report that's "off by one" only for some users.

Comments (0)
Leave your review

Thanks for your valuable comments. Your comments has been updated and appreciate your getting in touch...

01. About Shiroz

Mohammed Shiroz

Hi, I'm Mohammed Shiroz, a software engineer and AI enthusiast from Sri Lanka who turns ideas into intelligent, real-world solutions. With over 9 years of hands-on experience, I currently lead real estate ERP development at Kate Group, a...

03.My Projects

04. Categories

Ready To order Your Project ?

Get in Touch
Close