LabHub

Blog

The Technical Hell of Time and Date — TimeZone, DST, Leap Seconds, Temporal API, NTP Complete Guide (2025)

한국어English日本語

Prologue — "October 27, 2024, 02:30" Might Not Exist

On the day summer time ends in Europe, when 03:00 arrives, the clock falls back to 02:00. That means 02:30 appears twice that day. If a coupon was issued in an event at 02:30 that day, which 02:30 does the coupon belong to?

Conversely, on the last Sunday of March, Europe jumps from 02:00 to 03:00. The time 02:30 does not exist at all. If a user saves "reservation at 02:30," when will it run?

These aren't just UX issues. Order sorting in financial trading, chronological log analysis, event ordering in distributed systems, payroll calculations — real systems break on this subtlety. Jon Skeet (author of Noda Time) famously said: "Time is a joke. Seriously."

This article explains the technical foundations of handling time and date from scratch. What UTC is and how it differs from GMT, why Unix time ignores leap seconds, why the IANA time zone database gets updated frequently, how to handle DST transitions safely, the infamous pitfalls of new Date(), why the JavaScript Temporal API is a once-in-a-decade revolution, how NTP/PTP synchronize clocks in distributed systems, and even Google Spanner's TrueTime.

If the Unicode article said "strings are a hard problem," time is harder. Strings are at least deterministic within a single computer, but time is entangled with Earth's rotation changes, political decisions by nations, and OS clock drift.


1. Three Concepts of Time — Instant, Local, Civil

When dealing with time in programs, the three distinctions you must make first:

1.1 Instant

The absolute point "right now." No matter where on Earth you observe, the value is the same. Unix timestamp (seconds elapsed since 1970-01-01T00:00:00Z) is the representative form.

Example: 1760000000 (the epoch in seconds corresponding to 2025-10-09T07:33:20Z)

1.2 Civil Date/Time

Values read from a calendar and clock, like "April 15, 2026, 13:30." Ambiguous without a time zone.

1.3 Zoned Date/Time (civil time attached to a time zone)

"2026-04-15 13:30 Asia/Seoul". Now convertible to an Instant.

1.4 Core Rules

"Just store in UTC" is half right and half wrong. If a Seoul user sets "alert at 00:00 on December 25, 2026," storing it converted to UTC may cause the date to change under DST shifts or time zone changes. In such cases you must preserve the original civil+tz.


2. UTC, GMT, TAI, UT1 — The Confusion of Time Standards

2.1 GMT (Greenwich Mean Time)

Mean solar time at Greenwich, UK. Standardized in the 19th century for maritime navigation. Legally still used in some UK official documents, but in technical writing "GMT" is almost always a colloquial synonym for UTC.

2.2 UTC (Coordinated Universal Time)

The international standard. Based on atomic time, extremely precise, but inserts leap seconds to align with Earth's rotation. UTC = TAI - (accumulated leap seconds).

2.3 TAI (International Atomic Time)

Average of about 400 atomic clocks worldwide. No leap seconds. As of 2025 it is 37 seconds ahead of UTC. GPS time is based on TAI (not UTC), with a 1980 reference offset.

2.4 UT1

Time actually aligned with Earth's rotation. UTC must stay within 0.9 seconds of UT1, which is why leap seconds are inserted.

2.5 Practical Summary


3. Unix Timestamp — The Price of Simplicity

3.1 Definition

Seconds (or ms/ns) elapsed since 1970-01-01T00:00:00Z.

3.2 Where Do Leap Seconds Go?

When a leap second is inserted in real Earth time, UTC passes through 23:59:60 once. How does Unix time handle this?

Two approaches:

  1. Slew (recommended): slowly stretch time over several hours before and after the leap second to absorb it. Used by Google/AWS/Meta. No moment is "skipped."
  2. Smear: the NTP server slews so that the leap-second day runs roughly 86,401 seconds instead of 86,400. The OS clock progresses smoothly.
  3. Jump: jump back from 23:59:59 to 00:00:00. Many systems crash or generate duplicate events.

