Why Time Is Hard in Software

A recurring meeting shifts by an hour. A midnight deadline fires at 23:00 the night before. These failures surface only on specific dates: the second Sunday of March, the first Sunday of November. The code runs perfectly for months, then breaks on a Sunday.

The root cause is that time is not linear. Days stretch to 25 hours or shrink to 23 when clocks change for daylight saving. Months run 28 to 31 days. Years span 365 or 366 days. The rules vary by country and get rewritten by governments.

The industry has converged on practices that eliminate most time-related bugs. Many developers still ignore them.

Always Store in UTC, Display in Local

Store all timestamps in UTC. Do not store local time. Do not store a UTC offset. Store the absolute point in time.

Convert from UTC to the user's local time zone at the moment of display. The same stored timestamp shows as 3:00 PM for someone in New York and 8:00 PM for someone in London. Both are correct.

UTC does not observe daylight saving. It has no discontinuities. A timestamp stored as 2026-09-30T14:00:00Z means the same instant everywhere.

The one exception: future events

Someone schedules a meeting for 10:00 AM on November 5, 2027 in New York. Store the local time and the time zone (America/New_York), not a UTC timestamp. The UTC offset on that future date might differ from today's offset if DST rules change. Convert to UTC only when you need to compare or sort.

IANA Time Zones vs UTC Offsets

A UTC offset like -05:00 tells you the difference from UTC right now. It does not tell you what the offset will be next month. It does not tell you when DST starts or ends.

A time zone like America/New_York encodes the entire history and future of timekeeping for that location: DST start, DST end, and the offset at any given date. This data comes from the IANA time zone database (tzdata), the canonical source for time zone rules.

Why abbreviations fail in code

Never hardcode offsets. Never use time zone abbreviations in logic. CST means Central Standard Time (US), China Standard Time, or Cuba Standard Time. IST means India Standard Time (UTC+5:30), Israel Standard Time (UTC+2), or Irish Standard Time (UTC+1, summer). BST means British Summer Time (UTC+1) or Bangladesh Standard Time (UTC+6).

Identifiers that work

Use IANA identifiers: Europe/London, Asia/Kolkata, America/New_York. Every major programming language has a library that reads the IANA database.

How do you handle the 23 and 25-hour day in time arithmetic programming?

When clocks spring forward, the day has 23 hours. When they fall back, the day has 25 hours. This is not a corner case. It happens every year.

The missing hour

A recurring event scheduled for 10:00 AM daily hits a problem on the spring-forward day. In the US, clocks jump from 01:59 to 03:00 on the second Sunday of March. 10:00 AM does not exist on that day.

The duplicated hour

On the fall-back day (first Sunday of November in the US), 10:00 AM occurs twice. The clock reaches 01:59, falls back to 01:00, and runs through 02:00 again.

How libraries resolve it

Python's zoneinfo and JavaScript's date-fns with time zone support skip or duplicate the missing or extra hour correctly. But only if you use IANA time zone names, not offsets.

When calculating duration across a DST boundary, "24 hours later" is not the same as "same time tomorrow." If you need "same time tomorrow" across a DST change, add one day to the date, not 24 hours to the timestamp.

What is the Year 2038 problem in time arithmetic programming?

Unix time counts seconds since 1970-01-01 00:00:00 UTC, ignoring leap seconds. Adding 86,400 seconds always gives the same time the next UTC day. The arithmetic is clean.

The 32-bit ceiling

Systems that store Unix time as a 32-bit signed integer will overflow on 2038-01-19 03:14:07 UTC. The integer wraps to negative, representing dates in 1901.

Where the risk lives

Most modern languages (Python 3, JavaScript, Java, Go) already use 64-bit numbers for time. Embedded systems, legacy databases, and C/C++ code may still use 32-bit. If you are writing code today that will still run in 2038, use 64-bit integers for timestamps.

The straightforward fix

Store timestamps as 64-bit integers or as ISO 8601 strings. Do not truncate to 32 bits. There is no reason to use a 32-bit timestamp in a new data schema.

Which libraries handle time arithmetic programming correctly?

Use a library that reads the IANA time zone database. Do not write your own time zone logic.

Python

zoneinfo has been in the standard library since Python 3.9. For older versions, use pytz. Always call .localize() and .normalize() with pytz.

JavaScript

date-fns with the date-fns-tz extension is reliable. moment.js works but is in maintenance mode. Luxon is a modern alternative.

Java and Go

Java: java.time (ZonedDateTime, ZoneId). Use ZoneId.of("America/New_York"). Go: the time package with a time zone database, or github.com/evanoberst/date.

What to skip

Libraries that only handle offsets, not IANA time zones. String manipulation of timestamps. Code that assumes all days have 24 hours.

Testing Time-Dependent Code

Write tests that run at DST transition boundaries. Test the spring-forward day (second Sunday of March in the US) and the fall-back day (first Sunday of November in the US). Test dates near the Year 2038 boundary if 32-bit timestamps are still in play.

A recurrence test

Create a recurring event at 10:00 AM. Advance one day across the spring-forward boundary. Verify the event fires at 10:00 AM on the other side, not 11:00 AM or 09:00 AM.

A duration test

Calculate the duration between two timestamps that span a DST transition. Verify it is 23 or 25 hours, not 24.

Environment hygiene

Use a fixed time zone for your test environment, not the system time zone. Never let tests depend on the machine's time zone setting.

How do you fix existing time arithmetic code?

Audit existing code for time zone handling. Replace offset-based logic with IANA time zone names. Check timestamp storage for 32-bit limits.

What to check first

Every place the code stores a local time without a time zone. Every place it adds or subtracts hours across a date boundary. Every place it compares timestamps from different sources.

Where to learn more

For a deeper look at how UTC offsets and DST rules interact, see the Time Zone Converter Help: Understanding Offsets and DST. It explains the difference between a time zone and an offset with concrete examples.

For the special case of leap seconds, which affect UTC but not Unix time, see Leap Seconds Explained: Why UTC Adjusts for Earth's Rotation. The IERS has added 27 leap seconds since 1972. The last was added 31 December 2016. A decision in 2022 set a target to abolish leap seconds by 2035.