Malaysia Calendar API
Malaysia's most complete calendar API โ public holidays, school calendar, exam schedules, and MCP server for AI tools.

Data source: Official government gazette (JPM BKPP), JAKIM, KPM, MPM. Not scraped from third-party websites.
Links
Packages
Published to npm under the @catlabtech scope:
Features
- 49 public holidays for 2026 from official gazette (Warta Kerajaan) โ federal + state-specific
- 16 states + 3 Federal Territories with aliases (KL, JB, Penang, etc.)
- Weekend-aware โ Kedah/Kelantan/Terengganu use Fri-Sat (Kumpulan A), all others use Sat-Sun (Kumpulan B), with Johor's historical switch tracked
- Cuti ganti (replacement holiday) auto-calculation per state weekend config
- Business day calculator โ per-state, holiday-aware
- School calendar โ terms, holidays, KPM cuti perayaan (Lampiran A/B/C)
- Exam schedules โ SPM, STPM, MUET, PT3
- iCal subscription feeds โ per-state
.ics feeds
- MCP Server for AI agents โ 13 tools for Claude, ChatGPT, and other assistants
- TypeScript SDK (
@catlabtech/mycal-sdk) with typed responses
- OpenAPI 3.1 spec + interactive docs
- Trilingual โ Bahasa Melayu, English, Chinese names (ไธ่ฏญๆฏๆ)
Quick Start
git clone https://github.com/Junhui20/malaysia-calendar-api.git
cd malaysia-calendar-api
pnpm install
pnpm --filter @catlabtech/mycal-core build
pnpm --filter @catlabtech/mycal-sdk build
cd packages/api && npx wrangler dev
pnpm --filter @mycal/web dev
pnpm validate
pnpm test
API Examples
Base URL: https://mycal-api.huijun00100101.workers.dev/v1
List holidays
curl "https://mycal-api.huijun00100101.workers.dev/v1/holidays?year=2026&state=selangor"
curl "https://mycal-api.huijun00100101.workers.dev/v1/holidays?year=2026&type=islamic"
curl "https://mycal-api.huijun00100101.workers.dev/v1/holidays?year=2026&state=KL&month=3"
Check a date
curl "https://mycal-api.huijun00100101.workers.dev/v1/holidays/check?date=2026-03-21&state=KL"
Response:
{
"data": {
"date": "2026-03-21",
"dayOfWeek": "Saturday",
"isHoliday": true,
"isWeekend": true,
"isWorkingDay": false,
"isSchoolDay": false,
"holidays": [
{
"id": "2026-hari-raya-aidilfitri-1",
"name": { "ms": "Hari Raya Aidilfitri", "en": "Eid al-Fitr", "zh": "ๅผๆ่" },
"type": "islamic",
"status": "confirmed"
}
]
}
}
Business days
curl "https://mycal-api.huijun00100101.workers.dev/v1/business-days?start=2026-03-01&end=2026-03-31&state=selangor"
curl "https://mycal-api.huijun00100101.workers.dev/v1/business-days/add?date=2026-03-01&days=10&state=selangor"
School calendar
curl "https://mycal-api.huijun00100101.workers.dev/v1/school/is-school-day?date=2026-03-21&state=selangor"
curl "https://mycal-api.huijun00100101.workers.dev/v1/school/holidays?year=2026&group=B"
curl "https://mycal-api.huijun00100101.workers.dev/v1/school/exams?year=2026&type=spm"
Next holiday
curl "https://mycal-api.huijun00100101.workers.dev/v1/holidays/next?state=penang"
State resolution
curl "https://mycal-api.huijun00100101.workers.dev/v1/states/resolve?q=kl"
Full API Reference
| Endpoint | Description |
|---|
GET /v1/holidays | List holidays (filter by year, state, type, status, month) |
GET /v1/holidays/check | Is this date a holiday/weekend/working day/school day? |
GET /v1/holidays/today | Today's holiday status |
GET /v1/holidays/next | Next upcoming holiday |
GET /v1/holidays/between | Holidays in date range |
GET /v1/holidays/leave-optimizer | Suggest annual-leave days to spend for the longest consecutive break |
GET /v1/business-days | Count business days between dates |
GET /v1/business-days/add | Add N business days to a date |
GET /v1/states | All 16 states + 3 FTs with weekend config |
GET /v1/states/resolve | Resolve alias (KL, penang, jb) to canonical code |
GET /v1/school/terms | School term dates + day counts |
GET /v1/school/holidays | School holidays + KPM cuti perayaan |
GET /v1/school/exams | SPM, STPM, MUET, PT3 schedule |
GET /v1/school/is-school-day | Is this a school day? |
GET /v1/feed/ical/:state | iCal subscription feed |
GET /v1/feed/csv/:state | CSV/spreadsheet export of holidays |
GET /v1/data/* | Raw, cacheable open-data snapshots โ manifest, states, holidays/:year, all (no API key required) |
GET /v1/prayer-times/:zone | JAKIM prayer times for a zone (MYT-formatted); GET /v1/prayer-times/zones lists the zone directory |
POST /v1/keys | Issue an API key (admin-gated for now; GET /v1/keys/whoami checks the caller's tier) |
See the full OpenAPI 3.1 spec for request/response schemas.
SDK Usage
import { MyCalClient } from "@catlabtech/mycal-sdk";
const cal = new MyCalClient();
const result = await cal.check("2026-03-21", "selangor");
console.log(result.isWorkingDay);
const holidays = await cal.holidays({ year: 2026, state: "KL" });
const workDays = await cal.businessDays("2026-03-01", "2026-03-31", "selangor");
console.log(workDays.businessDays);
const terms = await cal.school.terms({ year: 2026, group: "B" });
const exams = await cal.school.exams({ year: 2026, type: "spm" });
const isSchool = await cal.school.isSchoolDay("2026-03-21", "selangor");
MCP Server
Connect the Malaysia Calendar API to Claude, ChatGPT, or any MCP-compatible AI assistant.
Setup with Claude Desktop / Claude Code
Add to your MCP configuration:
{
"mcpServers": {
"malaysia-calendar": {
"command": "npx",
"args": ["@catlabtech/mycal-mcp-server"]
}
}
}
| Tool | Description |
|---|
get_malaysia_holidays | Get public holidays (filter by year, state, type) |
check_malaysia_holiday | Check if a date is a holiday or working day |
next_malaysia_holiday | Find the next upcoming holiday |
malaysia_business_days | Count working days between two dates |
malaysia_long_weekends | Find long weekends (3+ days) |
malaysia_leave_optimizer | Find the most efficient leave days to take to maximise consecutive days off |
list_malaysia_states | List all states with weekend config |
resolve_malaysia_state | Resolve alias (KL, JB) to canonical code |
malaysia_holiday_changes | Recent data changes |
malaysia_school_terms | School term dates and day counts |
malaysia_school_holidays | School holidays (cuti penggal, cuti perayaan) |
malaysia_exams | SPM, STPM, MUET, PT3 exam schedule |
malaysia_is_school_day | Check if a date is a school day |
State Codes
| Code | Aliases | Group | Weekend |
|---|
johor | jhr, jb | B | Sat-Sun (was Fri-Sat 2014-2024) |
kedah | kd, kdh | A | Fri-Sat |
kelantan | kel, kb | A | Fri-Sat |
terengganu | trg, kt | A | Fri-Sat |
perak | prk, ipoh | B | Sat-Sun |
pulau-pinang | penang, pg | B | Sat-Sun |
selangor | sel, sgr | B | Sat-Sun |
negeri-sembilan | ns, n9 | B | Sat-Sun |
melaka | mlk, malacca | B | Sat-Sun |
pahang | phg, kuantan | B | Sat-Sun |
perlis | pls, kangar | B | Sat-Sun |
sabah | sbh, kk | B | Sat-Sun |
sarawak | swk, kuching | B | Sat-Sun |
kuala-lumpur | kl | B | Sat-Sun |
wp-putrajaya | putrajaya, pjy | B | Sat-Sun |
wp-labuan | labuan, lbn | B | Sat-Sun |
State aliases are case-insensitive. Use GET /v1/states/resolve?q=kl to resolve any alias to the canonical code.
Project Structure
malaysia-calendar-api/
โโโ data/ # JSON data files (source of truth / ๆฐๆฎๆบ)
โ โโโ holidays/
โ โ โโโ 2024.json # Holiday data per year
โ โ โโโ 2025.json
โ โ โโโ 2026.json
โ โโโ school/
โ โ โโโ terms-2026.json # School terms (Kumpulan A + B)
โ โ โโโ holidays-2026.json # School holidays + KPM cuti perayaan
โ โ โโโ exams-2026.json # SPM, STPM, MUET, PT3 schedules
โ โโโ states.json # 16 states + 3 FT, aliases, weekend history
โ โโโ known-fixed-holidays.json
โโโ packages/
โ โโโ core/ # Shared business logic (types, schemas, utils)
โ โ โโโ src/
โ โ โโโ types.ts # Holiday, State, SchoolTerm, Exam interfaces
โ โ โโโ schemas.ts # Zod validation schemas
โ โ โโโ filter.ts # Query filtering logic
โ โ โโโ replacement.ts # Cuti ganti calculation
โ โ โโโ state-resolver.ts
โ โ โโโ business-days.ts
โ โ โโโ school.ts # School term/holiday/exam logic
โ โโโ api/ # Hono API on Cloudflare Workers
โ โโโ mcp-server/ # MCP Server (13 tools)
โ โโโ sdk/ # TypeScript client SDK (@catlabtech/mycal-sdk)
โ โโโ web/ # Astro + Starlight โ marketing site, demos, docs
โโโ scripts/
โ โโโ validate-data.ts # 5-layer data validation pipeline
โ โโโ sync-to-kv.ts # JSON -> Cloudflare KV denormalization
โโโ openapi.yaml # OpenAPI 3.1 spec (spec-first)
โโโ pnpm-workspace.yaml
โโโ turbo.json
Data Sources
All data is sourced from official Malaysian government publications:
| Source | Data | URL |
|---|
| JPM BKPP | Federal Gazette / Warta Kerajaan (public holidays) | kabinet.gov.my |
| JAKIM | Takwim Hijri-Miladi (Islamic calendar) | e-solat.gov.my |
| KPM | Kalendar Akademik / school calendar (Lampiran A/B/C) | moe.gov.my |
| MPM | STPM & MUET exam schedules | mpm.edu.my |
| State Portals | State-specific holidays (16 states) | *.gov.my |
Holiday data includes gazette references (e.g., P.U.(B) 305/2025) for traceability.
Deploy
Two parts deploy independently:
API โ Cloudflare Workers
pnpm --filter @catlabtech/mycal-core build
cd packages/api && npx wrangler deploy
Website โ Cloudflare Pages
pnpm --filter @catlabtech/mycal-core build
pnpm --filter @catlabtech/mycal-sdk build
pnpm --filter @mycal/web build
cd packages/web
npx wrangler pages deploy dist --project-name=mycal-web
First-time Pages setup:
- Create a Pages project named
mycal-web in the Cloudflare dashboard.
- Either connect the GitHub repo for automatic builds, or rely on the GitHub Actions workflow (
.github/workflows/deploy.yml) to push via wrangler pages deploy.
- Required GitHub secrets:
CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID.
CI/CD
GitHub Actions handles:
- PR gate โ Zod schema validation + cross-source checks on every PR
- Deploy โ On merge to
main: build and deploy API to Workers + Web to Pages in parallel
- Daily scrape โ Government portal monitoring for updates
- Rukyah monitor โ Islamic date confirmation tracking
Contributing
See CONTRIBUTING.md for how to:
- Report a missing holiday or cuti peristiwa
- Fix data errors
- Add new features
License
MIT