Historical incidents:

Future: in 2022 the General Conference on Weights and Measures (CGPM) decided to abolish leap seconds by 2035. After that, adjustments will be made on a scale of decades using "leap minutes" or similar coarser units.

3.3 The Year 2038 Problem

If Unix time is stored as a 32-bit signed integer, it overflows after 2038-01-19T03:14:07Z. A time bomb for 32-bit embedded systems (IoT, legacy database formats).

Mitigation: most modern OSes/languages have moved to 64-bit, but legacy C/C++ binaries, some DB schemas, and file formats must be audited. Verify time_t size explicitly.

3.4 Year Zero Issue vs Negative Epoch

-62135596800 is 1 AD January 1 UTC. Earlier dates (BCE) are representable or not depending on the protocol. Java's Instant.MIN is -9999999-01-01, Instant.MAX is 9999999-12-31.


4. ISO 8601 — The String Representation Standard

4.1 Basic Formats

2026-04-15                       date
2026-04-15T13:30:00              local time
2026-04-15T13:30:00Z             UTC
2026-04-15T13:30:00+09:00        Seoul time
2026-04-15T13:30:00.123456789Z   nanosecond precision
2026-W16-3                       week-based (Wednesday of ISO week 16)
P1Y2M10DT2H30M                   duration (1y 2mo 10d 2h 30m)
2026-04-15/2026-04-20            interval

4.2 RFC 3339 — A Subset of ISO 8601

A subset restricted by IETF for internet protocols. Lowercase T allowed, time zone offset mandatory, etc.

4.3 Everyday Pitfalls

Lesson: when exchanging as strings, always include the time zone offset explicitly. Use official ISO 8601 parsers (Java Instant.parse, Python 3.11+ fromisoformat, JS Temporal.Instant.from).


5. Time Zones — The Politics of Time

5.1 What Time Zones Do

They determine the offset from UTC. But the offset:

5.2 IANA Time Zone Database (tz database, zoneinfo, Olson DB)

Maintained by Paul Eggert since 1986, the textbook of time-zone history. Keyed by names like Asia/Seoul, America/New_York, it contains every offset, DST rule, and historical change that region has ever used.

Example: Asia/Seoul changed from UTC+8:30 to UTC+9 on March 21, 1954. The offset shifted several times until 1961, and DST existed from 1987 to 1988 before being abolished. To convert "January 1, 1955, 00:00 Seoul" to UTC today, an API must know this history.

5.3 tzdata Updates

New versions appear 5–10 times per year. Recent changes:

Operational importance:

5.4 Ambiguity of Abbreviations

CST — which country?

IST — even worse:

Rule: use abbreviations for display only. In programs, use IANA names (Asia/Seoul, America/Chicago).

5.5 POSIX TZ Strings

An older format but still alive: EST5EDT,M3.2.0,M11.1.0. Never use it — use IANA names.


6. DST — The Live Minefield

6.1 "The Time That Doesn't Exist"

March 31, 2024 in Europe: 02:00 jumps to 03:00. 02:30 does not exist.

// JavaScript, if you don't know what you're doing
new Date('2024-03-31T02:30:00+01:00').toISOString()
// "2024-03-31T01:30:00.000Z" — not the 02:30 you intended

Handling:

6.2 "The Time That Appears Twice"

October 27, 2024 in Europe: 03:00 falls back to 02:00. 02:30 exists twice.

6.3 Calendar Reservation Disaster

If "Monday 9:00 AM weekly meeting" is stored converted to UTC:

Storing the original civil+tz is the correct answer. Split DB columns into (timestamp, timezone, recurrence_rule).

6.4 The Trend Toward Abolishing DST

The EU decided in 2019 to "abolish DST from 2021," but it was delayed by member-state disagreements. In 2022 the US "Sunshine Protection Act" (permanent DST) passed the Senate but stalled in the House. Turkey, Russia, Iceland, China, etc. have already abolished it.

From the developer's perspective: abolition decisions get finalized suddenly, just months in advance, so you must always keep the tzdata update chain alive.


