Time & dates
Concept  ·  SQL & Querying

Stored-UTC vs displayed-local shifts 'daily' metrics by the TZ choice, and DST gives 23/25-hour days.

A TIMESTAMP column is a labeled clock face, not a moment — the same value means different instants in different zones, and on two days a year the label misbehaves: spring-forward has a hole (02:30 never occurs), fall-back a fold (one label, two instants). Durations on naive local times inherit both: the night of spring-forward is 23 wall-clock hours. Instants in UTC in storage; zones only at the edges; durations on instants.

See it break
soaks
guestin_utcout_utc
g1TIMESTAMPTZ '2027-03-14 06:30:00+00'TIMESTAMPTZ '2027-03-14 07:00:00+00'
The trap
SELECT
  guest,
  CAST(date_diff('minute', in_utc AT TIME ZONE 'America/New_York', out_utc AT TIME ZONE 'America/New_York'
    ) AS INT) AS mins
FROM
  soaks;
The fix
SELECT
  guest,
  CAST(date_diff('minute', in_utc, out_utc) AS INT) AS mins
FROM
  soaks;
INPUTYour queryThe fix
g19030

The soak session read 90 minutes, not 30 — subtracting two naive local wall-clock times across the spring-forward night counts the skipped 02:00 hour; computing on UTC instants gives the true duration.

The rule

Instants in UTC (timestamptz) in storage; zones applied only at display/input edges; durations computed on instants, never on labels.

timestamptz behavior and session-zone semantics vary; the nonexistent/ambiguous hours are universal.

Shows up elsewhere
BETWEEN on timestamps — the midnight trapHalf-open intervals
Try one →Prove it in practice →