cloudflare / cloudflare/workerd

Africa/Casablanca and Africa/El_Aaiun still resolve to UTC+01 after the 2026-09-20 switch (embedded tzdata predates IANA 2026c)

Open
#7,256 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
8.7k
Forks
739
Avg merge
2d 20h
Merged PRs (30d)
174

Description

## Summary

`workerd`'s embedded tzdata is older than IANA **2026c**, so `Africa/Casablanca` and `Africa/El_Aaiun` still resolve to **UTC+01** for instants on or after **2026-09-20T01:00:00Z**. The correct offset from that instant is **UTC+00**.

The transition is 14 days away. It is a permanent change to plain UTC, not a seasonal one, so every Worker that renders a wall-clock time for Morocco or Western Sahara will be an hour late from 2026-09-20 onward, indefinitely.

## Why this is easy to miss

The failure is silent. `Date` instants stay correct — only the wall-clock label is wrong — so responses look plausible and nothing throws. In our case, maghrib for `18:34Z` rendered as `19:34` local instead of `18:34`.

It also does not reproduce under some local toolchains. Bun already carries 2026c, so our unit tests and validation harness passed while production was wrong.

## Reproduction

```js
import { Miniflare } from "miniflare";

const script = `
export default { async fetch() {
const fmt = (d, tz) => new Intl.DateTimeFormat("en-GB", {
timeZone: tz, timeZoneName: "longOffset"
}).formatToParts(new Date(d)).find(p => p.type === "timeZoneName").value;
return Response.json({
before_casablanca: fmt("2026-09-19T12:00:00Z", "Africa/Casablanca"),
after_casablanca: fmt("2026-09-25T12:00:00Z", "Africa/Casablanca"),
after_el_aaiun: fmt("2026-09-25T12:00:00Z", "Africa/El_Aaiun"),
});
}};`;

const mf = new Miniflare({ modules: true, script, compatibilityDate: "2026-08-01" });
console.log(await (await mf.dispatchFetch("http://x/")).json());
await mf.dispose();
```

Observed on `workerd` **1.20260906.1** (also on 1.20260730.1, and reported against production Workers on 2026-08-25):

```json
{
"before_casablanca": "GMT+01:00",
"after_casablanca": "GMT+01:00",
"after_el_aaiun": "GMT+01:00"
}
```

Expected, per 2026c:

```json
{
"before_casablanca": "GMT+01:00",
"after_casablanca": "GMT",
"after_el_aaiun": "GMT"
}
```

| Instant (UTC) | workerd 1.20260906.1 | Expected (2026c) |
| --- | --- | --- |
| 2026-09-19T12:00:00Z | +01:00 | +01:00 |
| 2026-09-20T00:00:00Z | +01:00 | +01:00 |
| 2026-09-20T02:00:00Z | +01:00 | **+00:00** |
| 2026-10-15T12:00:00Z | +01:00 | **+00:00** |
| 2026-12-01T12:00:00Z | +01:00 | **+00:00** |

## Source for the rule

- IANA tzdb **2026c**, from the `[PROPOSED] Morocco goes to plain UTC on 2026-09-20` thread on `tz@iana.org` (Paul Eggert, 2026-07-04).
- Morocco's National Telecommunications Regulatory Agency posted to the same list.
- Moroccan Decree **2.26.530**.

Both `Africa/Casablanca` and `Africa/El_Aaiun` are covered by the decree, and both are affected here.

## Impact

Anything that formats a Moroccan wall-clock time in a Worker: scheduling, calendar exports, business-hours logic, prayer times. Because `Date` instants remain correct, existing tests that compare instants will not catch it — only tests that assert on rendered local time will.

## What we did meanwhile

We added a runtime-conditional override that formats Casablanca/El_Aaiun instants at or after `2026-09-20T01:00:00Z` in UTC, but only while the runtime disagrees, so it disables itself once the embedded tzdata catches up. Happy to share that if useful, though the real fix is the tzdata bump.

Please let me know if a minimal repro repo would help.

Contributor guide

Open the contributing guide

Research direction

Start by running the supplied Miniflare/workerd reproduction and inspect how workerd embeds and updates tzdata. Verify the 2026-09-20 transition for both Africa/Casablanca and Africa/El_Aaiun, with rendered offsets changing from GMT+01:00 before the transition to GMT afterward; add or run coverage that asserts these local-time results.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, javascript
Domain
backend, internationalization
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.