Penang public holidays, scraped from the Penang state government portal ("Info Cuti Umum") and published as JSON.
The data is exactly what the portal lists. It does not add replacement days the portal leaves out, or one-off holidays declared after the list was gazetted.
One file per year in data/, e.g. data/2026.json:
{
"year": 2026,
"holidays": [
{
"date": "2026-03-21",
"name": "Hari Raya Puasa",
"type": "federal",
"tentative": true
}
]
}| Field | Description |
|---|---|
date |
ISO date |
name |
Name as published, in Malay |
type |
federal (Hari Kelepasan Am Persekutuan) or state (Hari Kelepasan Am Negeri) |
tentative |
Marked ( * ) Tertakluk kepada perubahan (subject to change) on the portal |
Two holidays can share a date, e.g. 7 July 2024.
A SvelteKit app on Cloudflare Workers serves the same data, bundled from data/ at build time. Its home page explains the API.
| Request | Response |
|---|---|
GET /years |
{ "years": [2023, 2024, 2025, 2026] } |
GET /2026 |
All holidays in 2026 |
GET /2026/3 |
Holidays in March 2026 |
A year or month comes back as { "holidays": [...] }, each holiday with the fields above, named in English unless you ask for Malay or Chinese (see Languages). The year and month aren't repeated, since they're in the path.
Holidays are named in English, in Malay as the portal publishes them, or in Chinese (simplified, as written in Malaysia). The API picks the language from:
?lang=ms,?lang=enor?lang=zh, e.g.GET /2026?lang=zh. Any other value is a 400.- Otherwise, the
Accept-Languageheader: the first language in it that's one of these three. - Otherwise, English.
The response's Content-Language header says which it is (ms, en or zh-Hans-MY). A holiday named in English or Chinese keeps the portal's Malay name in originalName:
{
"date": "2026-02-17",
"name": "农历新年",
"originalName": "Tahun Baru Cina",
"type": "federal",
"tentative": false
}The translations aren't official; see Credits. A name without one yet stays in Malay, without originalName.
The home page is in English at / and Malay at /ms. Its words are in messages/, compiled by Paraglide.
Successful responses are cached for an hour, by browsers and by Workers Cache, which answers repeat requests without running the Worker. Errors aren't cached. A response whose language came from Accept-Language is cached once for each value of the header (Vary: Accept-Language); ?lang= is cached once for everyone.
The Scrape workflow runs every 1 December, and on demand from the Actions tab. It commits any new or changed JSON files, which redeploys the Worker.
To run it locally (Node 24+):
pnpm install
pnpm scrape # every year currently on the portal
pnpm scrape 2027 # only 2027; fails if it isn't published yetThe portal only shows the last few years. Files for years that drop off are kept.
pnpm dev # local dev server
pnpm test
pnpm build
pnpm preview # run the production build in the Workers runtimeThe app deploys through Cloudflare's Git integration (Workers Builds) on every push to main:
- In the Cloudflare dashboard, go to Workers & Pages → Create → Import a repository and pick this repository.
- Keep the project name as
penang-holidaysto matchwrangler.jsonc. - Set the build command to
pnpm buildand the deploy command tonpx wrangler deploy.
To deploy manually instead, run pnpm run deploy.
General-purpose holiday libraries work out dates from rules and secondary sources, and can lag behind or differ from what a state actually gazettes. For Penang, the most reliable source is the state government itself, so this project takes its list straight from penang.gov.my and changes nothing.
If you need more than Penang, the holidays Python library covers a far bigger scope: countries and their subdivisions around the world, Malaysia's states among them. It's a great project, and worth a look if you need holidays beyond Penang.
The portal only names holidays in Malay. The English and Chinese names in src/lib/names.ts are translations, not official names:
- English names come from the holidays Python library's English translations for Malaysia, © Vacanza Team and individual contributors, licensed MIT.
- Chinese names come from the holiday table in Chinese Wikipedia's 馬來西亞公共假期 (Malaysian variant), by its contributors, licensed CC BY-SA 4.0.
The Penang flag in src/lib/assets comes from Wikimedia Commons and is licensed CC BY-SA 3.0. The favicon is a squared-up version of it.