Skip to content

Postgres is built against tzdata 2025b while the image ships tzdata 2026d, so timezone conversions are ~18 months stale #2465

Description

@dwmoffatt

Summary

The public.ecr.aws/supabase/postgres image contains two different zoneinfo databases, and postgres is compiled against the older one. /usr/share/zoneinfo is Alpine's current tzdata-2026d, but the server is built with --with-system-tzdata pointing at a Nix-provided tzdata-2025b. Any zone whose rules changed between 2025b and 2026d converts incorrectly inside Postgres, while date in the same container gets it right.

It fails silently. There is no warning, and pg_timezone_names is a view over the same stale directory, so nothing inside the database can reveal the skew.

Affected versions

Confirmed on 17.6.1.136 and on the newest release, 17.6.1.173. Both use the identical nix store path, so the pin has not moved across any release in that range.

$ docker run --rm --entrypoint pg_config public.ecr.aws/supabase/postgres:17.6.1.173 --configure | tr ' ' '\n' | grep tzdata
'--with-system-tzdata=/nix/store/q42y30akm86ph3pzzdrp5qyrm7pg6sqy-tzdata-2025b/share/zoneinfo'

$ docker run --rm --entrypoint apk public.ecr.aws/supabase/postgres:17.6.1.173 list -I tzdata
tzdata-2026d-r0 x86_64 {tzdata} (Public-Domain) [installed]

The next release will carry the same pin unless it is bumped: flake.lock on develop resolves nixpkgs to fb7944c (2026-01-02), and tzdata at that revision is still 2025b.

Reproduction

British Columbia, Alberta and the Northwest Territories moved to permanent summer time, so their winter offsets changed. Morocco moved to permanent UTC on 2026-09-20.

select z, ('2027-01-15 12:00'::timestamp at time zone z) at time zone 'UTC' as utc
from unnest(array['America/Vancouver','America/Edmonton','America/Inuvik','Africa/Casablanca','America/New_York']) z;
zone returned correct (tzdata 2026d)
America/Vancouver 2027-01-15 20:00 19:00 (changed in 2026b)
America/Edmonton 2027-01-15 19:00 18:00 (2026c)
America/Inuvik 2027-01-15 19:00 18:00 (2026d)
Africa/Casablanca 2027-01-15 11:00 12:00 (2026c)
America/New_York 2027-01-15 17:00 17:00 (control, unchanged)

The same container disagrees with itself:

$ docker run --rm --entrypoint bash public.ecr.aws/supabase/postgres:17.6.1.173 -c '
  NIX=/nix/store/q42y30akm86ph3pzzdrp5qyrm7pg6sqy-tzdata-2025b/share/zoneinfo
  for z in America/Vancouver America/Edmonton; do
    echo "$z  usr=$(TZ=:/usr/share/zoneinfo/$z date -d "2027-01-15 12:00" +%z)" \
         " nix=$(TZ=:$NIX/$z date -d "2027-01-15 12:00" +%z)"
  done'

America/Vancouver  usr=-0700  nix=-0800
America/Edmonton   usr=-0600  nix=-0700

Which zones are affected

34 zone files differ between the two directories. I compared every one of them at 02:30 and 12:00 local time on every day from 2026-09-18 to 2028-12-31. These give wrong future instants inside Postgres:

  • America/Vancouver (and Canada/Pacific): an hour off every winter from 2026-11-01
  • America/Edmonton (and Canada/Mountain, America/Yellowknife): an hour off every winter from 2026-11-01
  • America/Inuvik: an hour off every winter from 2026-11-01
  • Africa/Casablanca and Africa/El_Aaiun: an hour off almost all year from 2026-09-20

The other 26 files differ only in past timestamps. Europe/Chisinau also moved its transition times by an hour (2026a), which only matters inside the transition hour.

Impact

Applications that convert a local wall clock to an instant server-side, with (date + time) AT TIME ZONE tz, which is the documented way to schedule from user input, persist the wrong instant with no error. Because the value is materialised at write time, every downstream consumer inherits it: reminders, notification scheduling and iCalendar feeds. The rows stay wrong after tzdata is eventually fixed, and for one-off events the original wall clock can't be recovered once the zone rules move, so the corruption isn't fully repairable after the fact.

Clients are usually correct, since Android/Hermes and recent browsers already ship 2026c or 2026d. So the database disagrees with the device that produced the input, which makes it look like an application bug rather than a platform one. It cost us a full misdiagnosis cycle.

Many current Node builds are also on 2025b (process.versions.tz), so a Node-based test agrees with the stale database by coincidence and can't detect this.

Suggested fix

Move the nix tzdata pin forward so the server is built against the same generation the image already ships. Ideally, also assert at build time that the two agree. A single conversion check on a recently changed zone would have caught this.

Workarounds (for anyone who finds this issue)

  • Overwriting the files under the nix store path inside a running container works, but it is lost when the container is recreated and can't be applied to a hosted project.
  • At the application level, the most robust option is to store the local wall clock and zone alongside the instant, so rows can be recomputed once tzdata is fixed. It has to be in place before the tzdata moves for the recompute to be lossless. For a zone whose new rule is a fixed offset, doing the conversion yourself for that zone also works in the meantime.

Environment

public.ecr.aws/supabase/postgres:17.6.1.173 (and :17.6.1.136), PostgreSQL 17.6, Alpine Linux, linux/amd64, Docker on Windows 11.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions