Dates & Time cheat sheet
A scannable Dates & Time reference: 33 short snippets across 14 topics, each linking back to the lesson it came from.
At a glance
| Topic | What it covers | |
|---|---|---|
| Unix timestamps & UTC | A Unix timestamp is the number of seconds (or milliseconds) since the epoch: 1970-01-01 00:00:00 UTC. It is | lesson |
| Time zones & UTC offsets | An offset like UTC+8 only says '8 hours ahead of UTC'. It does not tell you which region's rules apply — when daylight | lesson |
| Calendars, leap years and leap seconds | A year is a leap year if it is divisible by 4, unless it is divisible by 100, unless it is divisible by 400. Two | lesson |
| ISO 8601 and RFC 3339 formats | The T is required when a duration has a time part, because P1M is ambiguous otherwise: M before the T is months, after | lesson |
| Parsing and formatting dates safely | 03/04/2026 is 3 April in most of the world and 4 March in the United States. A parser that just works on this input is | lesson |
| Durations, periods and intervals | One month after 31 January is not a well-defined instant. February has no 31st, so the result must be clamped to the | lesson |
| Daylight saving and its edge cases | When a zone enters daylight saving, the local clock jumps forward and a range of local times never happens. When it | lesson |
| Monotonic clocks and elapsed time | The system wall clock is continuously corrected by NTP, by virtualisation hosts and by manual changes. It can jump | lesson |
| Date libraries compared | Libraries split into two camps: mutable date objects with implicit conversion, and separate types per concept. The | lesson |
| Storing dates in databases | The name TIMESTAMP WITH TIME ZONE is misleading: PostgreSQL does not store a zone. It stores a UTC instant and converts | lesson |
| Scheduling, cron and time-based triggers | An in-process scheduler is not durable. If the process is down at the scheduled moment, the run never happens and | lesson |
| The 2038 problem and 32-bit time | A signed 32-bit integer counting seconds from 1970 reaches its maximum on 2038-01-19T03:14:07Z. One second later it | lesson |
| Testing time-dependent code | Code that calls datetime.now() directly cannot be tested without patching a global. Accepting a clock as a parameter or | lesson |
| Internationalisation: locales, calendars and relative time | Do not hand-write month names or date order. Every platform ships locale data — the CLDR database — behind an API | lesson |
Quick snippets
Unix timestamps & UTC
What a Unix timestamp is
2026-09-17 12:00:00 UTC → 1787112000
2026-09-17 08:00:00 EDT → 1787112000 (same instant!)
2026-09-17 20:00:00 +08:00 → 1787112000
Store UTC, display local
const now = Math.floor(Date.now() / 1000); // seconds since epoch
const d = new Date();
console.log(d.toISOString()); // always UTC: 2026-09-17T12:00:00.000Z
Store UTC, display local
from datetime import datetime, timezone
now = datetime.now(timezone.utc)
print(int(now.timestamp())) # epoch seconds
print(now.isoformat()) # 2026-09-17T12:00:00+00:00Full lesson: Unix timestamps & UTC →
Time zones & UTC offsets
Use ISO 8601 with the zone
2026-09-17T12:00:00Z (Z = UTC)
2026-09-17T20:00:00+08:00 (with explicit offset)
2026-09-17T08:00:00-04:00 (same instant, different zone)Full lesson: Time zones & UTC offsets →
Calendars, leap years and leap seconds
The leap year rule, exactly
def is_leap(year):
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
# 2024 leap, 2023 not, 1900 not (divisible by 100), 2000 leap (divisible by 400)
assert [y for y in (2023, 2024, 1900, 2000) if is_leap(y)] == [2024, 2000]
Week numbers and the year boundary
from datetime import date
d = date(2025, 12, 29)
iso_year, iso_week, iso_day = d.isocalendar()
print(iso_year, iso_week, iso_day) # 2026 1 1 — belongs to ISO year 2026
# the trap: strftime year is NOT the ISO year
print(d.strftime("%G-W%V-%u")) # 2026-W01-1 (ISO year, ISO week)
print(d.strftime("%Y-W%W")) # 2025-W52 (calendar year, week 0-based)Full lesson: Calendars, leap years and leap seconds →
ISO 8601 and RFC 3339 formats
The pieces of a timestamp
2026-09-18 date only
10:30:00 time, no zone
2026-09-18T10:30:00Z UTC (Z means the same as +00:00)
2026-09-18T10:30:00+02:00 offset from UTC
2026-09-18T10:30:00.123Z fractional seconds
2026-09-18T10:30:00.123456789Z nanoseconds
2026-09-18T10:30:00+02:00[Europe/Paris] offset plus IANA zone (RFC 9557)
2026-W38-5 ISO week date
2026-261 ordinal date: day 261 of 2026
Durations, intervals and recurrence
// JavaScript has no duration parser; Temporal does (shipping in modern runtimes)
const d = Temporal.Duration.from("P1Y2M3DT4H5M6S");
const later = Temporal.PlainDate.from("2026-01-31").add({ months: 1 });
later.toString(); // "2026-02-28" — clamped, not overflowing into March
Why this and not something else
# RFC 3339 requires an uppercase T and Z, and a fixed-width offset
date -u +%Y-%m-%dT%H:%M:%SZ
# 2026-09-18T10:30:00Z
# GNU date can round-trip an offset into a zone
TZ=Europe/Paris date -d '2026-09-18T10:30:00+02:00' +%Y-%m-%dT%H:%M:%S%:zFull lesson: ISO 8601 and RFC 3339 formats →
Parsing and formatting dates safely
Ambiguous input and lenient parsers
new Date("03/04/2026") // parsed as March 4 (US, month first)
new Date("2026-03-04") // parsed as March 4 (ISO, but see the trap below)
new Date("2026-03-04T00:00") // interpreted as LOCAL time, not UTC
new Date("2026-03-04") // interpreted as UTC midnight
// two forms that look alike produce instants a day apart
Parse strictly, format explicitly
from datetime import datetime, timezone
# explicit format, no guessing
dt = datetime.strptime("2026-09-18 10:30", "%Y-%m-%d %H:%M")
try:
datetime.strptime("18/09/2026", "%Y-%m-%d")
except ValueError as e:
print("rejected:", e) # time data '18/09/2026' does not match format
# machine output: always ISO 8601 with an offset
print(dt.isoformat()) # 2026-09-18T10:30:00
print(dt.replace(tzinfo=timezone.utc).isoformat()) # ...+00:00
Parse strictly, format explicitly
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
# a naive datetime is a bug waiting to happen: no zone, no instant
naive = datetime(2026, 9, 18, 10, 30)
# attach a zone explicitly before any conversion
aware = naive.replace(tzinfo=ZoneInfo("Europe/Paris"))
utc = aware.astimezone(timezone.utc)
# subtract aware datetimes; subtracting naive ones across zones is meaningless
delta = datetime.now(timezone.utc) - utcFull lesson: Parsing and formatting dates safely →
Durations, periods and intervals
Three different concepts that share a name
from datetime import datetime, timedelta, timezone
from dateutil.relativedelta import relativedelta
start = datetime(2026, 1, 31, tzinfo=timezone.utc)
# duration: exact, unit-independent
print(start + timedelta(days=30)) # 2026-03-02T00:00:00+00:00
# period: calendar-aware, clamped
print(start + relativedelta(months=1)) # 2026-02-28T00:00:00+00:00
Addition is not associative
// the order of operations can change the answer
const d = new Date("2026-01-31T00:00:00Z");
const a = new Date(d); a.setUTCMonth(a.getUTCMonth() + 1); a.setUTCDate(a.getUTCDate() + 1);
const b = new Date(d); b.setUTCDate(b.getUTCDate() + 1); b.setUTCMonth(b.getUTCMonth() + 1);
a.toISOString();
b.toISOString();
// both land on 2026-03-01 here, but not for every date — pick and document an order
Business days and rounding
import numpy as np
from datetime import date
start = np.datetime64("2026-09-18") # a Friday
print(np.busday_offset(start, 5, roll="forward"))
# 2026-09-25 — five business days later, skipping the weekend
# holidays must be supplied; there is no universal list
holidays = ["2026-12-25", "2026-12-28"]
print(np.busday_offset("2026-12-24", 1, roll="forward",
holidays=holidays)) # 2026-12-29Full lesson: Durations, periods and intervals →
Daylight saving and its edge cases
Two clocks move in opposite directions
Europe/Paris, 2026
spring forward, 29 March 02:00 -> 03:00
01:58 01:59 03:00 03:01 (02:30 does not exist)
fall back, 25 October 03:00 -> 02:00
02:58 02:59 02:00 02:01 (02:30 happens twice)
Scheduling rules that survive a transition
# cron in local time is zone-dependent
30 2 * * * # in Europe/Paris this is skipped on 29 March
# cron in UTC has no gaps
30 1 * * * # 01:30 UTC is a real instant on every dayFull lesson: Daylight saving and its edge cases →
Monotonic clocks and elapsed time
The wall clock can move backwards
const start = Date.now();
await work();
const elapsed = Date.now() - start; // can be negative after an NTP step
// the correct tool
const t0 = performance.now();
await work();
const ms = performance.now() - t0; // monotonic, sub-millisecond
The API in each language
start := time.Now()
doWork()
// Sub uses the monotonic reading when both values have one
fmt.Println(time.Since(start))
// mixing a wall-clock-only time into the subtraction loses the monotonic part
wall := time.Unix(0, start.UnixNano())
fmt.Println(start.Sub(wall)) // wall-clock semantics, not monotonic
Measuring things correctly
# a timeout that cannot be fooled by a clock step
def run_with_timeout(call, timeout_s):
deadline = time.monotonic() + timeout_s
while True:
remaining = deadline - time.monotonic()
if remaining <= 0:
raise TimeoutError
if call(min(remaining, 0.25)): # never block longer than the deadline
returnFull lesson: Monotonic clocks and elapsed time →
Date libraries compared
The same task in each
now := time.Now() // has a monotonic reading
loc, _ := time.LoadLocation("Europe/Paris")
local := now.In(loc)
t, err := time.Parse(time.RFC3339, "2026-09-18T10:30:00Z") // strict
if err != nil { log.Fatal(err) }
_ = t.AddDate(0, 1, 0) // calendar arithmetic (year, month, day)
_ = t.Add(30 * 24 * time.Hour) // exact duration
Choosing and migrating
# pytz (legacy) vs zoneinfo (modern)
import pytz
from zoneinfo import ZoneInfo
dt = datetime(2026, 9, 18, 10, 30)
legacy = pytz.timezone("Europe/Paris").localize(dt) # explicit localize step
modern = dt.replace(tzinfo=ZoneInfo("Europe/Paris")) # plain constructor
# both are aware, but only zoneinfo behaves like the standard library expectsFull lesson: Date libraries compared →
Storing dates in databases
Why naive timestamps cause tickets
-- DANGER: a naive column silently accepts any local time
CREATE TABLE shipments (shipped_at TIMESTAMP); -- no zone
INSERT INTO shipments VALUES ('2026-09-18 10:30:00'); -- whose 10:30?
-- an application in Tokyo and one in Berlin now write different instants
-- into the same column, and nothing detects the difference.
Ranges, indexes and per-dialect traps
-- half-open range: includes the whole day, excludes the next day's midnight
SELECT count(*) FROM events
WHERE occurred_at >= '2026-09-18T00:00:00Z'
AND occurred_at < '2026-09-19T00:00:00Z';
-- a function on the column defeats the index
WHERE date(occurred_at) = DATE '2026-09-18' -- no index use
-- time-bucket aggregation
SELECT date_trunc('hour', occurred_at AT TIME ZONE 'UTC') AS bucket, count(*)
FROM events GROUP BY 1 ORDER BY 1;Full lesson: Storing dates in databases →
Scheduling, cron and time-based triggers
Misfires, catch-up and clusters
# systemd timer: OnCalendar with an explicit zone, Persistent handles downtime
# /etc/systemd/system/report.timer
# [Timer]
# OnCalendar=Mon..Fri 04:00
# Persistent=true
# RandomizedDelaySec=300
systemctl list-timers --all
systemctl status report.timer
Delivery semantics and durable delays
# idempotent daily job keyed on the intended date, not on when it ran
def run_daily(run_date: date):
with db.transaction():
if already_done(run_date): # a unique index on (job, run_date)
return
process(run_date)
mark_done(run_date)Full lesson: Scheduling, cron and time-based triggers →
The 2038 problem and 32-bit time
What actually overflows
import datetime
max32 = 2**31 - 1 # 2147483647
print(datetime.datetime.fromtimestamp(max32, datetime.timezone.utc))
# 2038-01-19 03:14:07+00:00
# what the wrap produces on a system that stores a signed 32-bit value
print(datetime.datetime.fromtimestamp(-2**31, datetime.timezone.utc))
# 1901-12-13 20:45:52+00:00
Where it hides
#include <stdio.h>
#include <time.h>
int main(void) {
printf("sizeof(time_t) = %zu bytes\n", sizeof(time_t));
// 4 bytes on a 32-bit build: affected
// 8 bytes on a 64-bit build: safe
return 0;
}
Planning the migration
# check a running system for 32-bit time
getconf LONG_BIT
date -d @2147483647 -u # the last representable second
# in a container, run with an offset clock to simulate:
# faketime '2038-01-19 03:14:08' ./your-programFull lesson: The 2038 problem and 32-bit time →
Testing time-dependent code
Freezing and travelling
# freezegun patches the clock; useful for code you cannot refactor
from freezegun import freeze_time
@freeze_time("2026-09-18T10:30:00Z")
def test_daily_report_uses_today():
assert report_date() == date(2026, 9, 18)
# travel forward to test a scheduled transition
with freeze_time("2026-09-18T10:30:00Z") as frozen:
frozen.tick(delta=timedelta(hours=2)) # explicit advance, not real waiting
Testing across zones and boundaries
# run the whole suite under several zones in CI
for tz in UTC Europe/Paris America/New_York Australia/Lord_Howe Asia/Kathmandu; do
TZ=$tz pytest -q || exit 1
doneFull lesson: Testing time-dependent code →
Internationalisation: locales, calendars and relative time
Non-Gregorian calendars
const dt = new Date("2026-09-18T10:30:00Z");
new Intl.DateTimeFormat("th-TH-u-ca-buddhist", { dateStyle: "long" }).format(dt);
// Buddhist era year 2569 (Gregorian year plus 543), rendered in Thai script
new Intl.DateTimeFormat("ar-SA-u-ca-islamic-umalqura", { dateStyle: "long" }).format(dt);
// the corresponding Hijri date, rendered in Arabic script
new Intl.DateTimeFormat("en-US-u-ca-hebrew", { dateStyle: "long" }).format(dt);
// the corresponding Hebrew calendar date, in English
The size and correctness cost
// Node: full ICU is the default in modern releases
process.config.variables.icu_small; // false means full data
// Browsers ship the data; in Node you may need to build with full-icu
// or rely on the small-icu default, which supports only English formatsFull lesson: Internationalisation: locales, calendars and relative time →
FAQ
Is this Dates & Time cheat sheet free to use?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
Algorithms Data Structures Computer Networks Operating Systems Character Encodings Hashing & Checksums
Last refreshed 2026-09-27.