@whyshouldaicareone AI disaster a day, and the fix

Store UTC, show local: timestamps that survive zones

A post set for 22:00 went out at midnight. The rule and copy-paste code for PostgreSQL, Python and JavaScript.

The fix: PostgreSQL

Use timestamptz. PostgreSQL stores it as UTC and converts to the session TimeZone only on output. It does not keep the zone you typed, so save the user's zone name (like Europe/Warsaw) in its own column if you need it. AT TIME ZONE turns a local wall time into the instant, and back.

copy and paste
CREATE TABLE posts (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  publish_at timestamptz NOT NULL
);
INSERT INTO posts (publish_at) VALUES (
  TIMESTAMP '2026-10-25 22:00' AT TIME ZONE 'Europe/Warsaw');
SELECT publish_at AT TIME ZONE 'America/New_York' FROM posts;

The fix: Python

Use aware datetimes. Convert the user's local time to UTC for storage, convert back to the viewer's zone for display. The UTC constant needs Python 3.11 or newer.

copy and paste
from datetime import datetime, UTC
from zoneinfo import ZoneInfo

warsaw = ZoneInfo("Europe/Warsaw")
local = datetime(2026, 10, 25, 22, 0, tzinfo=warsaw)
print(local.astimezone(UTC).isoformat())
# 2026-10-25T21:00:00+00:00

The fix: JavaScript

toISOString() always returns UTC with a Z. Intl.DateTimeFormat shows it in any IANA zone. Leave timeZone out to use the viewer's own zone.

copy and paste
const when = new Date("2026-10-25T21:00:00Z");
when.toISOString();  // "2026-10-25T21:00:00.000Z"
new Intl.DateTimeFormat("en-GB", {
  timeZone: "America/New_York",
  hour: "2-digit", minute: "2-digit",
  timeZoneName: "short",
}).format(when);     // "17:00 GMT-4"

Check it worked

Run the same line with the machine set to two zones. A time written without Z or an offset gives a different instant in each. That is the bug. A string that ends in Z gives the same instant everywhere.

copy and paste
for tz in UTC Europe/Warsaw; do
  TZ=$tz node -p 'new Date("2026-10-25T22:00:00").toISOString()'
done
# 2026-10-25T22:00:00.000Z
# 2026-10-25T21:00:00.000Z  <- same text, other instant

Pro tip: the hour that happens twice

When clocks go back, one local hour repeats. Python's zoneinfo uses fold=0 for the first pass and fold=1 for the second. Adding a day to a local datetime keeps the wall-clock time, so the real gap can be 23 or 25 hours. Storing UTC avoids both. You only meet them when you read local times typed by people.

copy and paste
from datetime import datetime
from zoneinfo import ZoneInfo

w = ZoneInfo("Europe/Warsaw")
# clocks go back at 03:00 that night
t = datetime(2026, 10, 25, 2, 30, tzinfo=w)
print(t.utcoffset(), t.replace(fold=1).utcoffset())
# 2:00:00 1:00:00

Common mistake

Naive datetimes. datetime.utcnow() returns a datetime with no zone and is deprecated since Python 3.12; use datetime.now(UTC). In JavaScript, "2026-10-25T22:00:00" is read as local time, while a date-only string is read as UTC. Always write the Z or an offset. On Windows, ZoneInfo raises ZoneInfoNotFoundError until you run: pip install tzdata

Official docs

One fix of twenty: the whole Claude Code survival kit.

Follow @whyshouldaicare

A new disaster and its fix every day. Comment the word under a reel to get its guide.