Storing and exchanging times

datetime · Advanced · 10 min

Data that crosses systems is where time zone mistakes become permanent.

A small set of conventions avoids most problems:

  • Instants (when something happened: created_at, log lines, payments): store in UTC. In databases, use a time-zone-aware column type such as PostgreSQL timestamptz, and pass aware datetimes to the driver. In JSON and logs, write ISO 8601 with an offset: 2025-03-01T12:00:00+00:00 or with a Z.
  • Future local events (a meeting at 9:00 in Berlin next October): store the local date-time and the IANA zone name. Time zone rules may change before then; converting to UTC now could freeze the wrong offset.
  • Calendar dates (birthdays, due dates): store a date, not a datetime at midnight, which shifts a day when converted between zones.

Convert to the user's local zone only at the edges, when displaying.

When reading data, decide explicitly what naive values mean. A CSV with 2025-03-01 09:00 is ambiguous; attach the source's zone with replace(tzinfo=ZoneInfo(...)) at the boundary and keep everything aware inside.

JSON has no date type. Serialise with isoformat() and parse with fromisoformat(); for numeric APIs, document whether timestamps are seconds or milliseconds.

Example

import json
from datetime import UTC, date, datetime
from zoneinfo import ZoneInfo

event = {
    "created_at": datetime(2025, 3, 1, 12, 0, tzinfo=UTC).isoformat(),
    "starts_local": "2025-10-28T09:00",
    "zone": "Europe/Berlin",
    "due": date(2025, 4, 1).isoformat(),
}
text = json.dumps(event)
data = json.loads(text)

created = datetime.fromisoformat(data["created_at"])
start = datetime.fromisoformat(data["starts_local"]).replace(tzinfo=ZoneInfo(data["zone"]))
print(created.tzinfo, start.utcoffset())     # UTC 1:00:00
print(start.astimezone(UTC).isoformat())     # 2025-10-28T08:00:00+00:00
print(date.fromisoformat(data["due"]))       # 2025-04-01

Remember

  • Store instants in UTC with an offset in ISO 8601
  • Store future local events as local time plus IANA zone name
  • Use date for calendar dates and convert to local time only for display

Watch out for

  • Storing a birthday as a UTC datetime at midnight, which displays as the previous day in western time zones.

Check yourself

  1. A user books a dentist appointment for 10:00 Europe/Paris in 18 months. What should you store?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/datetime.html#datetime.datetime.isoformat