Why Does My App Show the Wrong Time After a DST Change?

A timestamp lands in your error tracker. The event happened at 02:30. But 02:30 in what context? Without a zone, the number is orphaned. The fix costs one extra column and a rule you enforce from day one.

Store the instant in Coordinated Universal Time. Append a Z to your ISO 8601 string: 2026-10-25T09:00:00Z. Or use a 64-bit integer count of seconds since the Unix epoch. This is a fixed point. It does not shift when a government redraws its clocks.

Store the IANA zone name separately when local context matters. A meeting saved as 2026-10-25T09:00:00+02:00 is pinned to an offset that Central European countries abandon that very morning. Saved as 09:00 local in Europe/Berlin, it lands correctly whatever the rules become.

What you are storing Store this Why
Past event (log, bill, timestamp) UTC instant only The rendering is a display concern; the fact is fixed
Future event (meeting, alarm, deadline) Local wall time + IANA zone name The offset that will apply is not yet knowable
Recurring schedule (every Tuesday at 09:00) Rule in local terms + IANA zone name Expand to instants at read time
Audit log UTC in ISO 8601 with Z Never rely on the server's local configuration

What Is the Difference Between an IANA Zone Name and a UTC Offset?

An offset is a number. A zone is a rule. The gap between them is where your code breaks.

IANA zone names, America/Chicago, Asia/Kolkata, Pacific/Auckland, encode a region's entire history of timekeeping: when daylight saving started and ended, when the country shifted its base offset, when the government made an unscheduled change. These names are the only reliable identifier for a location's time. They come from the IANA Time Zone Database.

A UTC offset, +05:30, -04:00, is valid for one instant. It cannot tell you what the offset will be next Tuesday. The DST rule is not encoded in the number. An offset saved today is wrong tomorrow if the jurisdiction changes its clocks.

Abbreviations are worse. IST means India Standard Time (UTC+05:30), Israel Standard Time (UTC+02:00), and Irish Standard Time (UTC+01:00). CST maps to six different zones. No registry arbitrates. Render abbreviations if users expect them, but resolve them from a known IANA zone, not from a global lookup.

Never Use Offsets Alone for Future Dates

A user creates a meeting for 15 March 2027 at 10:00. Your system records -04:00 because that is the current offset in New York. On 12 March 2027, the United States shifts to daylight saving time. New York moves to -04:00 on that date anyway. But in other years, the shift date changes. If the US Congress ever passes the Sunshine Protection Act and makes daylight saving permanent, a meeting recorded as -05:00 for a date in November would be wrong by one hour.

A fixed offset cannot predict future times. Only a zone rule can. Store America/New_York and the local wall time. Let the database compute the correct offset when you query.

How Does the IANA Database Reach Your Code?

The IANA time zone database, often called tzdata, is the single source of truth for civil timekeeping rules worldwide. It reaches your application through three paths:

  1. Operating system updates. Linux distributions, macOS, and Windows each ship tzdata updates. These updates are how your server learns that a country abolished DST or changed its base offset.

  2. Language runtimes. Python's zoneinfo module (standard library since Python 3.9) reads the system tzdata. Java's java.time zone rules come from the system or from a bundled tzdata package. JavaScript's Intl API uses the browser's or operating system's tzdata.

  3. Browsers. When a user's browser renders a time in Europe/London, it is using the system tzdata.

In 2022, the IANA database saw a record number of releases. Multiple countries abolished or changed DST, and each change required a new release. A hard-coded list of shifts is dangerous. The list changes every year, sometimes more than once.

Common Pitfalls in Date/Time Code

Pitfall 1: Parsing user input like "3pm EST". You do not know which EST the user means. Accept a city or IANA zone name instead. Use a time zone picker that shows unambiguous identifiers.

Pitfall 2: Using the server's local time. datetime.now() without an argument returns the server's configured zone. If the server moves to a different data center, or if the system administrator changes the zone setting, all your timestamps shift. Always call datetime.now(timezone.utc) or the equivalent in your language.

Pitfall 3: Assuming DST shifts are one hour. Lord Howe Island in Australia moves by thirty minutes. Some jurisdictions have shifted by 45 minutes or two hours. Hard-coding a one-hour adjustment is wrong.

Pitfall 4: Storing a time zone abbreviation as the primary identifier. See the IST problem above. Store the IANA zone name.

Test Across Zones and DST Shifts: What to Do

Write tests that cover the change moments. You need at least three test cases per zone you support:

  1. A date in standard time. Verify the offset matches the standard offset.
  2. A date in daylight saving time. Verify the offset matches the DST offset.
  3. A date during the change itself. This is where bugs live.

For each change, test the gap (spring-forward) and the overlap (fall-back). Use a known change date: for the United States in 2026, clocks spring forward on 8 March and fall back on 1 November. For Europe, spring forward is 29 March, fall back is 25 October.

Set your test server's zone to a non-UTC value. Pick America/Denver. It is far from UTC and observes DST. This catches code that calls datetime.now() without an argument.

How Should You Handle Missing and Duplicate Hours at DST Changes?

At a DST change, two things happen that need an explicit policy.

Non-existent times (spring-forward gap). At 02:00 on the change date, clocks jump to 03:00. The local time 02:30 never occurs. If a user enters 02:30 as an alarm time on that day, pick a rule. Shift forward to 03:30. Shift backward to 01:30. Or reject the input. Choose deliberately and document the choice.

Ambiguous times (fall-back overlap). At 02:00 on the change date, clocks fall back to 01:00. The local time 01:30 occurs twice: once at 01:30 UTC-04 (first occurrence) and once at 01:30 UTC-05 (second occurrence). Which one do you mean? Python's zoneinfo exposes a fold attribute: 0 for first, 1 for second. Java offers resolver strategies. Most libraries silently pick the later instant. Decide which policy applies to your use case. Prefer the first occurrence for billing. Prefer the second for reminders. Or record the UTC instant so the question never arises.

How Do I Pick a Date Library in Python, JavaScript, Java, or C#?

These are pointers to the standard library features you need.

Python: Use zoneinfo from the standard library (Python 3.9+). Create a zone with ZoneInfo("America/Chicago"). Handle ambiguous times with the fold parameter on datetime.replace() or datetime.timetz(). Skip pytz for new code. zoneinfo is the maintained replacement.

JavaScript: Use the Intl API. Intl.DateTimeFormat with a timeZone option of "America/Chicago" handles display correctly. For arithmetic, use the Temporal proposal when it becomes stable. Until then, reach for date-fns-tz or @js-joda/core.

Java: Use java.time (Java 8+). ZonedDateTime stores a local date-time and a ZoneId. ZoneId.of("America/Chicago") loads the IANA rules. Handle gaps and overlaps with ZonedDateTime.ofLocal() and a resolver style.

C#: Use TimeZoneInfo. TimeZoneInfo.FindTimeZone("America/Chicago") returns the zone. ZonedDateTime handles conversions. For older versions, the tzdata package provides IANA data.

Where to Find the Key Time Zone References