Why did my timestamps shift by one hour after the clocks changed?
A timestamp stored during standard time but shown as if it were daylight saving, or the reverse, lands exactly one hour away from the truth.
The most common scenario: you record an event in March, the clock springs forward, and your application now shows that event one hour later than it actually happened. The day has 23 hours on spring-forward and 25 on fall-back. Software that assumes every day has 24 hours breaks during both transitions.
Your storage layer or application code is probably confusing the moment of recording with the moment of display. The fix is to isolate storage from presentation. Store the instant. Display it later with the correct zone rules applied.
Is my database time zone causing the one-hour offset?
When your server and your data store disagree on what zone they occupy, timestamps drift by an hour. This shows up as a consistent one-hour offset on every row, not just around DST transitions.
MySQL exposes the problem clearly. The TIMESTAMP type converts values to the session time zone on both read and write. If your session time zone is SYSTEM, and your system clock is set to UTC, but your application code assumes the store holds local time, you get a one-hour shift every time.
PostgreSQL stores everything as UTC internally. But its timezone setting controls how timestamps without a zone are interpreted. If timezone is set to America/New_York and you insert 2026-03-08 02:30:00, PostgreSQL treats it as Eastern Time and stores the UTC equivalent. Read it back with a different timezone setting, and the displayed hour changes.
The fix: set your session time zone to UTC explicitly and keep it there. Never rely on the SYSTEM setting.
Can browser date parsing shift my timestamps by one hour?
JavaScript Date objects are milliseconds since 1 January 1970 UTC internally. Only the display methods apply the local time zone. A timestamp that looks correct in one browser may be off by one hour in another, because each browser gets the zone from the operating system.
Two places with the same offset today may diverge next month. A user in New York and a user in Toronto both use Eastern Time, but the United States and Canada do not always switch to DST on the same date. If your application parses "2026-03-08" with new Date("2026-03-08"), JavaScript interprets the string as local time. For a user whose system is set to a zone that has already switched to DST, the result may differ by one hour from a user whose system has not.
The rule: always specify a time zone when parsing date strings. Use new Date("2026-03-08T12:00:00Z") for UTC or new Date("2026-03-08T12:00:00-05:00") for a specific offset. Never pass a date-only string to the JavaScript Date constructor if accuracy matters.
Why does displaying UTC timestamps in local time go wrong by one hour?
This is the most common architectural mistake. You store timestamps in UTC, which is correct. Then you present them by applying the user's local time zone. If the conversion code is wrong, every timestamp shifts by one hour.
The conversion goes wrong in two ways:
- The application assumes the stored timestamp is local time, not UTC. It applies the time zone offset again, effectively doubling it.
- The application applies the wrong offset, typically because it uses a static offset like
-5instead of the IANA zone name likeAmerica/New_York. Static offsets do not account for DST changes.
The fix for case one: never assume. Always tag stored timestamps with a time zone indicator. ISO 8601 strings ending in Z are unambiguous. Unix epoch integers are always UTC. If you cannot tell which time zone a column uses, you have a data quality problem.
The fix for case two: use the IANA time zone data in your application code. Python's zoneinfo module, Java's ZoneId, and the tz collection for most languages give you the correct offset for any date, past or future. Do not hardcode offsets.
How to diagnose the source of the error
When timestamps are off by one hour, run through this checklist:
- Is the error consistent on every row, or does it appear only around DST transition dates?
- Compare the timestamp as stored in the data store with the same timestamp shown on a separate tool, like a Unix epoch converter.
- Check the session time zone:
SELECT @@session.time_zone;in MySQL,SHOW timezone;in PostgreSQL. - Look at the application code that converts the stored value to a string. Does it use an offset or a zone name?
- Test with a known UTC value, like
2026-06-01T12:00:00Z. Present it in the user's local time. If the result is off by exactly one hour, the conversion is applying the wrong DST rule.
Fixes for common stacks
Node.js
Use Intl.DateTimeFormat for presentation. It returns the correct IANA zone from the user's browser. Never use .toString() on a Date object for server-side logging; use .toISOString() which always outputs UTC.
javascript
const date = new Date();
const formatter = new Intl.DateTimeFormat('en-US', {
timeZone: 'America/New_York',
hour: '2-digit', minute: '2-digit'
});
console.log(formatter.format(date));
Python
Use datetime.now(tz=zoneinfo.ZoneInfo("UTC")) for storage. Use astimezone(zoneinfo.ZoneInfo("America/New_York")) for presentation. Never use datetime.now() without a time zone argument.
```python from zoneinfo import ZoneInfo from datetime import datetime
utc_now = datetime.now(ZoneInfo("UTC")) ny_now = utc_now.astimezone(ZoneInfo("America/New_York")) ```
MySQL
Set the session time zone to UTC at connection start:
sql
SET time_zone = '+00:00';
Use TIMESTAMP columns for events that happened at a specific moment. Use DATETIME for future events where the time zone rule may change before the event occurs.
PostgreSQL
Set timezone in postgresql.conf or per connection:
sql
SET timezone = 'UTC';
Use TIMESTAMPTZ (timestamp with time zone) for instants. Use TIMESTAMP (without time zone) only when you know the data has no zone attached and never will.
The next change that matters for your code is 31 December 2026, the last day on which leap seconds could be inserted under the current system. The 2022 resolution to abolish leap seconds by 2035 means no more irregular extra seconds to break your timers. But until then, every minute of your code that assumes 86,400 seconds per day is a minute that could fail during a leap second event. The one-hour offset you are debugging today is a warning: time is more complicated than your code thinks it is.