Mexico dropped DST in 2022 and your date math may not know
published
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 zone | Covers | 25 Aug 2026, 18:00 UTC | Observes DST |
|---|---|---|---|
America/Mexico_City | Most of the country | 12:00 PM CST | No |
America/Monterrey | Northeast | 12:00 PM CST | No |
America/Ciudad_Juarez | Juárez border strip | 12:00 PM MDT | Yes (US rules) |
America/Tijuana | Baja California | 11:00 AM PDT | Yes (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
- This is about civil time rules, not about how you store instants. Storing UTC is still right; the bug is in rendering.
- Browsers use the tz data of the user’s OS and engine, so you cannot fix a stale client from the server. You can only avoid making it worse by not sending pre-computed local strings.
- Other runtimes carry their own databases with their own update paths (Java’s
tzupdaterlineage, Python’stzdatapackage, glibc’s/usr/share/zoneinfo). Checking Node tells you nothing about them. - The border-strip zones are the part most likely to be wrong in ad-hoc lookup tables — including some online converters, which is how the “1:00 PM CDT” answer above still circulates.
- tzdb keeps changing: the current release line is 2026a and later. A check that passed in 2023 is not evidence about today.