A self-hosted global public holiday calendar
Clandar is a web app that shows public holidays for 20 countries across ASEAN, Asia-Pacific, Europe, and the Americas. It runs as a single self-contained binary backed by SQLite — no external database required.
Key features:
- Filterable by region, country, and holiday type (public, optional, observance)
- Year navigation (past and future years)
- Holiday detail popovers with name, date, and type
- Dark UI
📸 Screenshot coming soon
| Region | Countries |
|---|---|
| ASEAN | Brunei, Cambodia, Indonesia, Laos, Malaysia, Myanmar, Philippines, Singapore, Thailand, Vietnam |
| Asia-Pacific | Australia, China, Japan |
| Europe | France, Germany, Italy, Netherlands, Spain, United Kingdom |
| Americas | United States |
- Nager.Date — free, no API key needed. Covers public holidays for most supported countries.
- Calendarific — optional, free tier available. Provides broader coverage including religious and cultural holidays, and fills in countries that Nager.Date does not support.
git clone https://github.com/barayuda/clandar
cd clandar/Clandar
cp .env.example .env # edit CALENDARIFIC_API_KEY if you have one
go mod tidy
go run ./cmd/server
# Open http://localhost:8080git clone https://github.com/barayuda/clandar
cd clandar/Clandar
cp .env.example .env # edit CALENDARIFIC_API_KEY if you have one
docker compose up --build
# Open http://localhost:8080Note: On first boot the server automatically seeds all 20 countries and fetches holiday data. This takes approximately 10–15 seconds before holidays appear in the UI.
| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
HTTP server port |
LOG_LEVEL |
info |
Logging level (debug / info / warn / error) |
DB_PATH |
./data/clandar.db |
Path to the SQLite database file |
CALENDARIFIC_API_KEY |
(empty) | Optional — improves coverage for ASEAN countries |
Copy .env.example to .env and set values there. The .env file is never committed.
| Endpoint | Query params | Description |
|---|---|---|
GET /api/holidays |
country, region, year, type |
Returns holidays matching the given filters |
GET /api/countries |
region |
Returns all countries, optionally filtered by region |
GET /api/regions |
— | Returns the 4 regions with country counts |
GET /health |
— | Health check |
Example requests:
curl "http://localhost:8080/api/holidays?country=SG&year=2026"
curl "http://localhost:8080/api/holidays?region=ASEAN&year=2026&type=public"# Go unit and integration tests
go test ./internal/...
# Playwright E2E tests (requires the server to be running on :8080)
cd tests/e2e
npm install && npx playwright install chromium
npm testClandar/
├── cmd/server/ # main entry point
├── internal/
│ ├── api/ # HTTP handlers & router
│ ├── fetcher/ # Nager.Date & Calendarific clients
│ ├── scheduler/ # yearly auto-sync
│ ├── seeder/ # country seeding & holiday sync
│ └── store/ # SQLite queries & models
├── db/ # schema.sql & sqlc queries
├── web/ # frontend (single HTML file)
├── tests/e2e/ # Playwright specs
├── Dockerfile
└── docker-compose.yml
- Go 1.22, chi router, zerolog, modernc.org/sqlite (pure Go — no CGo required)
- Playwright for E2E testing
- Data: Nager.Date API, Calendarific API