7. Language-Specific datetime APIs

7.1 JavaScript Date — Infamous Design

In 1995 Netscape quickly copied Java's java.util.Date, and that Java API was later deprecated (replaced by Joda Time / java.time). JS, however, couldn't fix it for decades due to compatibility.

Problems:

Libraries like Moment.js, date-fns, Luxon, Day.js filled the gap.

7.2 Temporal — The Savior of 2025

Stage 3 → late 2024 rolled out to browsers/Node. Main types:

const now = Temporal.Now.zonedDateTimeISO('Asia/Seoul')
const oneHourLater = now.add({ hours: 1 })
const duration = now.until(oneHourLater) // Temporal.Duration

Why it's revolutionary:

7.3 Python

from datetime import datetime
from zoneinfo import ZoneInfo
dt = datetime(2026, 4, 15, 13, 30, tzinfo=ZoneInfo("Asia/Seoul"))
dt.astimezone(ZoneInfo("America/New_York"))

7.4 Java

7.5 Go

7.6 Rust


8. Monotonic Clock vs Wall Clock

8.1 The Difference

8.2 Why It Matters

For measuring elapsed time, always use monotonic:

// Bad — if NTP jumps back, this goes negative
const start = Date.now()
await doWork()
const elapsed = Date.now() - start

// Good
const start = performance.now()
await doWork()
const elapsed = performance.now() - start

Timeouts, rate limiters, benchmarks should all use monotonic clocks.

8.3 Accessing Monotonic by Language


9. NTP, PTP — Clock Sync in Distributed Systems

9.1 NTP (Network Time Protocol)

9.2 PTP (Precision Time Protocol, IEEE 1588)

9.3 Google TrueTime

Built for Spanner DB to perform globally consistent transactions. Treats time not as a single value but as an [earliest, latest] interval.

Similar idea: CockroachDB's HLC (Hybrid Logical Clock) — combines NTP with a logical clock.

9.4 Practical Lessons


10. Calendar Systems — Gregorian Isn't Everything

10.1 Major Calendars

10.2 Unicode CLDR / ICU

Multilingual calendar support:

new Intl.DateTimeFormat('ja-JP-u-ca-japanese', { year: 'numeric', month: 'long', day: 'numeric' })
  .format(new Date('2026-04-15'))
// "令和8年4月15日"

new Intl.DateTimeFormat('ko-KR-u-ca-buddhist').format(new Date('2026-04-15'))
// "불기 2569년"

10.3 Leap Year Rule (Gregorian)

2000 was leap (400); 1900 and 2100 are common (100).

"Annual February 29 meeting" occurs only once every four years. Outlook and others offer fallback options like "last day of previous month" or "first day of next month."

10.4 The September 3–13, 1752 Gap

When Britain switched from the Julian to the Gregorian calendar in 1752, September 2 was followed by September 14. Russia did this in 1918. Few libraries model this historical gap precisely (Java GregorianCalendar and Noda Time do, in part).


11. Time Handling in Databases

11.1 PostgreSQL

Recommendation: use timestamptz unless you have a specific reason not to.

11.2 MySQL

Caution: TIMESTAMP results depend on the server's time_zone session variable. Recommended: SET time_zone = '+00:00' per connection.

11.3 MongoDB

11.4 DynamoDB, Redis

11.5 ClickHouse


12. Timestamps in Logs and Metrics

12.1 Principle: Store UTC, Display Local

12.2 Correlation and Clock Skew

With microservices A, B, C where A calls B and B calls C, if each server's clock differs slightly:

12.3 Store as ISO 8601 or RFC 3339

2026-04-15T13:30:00.123456789Z

The Z (= UTC) is essential. Strings without a zone are ambiguous to parse.

12.4 Event Streams Like Kafka

Each message has both a producer timestamp (assigned by the producer) and log append time (recorded by the broker). Order by offset; treat time as informational.


13. Ten Real-World Bug Cases

13.1 "Cron scheduled for 2 AM doesn't run on the DST day"

