~/blog

Mexico dropped DST in 2022 and your date math may not know

published

#dates#timezones#javascript

TL;DR

Mexico abolished daylight saving time nationwide effective 30 October 2022, so America/Mexico_City is UTC-6 all year. The exception is the US border strip: America/Tijuana and America/Ciudad_Juarez still switch with US DST rules. Any code that hardcodes UTC-5 in summer, or runs on a runtime with pre-2022 tzdata, is an hour off from April to October — and it fails silently.

The problem

You convert an 8:00 PM CEST stream start to Mexico City time and print it in a schedule. Two implementations disagree by an hour:

20:00 CEST  →  1:00 PM CDT   ← wrong (assumes DST)
20:00 CEST  →  12:00 PM CST  ← correct since 2022

Nothing throws. No test fails unless someone wrote an assertion against a real August date. The output just quietly tells every Mexican reader to show up an hour late, and it only misbehaves during the northern-hemisphere summer, so a bug filed in January cannot be reproduced.

Why it happens

Three separate causes produce the same wrong hour.

1. The rule really did change. The IANA time zone database recorded it in release 2022f (28 October 2022):

Mexico will no longer observe DST except near the US border. Chihuahua moves to year-round -06 on 2022-10-30.

Release 2022g (29 November 2022) then split the border strip out:

A new Zone America/Ciudad_Juarez splits from America/Ojinaga.

Ciudad Juárez follows US DST like El Paso, Texas. Ojinaga was moved to US DST the following year. Everywhere else in Mexico stopped switching.

2. Your runtime carries its own copy of the rules. In Node, Intl formatting comes from bundled ICU, and the tzdata version is exposed on process.versions:

$ node -e "const v=process.versions; console.log(v.node, 'icu', v.icu, 'tz', v.tz)"
26.2.0 icu 78.3 tz 2026b

A Node build with tz older than 2022f, a container image whose system zoneinfo was never updated, or a JVM/Python install with an old bundled database will keep applying the pre-2022 rule no matter how current your application code is.

3. Someone stored an offset instead of a zone. -06:00 and America/Mexico_City look interchangeable until the political rules move. Offsets are a rendering of a rule at a moment in time; they are not the rule.

Here is what the three zones actually do on a summer date, from a current runtime:

IANA zoneCovers25 Aug 2026, 18:00 UTCObserves DST
America/Mexico_CityMost of the country12:00 PM CSTNo
America/MonterreyNortheast12:00 PM CSTNo
America/Ciudad_JuarezJuárez border strip12:00 PM MDTYes (US rules)
America/TijuanaBaja California11:00 AM PDTYes (US rules)

Note the trap in row three: Ciudad Juárez shows the same wall clock as Mexico City in summer, but for a different reason and with a different abbreviation — and in winter they diverge. Treating “Mexico” as one zone is wrong in both seasons.

What to do

Check what your runtime believes. In Node:

console.log(process.versions.tz); // e.g. '2026b' — anything < '2022f' is stale

In a browser, ask Intl directly instead — there is no version string, so probe the behaviour:

const summer = new Date(Date.UTC(2026, 7, 25, 18, 0, 0)); // 25 Aug 2026, 18:00 UTC
const hour = new Intl.DateTimeFormat('en-US', {
  timeZone: 'America/Mexico_City',
  hour: 'numeric',
  hour12: false,
}).format(summer);

console.log(hour); // '12' on current data, '13' on pre-2022f data

Format from an IANA zone, never from a stored offset.

const fmt = new Intl.DateTimeFormat('es-MX', {
  timeZone: 'America/Mexico_City',
  dateStyle: 'full',
  timeStyle: 'short',
});
fmt.format(new Date('2026-08-25T18:00:00Z'));

Do not collapse Mexico to one zone. If you store user locations, store the IANA zone id. America/Tijuana and America/Ciudad_Juarez are separate answers, and America/Ciudad_Juarez only exists in tzdata 2022g and later — code that maps Juárez to America/Ojinaga predates the split.

Pin the data, then update it. Treat tzdata like any other dependency: it changes several times a year for political reasons you do not control. If your deployment target ships its own zoneinfo, updating it is part of patching, not an optional chore.

Caveats

References