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
- Website + demo: https://mycal-web.pages.dev
- Developer docs: https://mycal-web.pages.dev/docs
- API base URL: https://mycal-api.huijun00100101.workers.dev/v1
- GitHub: https://github.com/Junhui20/malaysia-calendar-api
Packages
Published to npm under the @catlabtech scope:
| Package | Purpose | Install |
|---|---|---|
@catlabtech/mycal-core | Shared types, Zod schemas, and calendar/business-day logic | npm install @catlabtech/mycal-core |
@catlabtech/mycal-sdk | TypeScript client SDK for the REST API | npm install @catlabtech/mycal-sdk |
@catlabtech/mycal-mcp-server | MCP server exposing 13 calendar tools for AI agents | npm install @catlabtech/mycal-mcp-server |
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
.icsfeeds - 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
# Clone and install
git clone https://github.com/Junhui20/malaysia-calendar-api.git
cd malaysia-calendar-api
pnpm install
# Build shared packages
pnpm --filter @catlabtech/mycal-core build
pnpm --filter @catlabtech/mycal-sdk build
# Run API locally (http://localhost:8787)
cd packages/api && npx wrangler dev
# Run web site locally (http://localhost:4321)
pnpm --filter @mycal/web dev
# Validate data
pnpm validate
# Run tests
pnpm test
API Examples
Base URL: https://mycal-api.huijun00100101.workers.dev/v1
List holidays
# All holidays for Selangor in 2026
curl "https://mycal-api.huijun00100101.workers.dev/v1/holidays?year=2026&state=selangor"
# Islamic holidays only
curl "https://mycal-api.huijun00100101.workers.dev/v1/holidays?year=2026&type=islamic"
# March holidays for KL
curl "https://mycal-api.huijun00100101.workers.dev/v1/holidays?year=2026&state=KL&month=3"
Check a date
# Is March 21 a holiday/weekend/working day?
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
# Count working days in March for Selangor
curl "https://mycal-api.huijun00100101.workers.dev/v1/business-days?start=2026-03-01&end=2026-03-31&state=selangor"
# Add 10 business days to a date
curl "https://mycal-api.huijun00100101.workers.dev/v1/business-days/add?date=2026-03-01&days=10&state=selangor"
School calendar
# Is this a school day?
curl "https://mycal-api.huijun00100101.workers.dev/v1/school/is-school-day?date=2026-03-21&state=selangor"
# School holidays for Kumpulan B
curl "https://mycal-api.huijun00100101.workers.dev/v1/school/holidays?year=2026&group=B"
# Exam schedule
curl "https://mycal-api.huijun00100101.workers.dev/v1/school/exams?year=2026&type=spm"
Next holiday
# Next holiday for Penang
curl "https://mycal-api.huijun00100101.workers.dev/v1/holidays/next?state=penang"
State resolution
# Resolve alias
curl "https://mycal-api.huijun00100101.workers.dev/v1/states/resolve?q=kl"
# -> { "data": { "canonical": "kuala-lumpur", "group": "B" } }
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/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 |
See the full OpenAPI 3.1 spec for request/response schemas.
SDK Usage
import { MyCalClient } from "@catlabtech/mycal-sdk";
const cal = new MyCalClient();
// Check if a date is a working day
const result = await cal.check("2026-03-21", "selangor");
console.log(result.isWorkingDay); // false
// List holidays
const holidays = await cal.holidays({ year: 2026, state: "KL" });
// Business days
const workDays = await cal.businessDays("2026-03-01", "2026-03-31", "selangor");
console.log(workDays.businessDays); // 22
// School calendar
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"]
}
}
}
Available Tools (12)
| 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) |
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
# Direct upload via wrangler (requires CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID)
cd packages/web
npx wrangler pages deploy dist --project-name=mycal-web
First-time Pages setup:
- Create a Pages project named
mycal-webin the Cloudflare dashboard. - Either connect the GitHub repo for automatic builds, or rely on the GitHub Actions workflow (
.github/workflows/deploy.yml) to push viawrangler 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