Max Heinritz

Dates and Times (WIP)

Dates and times can be tricky to interact with while programming.

One approach is to represent all times in UNIX epoch milleseconds and let a thin layer of frontend code handle the conversion to local time for rendering. This works well for system-level events like created_at or logged_in_at, and it might be enough for some simple business domains.

But epoch milliseconds are insufficient for more complicated domains that involve scheduling future times, time ranges, and users coordinating across time zones. International freight deals with of those things, and this led me to spend a lot of time (heh) learning about these concepts at Flexport.

I’ve come to follow the terminology defined by the W3C here:

This article is a companion to that page with some more tactical best practices.

Table of contents


Let’s start with colloquial examples to illustrate a few concepts:

# date with a time zone
October 21, 2021 in America/Chicago

# date with a named UTC offset
October 21, 2021 Central Daylight Time

# date time with a numeric UTC offset
8:30AM on October 21, 2021 UTC-06:00

Notice the different types: date vs date time and offset vs time zone.

Key distinctions

UTC offset vs time zone

A UTC offset is an hour and minute offset from UTC, represented either as a numeric offset like “UTC+06:00” or a named offset like “Central Daylight Time (CDT)”.

A time zone is a geographic area with a label like “America/Chicago”.

There is a many-to-many relationship between UTC offsets and time zones. A time zone’s UTC offset usually changes twice per year for daylight savings, and also may be updated due to political decisions. The authoritative mapping between time zones and offsets is the IANA database.

Incremental time vs wall time

Incremental time is based on a progression of fixed integer units that increase monotonically from a specific point in time (called the “epoch”). UTC and offset-based times are different flavors of incremental time, and all can be converted to the UNIX epoch representation:

# UTC (Z is shorthand for UTC)

# numeric offset

# named offset

# seconds since 00:00:00 UTC 1 January 1970

Wall time corresponds to what a person would recognize the time to be if they looked at a clock and/or calendar mounted on a wall in a particular place. In its most basic form it’s represented like this:

2021-10-01T10:30:00 # no offset

Wall time can be used without a time zone to represent something like “this year my birthday is on Tuesday June 8, 2021”, which is true regardless of time zone or UTC offset. But in my experience, most business use cases for wall times involve anchoring to a specific place. More on this in the section Representing dates in logistics.

Date vs date time

A “date” is not the same as a “date time”. A “date” is a time range from midnight to midnight in a particular time zone. Usually this is a 24-hour range, but there are some exceptions for years with leap seconds and on daylight savings transitions. A “date time” is a particular instant in time, which may or may not be anchored to a time zone.

IANA database

The IANA database is standard mapping of time zones to IANA offsets. You can download the dataset here:

Here’s an example of what the rules look like.=:

Zone America/Chicago	-5:50:36 -	LMT	1883 Nov 18 12:09:24
			-6:00	US	C%sT	1920
			-6:00	Chicago	C%sT	1936 Mar  1  2:00
			-5:00	-	EST	1936 Nov 15  2:00
			-6:00	Chicago	C%sT	1942
			-6:00	US	C%sT	1946
			-6:00	Chicago	C%sT	1967
			-6:00	US	C%sT

Representing dates in logistics

When updating a delivery ETA, what the user is really trying to convey is something like:

The package is expected to be delivered to
764 Treat Ave, San Francisco, CA
by 10:30AM on 2021-10-23.

The most precise way to capture the user’s intention might be with a data structure like this:

    place: "764 Treat Ave, San Francisco, CA, 94110",
    wall_date_time: "2021-10-23T10:30",

But this rather unwieldy.