Cause: 02:30 doesn't exist in that time zone. Cron expressions use local time. Fix: run cron in UTC, or set OnCalendar=UTC explicitly.

13.2 "JWT exp expires oddly early"

Cause: server/client clock skew. Fix: 5–30 seconds leeway. Enforce NTP sync.

13.3 "Email 'Received' header is in the future"

Cause: some relay server's clock runs ahead. Fix: ask the infra team to check NTP. Spam filters treat this as a suspicious signal.

13.4 "Birthday alert comes a day early or late"

Cause: stored in UTC, user moved to a different tz. Fix: store birthdays as PlainDate (no time). Map to 00:00 in the user's tz when sending.

13.5 "Displays as 1970-01-01"

Cause: null timestamp treated as 0, then formatted as a Date. Fix: null-check.

13.6 "2038 test fails"

Cause: 32-bit time_t. Fix: verify 64-bit compilation.

13.7 "Duration is negative"

Cause: used the wall clock. NTP corrected backward. Fix: monotonic clock (performance.now, time.monotonic).

13.8 "Event logs are out of order"

Cause: clock skew between servers. Fix: OpenTelemetry Span Link + logical clock.

13.9 "User booked 'today' but it saved as 'tomorrow'"

Cause: browser tz ≠ server tz. Parsing new Date(...) on the server uses server tz. Fix: use Temporal.PlainDate.from(...) in the browser, or ISO string + explicit tz offset.

13.10 "Vacation days compute as 0.96 across a DST transition"

Cause: (endMs - startMs) / (24 * 3600 * 1000) — DST makes a day 23 or 25 hours. Fix: civil-aware operations like ChronoUnit.DAYS.between(zonedStart, zonedEnd).


Storage

Conversion

DST

Synchronization

Libraries

Operations


15. Archive of Historical Events

15.1 2007 US DST Date Change

The US government moved DST start three weeks earlier. Many Outlooks and Blackberries kept following the old rules and meetings were off by an hour. MS shipped an emergency patch.

15.2 2011 Samoa Time Zone Change

Crossing the International Date Line meant December 30, 2011 did not exist. December 29 was followed by December 31. Aligning with New Zealand trading hours.

15.3 2016 Turkey

DST abolished with a same-day decision. Older Android devices didn't update automatically and people were an hour late to work.

15.4 2018 North Korea

Changed from UTC+8:30 to UTC+9 (as a gesture toward reunification). tzdata updated.

15.5 2023 Death of GMT?

UK government discussed time-zone policy post-Brexit. No change so far.

15.6 High-Frequency Trading in Finance

NASDAQ and others record order sequence with nanosecond accuracy. PTP + shared atomic clocks. A 1-microsecond difference can decide trades worth millions.


Closing — "There Is More Than One Time"

One recurring view from this article: there is no single concept called "time." There are many layers — Instant, Civil time, Zoned time, Monotonic, Logical time — each fit for a different purpose. Most program bugs arise when these layers get mixed up.

Five core principles:

  1. Store in UTC, display in local — and preserve original civil+tz for recurring schedules
  2. Sort and compute in Instant, display in ZonedDateTime
  3. Use monotonic for elapsed time (wall clock can jump)
  4. Use IANA names for time zones (no abbreviations)
  5. Include DST transition days in test cases

Like Unicode, time is an area where the complexity of human society seeps into software. DST is changed by political decisions, holidays shift with religious calendars, Earth's rotation slows so leap seconds are inserted. In a reality where "just use UTC" doesn't solve everything, developers must carry a layered model of time in their heads.

The next article dives into Consensus in distributed systems — Raft/Paxos, the CAP theorem, and eventual consistency. See how the inconsistency of time we saw here entangles with the core problems of distributed systems (Lamport clock, Vector clock, Hybrid Logical Clock, etc.).

Understanding time isn't just a technique for avoiding bugs. It's also a philosophical question about what "happened at the same time" means. As relativity tells us, in a distributed system there is no "global current time" — we only deal with approximations of it.

Comments

No comments yet.

Sign in to leave a comment