I once spent three hours debugging a cron job that ran twice on the same night. The job processed pending orders at 2:00 AM, and somehow it processed everything twice — charging customers double. The next morning, I had angry emails and a support ticket that started with "URGENT."
The cause? The clocks fell back that night. 1:59 AM happened, then 1:00 AM happened again, and my cron job, which triggered at 1:30 AM local time, ran on both 1:30 AMs. Both 1:30 AM strings looked identical to the system, but they were actually two different moments in time.
That's the DST problem in a nutshell. And the fix, then and now, is Unix timestamps.
What DST Actually Does to Your Data
Daylight Saving Time creates two specific problems:
Spring Forward (gap): At 2:00 AM, the clock jumps to 3:00 AM. The entire 2:00 AM hour simply doesn't exist. If your app lets users schedule something at 2:30 AM on that day, what happens? Most systems pick a side — either reject it, push it to 3:00 AM, or silently store an ambiguous time.
Fall Back (overlap): At 2:00 AM, the clock falls back to 1:00 AM. The 1:00 AM to 2:00 AM hour happens twice. If your app records an event at 1:30 AM local time, was that the first 1:30 AM (EDT, UTC-4) or the second 1:30 AM (EST, UTC-5)? They're two different moments separated by an hour, but they share the same clock reading.
These aren't edge cases. They happen every year in most of North America and Europe. If your app stores local timestamps as strings or DATETIME columns without timezone context, you have a bug waiting to happen.
Why Unix Timestamps Are the Answer
A Unix timestamp is just a number — the seconds since 1970-01-01 00:00:00 UTC. It doesn't care about DST. It doesn't care about time zones. It's a point on the universal timeline, and every point is unique.
// Fall back 2026 in US Eastern:
// First 1:30 AM EDT (UTC-4) = 1776190200
// Second 1:30 AM EST (UTC-5) = 1776193800
const firstTime = 1776190200;
const secondTime = 1776193800;
const d1 = new Date(firstTime * 1000);
const d2 = new Date(secondTime * 1000);
console.log(d1.toISOString()); // 2026-11-01T05:30:00Z
console.log(d2.toISOString()); // 2026-11-01T06:30:00Z
Different numbers, different moments. Zero ambiguity.
If you had stored those as "2026-11-01 01:30:00" in a MySQL DATETIME column, both moments would look identical. Sorting, querying, or counting them would produce wrong results.
The Problems You'll Hit in Practice
1. Sorting Breaks
-- If you store local timestamps as text or DATETIME:
SELECT * FROM events ORDER BY event_time;
-- Two rows both show "2026-11-01 01:30:00"
-- Which one came first? The database can't tell.
With Unix timestamps:
SELECT * FROM events ORDER BY event_timestamp ASC;
-- 1776190200 → first 1:30 AM
-- 1776193800 → second 1:30 AM
-- Always correct.
2. Scheduled Jobs Run Twice (or Not at All)
This is the one that bit me. If your scheduler checks "is it 2:00 AM?" by looking at the local clock, then on fall-back night, 2:00 AM happens twice.
# Bad: triggers on system local time
if datetime.now().hour == 2 and datetime.now().minute == 0:
run_job()
# On fall-back night, this runs twice.
# Better: use UTC for scheduling
from datetime import datetime, timezone
if datetime.now(timezone.utc).hour == 6 and datetime.now(timezone.utc).minute == 0:
run_job()
# UTC doesn't observe DST. No duplication.
Even better, track the last run time as a Unix timestamp and compare:
last_run = get_last_run_timestamp() # stored as integer
if int(time.time()) - last_run >= 3600:
run_job()
save_last_run(int(time.time()))
3. Duration Calculations Go Wrong
If you record a start time and end time in local datetime strings, calculating elapsed time across a DST boundary gives you the wrong answer:
# Fall back: event started at 1:00 AM EDT, ended at 1:30 AM EST
# That's actually 1.5 hours in real time, not 30 minutes.
start = "2026-11-01 01:00:00"
end = "2026-11-01 01:30:00"
# If you subtract these naively as local time, you get 30 minutes.
# The real duration is 90 minutes.
With Unix timestamps:
start_ts = 1776186600 # 1:00 AM EDT = 05:00:00 UTC
end_ts = 1776193800 # 1:30 AM EST = 06:30:00 UTC
duration_minutes = (end_ts - start_ts) / 60
# 90 minutes. Correct.
4. Comparing Dates Across Time Zones
If you have servers in Virginia (US Eastern) and Frankfurt (CET), and both store local timestamps, comparing them is impossible without knowing the offsets. With Unix timestamps, comparison is trivial:
if server_a_timestamp < server_b_timestamp:
print("Server A event happened first")
The One-Caveat Pattern: When You Need Local Time Display
Unix timestamps are great for storage and logic, but humans don't read 1719820800. You need to convert to local time for display — and that's fine. Just do the conversion at the last possible moment:
// Storage: always Unix timestamp
const eventTimestamp = 1776190200;
// Display: convert to local time at the UI layer
const date = new Date(eventTimestamp * 1000);
console.log(date.toLocaleString('en-US', {
timeZone: 'America/New_York',
hour: '2-digit',
minute: '2-digit',
}));
// Shows correct local time, respecting DST
The browser knows the user's time zone and handles DST automatically. You don't need a DST database on the frontend. You just need to pass a clean Unix timestamp.
What About Naive Libraries?
Some date libraries have explicit DST-unsafe modes or make assumptions you don't expect. Always test around DST boundaries if your app uses:
datetime.fromtimestamp(ts)without a timezone argument — Python uses the system clocknew Date(string)without timezone markers — JavaScript may interpret bare dates as UTC or local depending on the format- MySQL
FROM_UNIXTIME()without settingtime_zonesession — uses the server's local time
The fix is always the same: be explicit. Pass the timezone. Set the session. Never assume.
Wrapping Up
DST is a political invention that software has to deal with twice a year. The ambiguity it creates is real and breaks things that look correct in testing.
Unix timestamps don't solve every time problem, but they solve this one completely. Store your temporal data as integers (seconds or milliseconds since epoch), and you never have to worry about whether 1:30 AM happened once or twice. The number is the truth.
For the display layer, convert to local time with the user's timezone — but only at that layer. Keep everything else in UTC epoch seconds. Your cron jobs, your queries, your cross-region comparisons, and your 2 AM self will all thank you.
For quick DST boundary checks, use the FastUnix Timestamp Converter to see what a timestamp looks like in different time zones.