Monthly Archives: October 2023

london new york tokyo and moscow clocks

Handling Nonexistent Times in Software

The Problem

When your app lets users pick a date and time, like scheduling a meeting or setting a reminder, there’s a weird case you might never expect: What if the time they select… never existed?

It happens because of Daylight Saving Time (DST) changes.

For example, in Beijing on April 10, 1988, DST started and clocks jumped from 2:00 AM straight to 3:00 AM. That means:

What you expectWhat actually happened
1988-04-10 02:30 AMNever existed

So if a user picks 2:30 AM on that day, they’ve chosen a phantom time.

What Happens in Your Frontend

Let’s say you’re using JavaScript on the frontend.

Example:

const d = new Date('1988-04-10T02:30:00+08:00');
console.log(d.toString());

What you’d expect:

“Sun Apr 10 1988 02:30:00 GMT+0800 (China Standard Time)”

What might actually happen:

“Sun Apr 10 1988 03:30:00 GMT+0900 (China Daylight Time)”

Why?
Because JavaScript’s Date object silently corrects the time to a valid equivalent. Instead of throwing an error, it shifts forward to the next valid moment—losing 1 hour in the process.

This can break an app’s logic:

  • A booking might shift forward without warning.
  • An event could appear at the wrong time.
  • Logs might misalign with reality.

Time Zone History, a Bigger Problem

People think time zones are just UTC offsets (like UTC+8), but sometimes it is not true:

  • Time zones change.
  • DST rules change.
  • Historical quirks are baked into “Asia/Shanghai” and other zones.

For example, the Asia/Shanghai zone was UTC+8 in 1987, but UTC+9 during DST from 1986–1991. And in some TZ database versions, the rules were even wrong because of bad historical data.

This means that even past times aren’t always consistent across systems.

The Best Practice is Always Use UTC Internally

  • Store all times in UTC (e.g., as ISO strings or Unix timestamps).
  • Convert to local time zones only when displaying to the user.

Why? Because local time zones can change in the future. If you stored “2022-01-15 12:00 America/São Paulo” and Brazil canceled DST (they did), your saved time would have a different meaning later on.

It is important that you use timestamps in one standard timezone (UTC). A timestamp is absolute. It tells you exactly when something happened, but it doesn’t say what local time the user saw. A timestamp has no timezone context.


The Solution

Detecting Invalid Times

If you’re using Luxon, you can check if a time is valid:

import { DateTime } from 'luxon';

const dt = DateTime.fromObject(
  { year: 1988, month: 4, day: 10, hour: 2, minute: 30 },
  { zone: 'Asia/Shanghai' }
);

console.log(dt.isValid); // false
console.log(dt.invalidReason); // 'unsupported zone or time'

This helps identifying the error early.

Automatically Correcting It

Luxon (and Moment.js with timezone) will auto-correct the time by default unless you configure it otherwise. For example:

console.log(dt.toString());
// 1988-04-10T03:30:00.000+09:00

It is good practice to warn the user or refuse invalid inputs to avoid silent correction.

Converting Between Time Zones

Use Intl.DateTimeFormat if you don’t want extra libraries.

function convertTimeZone(date, timeZone) {
  return new Date(date.toLocaleString('en-US', { timeZone }));
}

const dt = new Date('1988-04-10T02:30:00Z'); // UTC time
const shanghaiTime = convertTimeZone(dt, 'Asia/Shanghai');

console.log(shanghaiTime.toString());
// Might shift automatically to valid time if the original is invalid.

Implementing in a Frontend App

User Flow

  1. User picks a date/time:
    → e.g., 1988-04-10 02:30 AM in Shanghai time.
  2. Frontend:
    • Converts to UTC using Luxon or native Intl API.
    • Validates the time:
      • If it’s valid: Save it.
      • If it’s invalid: Show a warning: “The time you selected doesn’t exist due to daylight saving changes. Please pick a different time.”

Handling Invalid Times with Luxon

import { DateTime } from 'luxon';

function validateDateTime(dateStr, zone) {
  const dt = DateTime.fromISO(dateStr, { zone });
  if (!dt.isValid) {
    alert('The time you picked is invalid because of DST! Please choose a different time.');
  } else {
    console.log('All good:', dt.toUTC().toISO());
    // Save to backend in UTC!
  }
}

validateDateTime('1988-04-10T02:30:00', 'Asia/Shanghai');

Why This Affects Your App

  • Silent bugs:
    Your app might “move” user-specified times forward (spring forward) or duplicate events during the fall back (2 AM might happen twice).
  • Confused users:
    Users might book something at 2:30 AM but it actually happens at 3:30 AM—leading to frustration.
  • Cross-platform inconsistencies:
    Different devices or browsers might have different timezone data, especially on older OSes or browsers.

Best Practices

Do ThisDon’t Do This
Store times in UTCStore raw local times
Use timezone-aware libraries (Luxon)Rely only on native JS Date everywhere
Validate user input in local timeAssume all times are valid
Always tag your times with time zoneOmit time zone info in string formats
Update timezone databases regularlyAssume timezone rules never change

What About Databases?

  • PostgreSQL: Great TZ support, but auto-adjusts invalid times (no error). Always store in UTC.
  • MySQL: Basic TZ support, often outdated unless you load TZ data manually. Handle conversions in app code.
  • MongoDB: No built-in time zone magic; dates are stored as UTC. You need to handle TZ logic in your app.

Basically: DBs are good at storing time, but you (the app) must handle time zone logic.

What’s a Time Zone Database?

To figure out what offset applies to a time zone at a specific moment, your app/library uses a time zone database.

  • Linux & Mac: use the IANA TZ Database.
  • Windows: has a different TZ database (cause mismatches).
  • JavaScript (modern browsers): uses IANA data internally via Intl API.

Problem:
If your TZ data is outdated, your app gets the wrong time.

Good practice: update your libraries regularly.


Looking Ahead: Temporal API

TC39’s Temporal API (currently a Stage 3 proposal) will finally fix all these messy Date problems in JS with a proper, built-in ZonedDateTime.

Example:

const zdt = Temporal.ZonedDateTime.from({
  timeZone: 'Asia/Shanghai',
  year: 1988,
  month: 4,
  day: 10,
  hour: 2,
  minute: 30,
});
console.log(zdt.toString());
// It will immediately tell you if it’s invalid or shift properly.

TL;DR

  • DST creates gaps (nonexistent times) and overlaps (duplicate times).
  • Your web app’s frontend might silently shift times without warning.
  • Always validate times when working in local zones, especially around DST change dates.
  • Store everything in UTC + use timezone-aware libraries to handle conversions safely.

The distinction between past, present, and future is only a stubbornly persistent illusion. — Albert Einstein