How to Store a Timestamp So It Still Makes Sense Next Year
Most timestamp bugs are not discovered when the code is written. They are discovered on the Sunday in autumn when one hour happens twice.
By Nishanth Narayanan · 2026-08-06 · 7 min read
Store the instant, display the local time
The rule that prevents almost every timestamp bug is to store when something happened as an unambiguous instant, and to work out what that looked like locally only at the moment you show it to somebody.
In practice that means storing UTC, either as an ISO 8601 string with a trailing Z, or as a timestamp type that your database genuinely treats as absolute. Converting to a user's zone is a display concern, exactly like formatting a currency.
Storing local time throws away information you cannot recover. Given "2026-11-01 01:30" recorded in New York, there is no way to know which of the two 01:30s it was, because the clocks went back that morning and that local time occurred twice.
The two hours that break things
The hour that never happens. When clocks spring forward, local time jumps. In the European Union, 02:00 becomes 03:00 on the last Sunday in March. A datetime of 02:30 on that date in that zone is not a real moment. Libraries handle this differently: some throw, some shift forward, some shift back. If your code silently accepts it, you have a bug that fires once a year.
The hour that happens twice. When clocks fall back, one local hour repeats. Sorting records by local timestamp puts them in the wrong order across that boundary. Calculating a duration by subtracting local times gives an answer an hour out. Deduplicating by local timestamp can merge two genuinely different events.
Both problems disappear if the stored value is UTC.
The exception: future recurring events
There is one case where storing UTC is wrong, and it catches people who have just learned to store UTC.
If a user sets a recurring alarm for 07:00 every weekday in London, they mean 07:00 local, forever. Converting that to 06:00 UTC and storing it will be correct until the clocks change, at which point the alarm starts going off at 08:00 local.
For future recurring events, store what the user actually meant: the local time and the zone identifier, so 07:00 plus Europe/London. Resolve it to an instant each time you need to fire it. The rule is that past events are instants, while future recurring intentions are local times with a zone attached.
Never store an offset as the zone
Storing "UTC+1" instead of "Europe/London" loses the rules. An offset tells you what the difference was at one moment; it does not tell you what it will be in June, and it cannot tell you what it was before the last legislative change.
Time zone rules change more often than people expect. Governments alter daylight saving dates, abolish the practice, or shift a country's base offset, and the IANA time zone database is updated several times a year to track it. Software that stores identifiers picks those updates up. Software that stored a number does not.
The same argument rules out storing abbreviations. As covered in our piece on why abbreviations are ambiguous, CST alone does not identify a zone.
Watch the fractional offsets
If you represent an offset internally, use minutes, not hours. India is UTC+5:30 and Nepal is UTC+5:45, so an integer hour field is wrong for well over a billion people. This is a common enough failure that we test for it explicitly on this site after shipping exactly that bug: an earlier version of our conversion tables anchored to the current hour and discarded the minutes, which was right for London to New York and thirty minutes wrong for anything involving India.
A short checklist
Store past events as UTC instants. Store future recurring events as local time plus an IANA zone identifier. Never store a bare offset or an abbreviation as if it identified a zone. Represent offsets in minutes. Convert for display only. And write at least one test that runs in both January and July, because a conversion bug that only appears in one season will otherwise ship.
Frequently asked questions
Should I store timestamps in UTC or local time?
Store past events as UTC instants. Store future recurring events as a local time plus an IANA zone identifier, because the user means the local time to stay fixed even when the clocks change.
Why is storing an offset like UTC+1 a problem?
An offset records what the difference was at one moment. It cannot tell you what the offset will be in another season, and it does not track legislative changes. Store the zone identifier instead, such as Europe/London.
What breaks when clocks go back?
One local hour occurs twice, so local timestamps in that hour are ambiguous. Sorting, deduplicating and duration arithmetic all give wrong answers unless the stored value is UTC.
About the author
Nishanth Narayanan builds and maintains GlobalTimeTools from Coimbatore, India. He works with distributed teams across Indian, European and US time zones, which is where most of these questions came from.