swiss-school-calendar-mcp
> π¨π **Part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide)**
>
> This is a **private project**. It is independent of any employer or institutional affiliation and represents no official position of any authority.
# π
swiss-holidays-mcp
[](https://opensource.org/licenses/MIT)
[](https://www.python.org/downloads/)
[](https://modelcontextprotocol.io/)
[](https://github.com/malkreide/swiss-holidays-mcp/actions)
[](https://github.com/malkreide/swiss-holidays-mcp)
[](https://www.openholidaysapi.org/)
> A **Swiss holiday calendar** for AI agents β **public holidays, school holidays and long weekends** for all 26 cantons, with cross-cantonal comparison. School holidays are differentiated by *Schulart* (school type), which matters more than it first appears. No API key required.
[π©πͺ Deutsche Version](README.de.md)
---
## Overview
**swiss-holidays-mcp** is a Swiss holiday calendar for AI assistants like Claude β **public holidays, school holidays and long weekends** for all 26 cantons, no API keys required. Public holidays are cantonal (Berchtoldstag, Fronleichnam & co. differ by canton, not just the federal minimum). School holidays are set cantonally, sometimes at district level, and β in six cantons β **separately per school type**. A single federal calendar does not exist; anyone planning across cantonal borders is otherwise reduced to opening 26 PDF pages.
The server covers two thematic clusters: **public holidays / long weekends** and **school holidays** (with *Schulart* differentiation). Each cluster maps to a group of purpose-built tools that translate raw agency data into clean, provenance-tagged JSON responses. All data comes from the [OpenHolidays API](https://www.openholidaysapi.org/) (CC BY 4.0) and [Nager.Date](https://date.nager.at/) (MIT).
> **Mnemonic:** *A duplicate in Swiss school data is usually a school type in disguise.* The underlying API publishes the *same* holiday period several times when a canton differentiates by school type. That looks like duplicated data and invites naive de-duplication β which would destroy exactly the distinction a school authority needs.
**Anchor demo query:** *"In which weeks of 2026 are the compulsory schools of Zurich, Zug and Aargau simultaneously on holiday β and how many overlapping days does each pair share?"*
β This exercises `find_common_free_window`, `compare_school_holidays` and `list_school_types` in a single conversation, and answers a question that recurs every planning cycle in inter-cantonal coordination.
β [More use cases by audience](EXAMPLES.md) β
### Demo

---
## Features
- π« **School holidays** β periods per canton and date range, differentiated by *Schulart* (`VS` / `MS` / `BS` / `EO`)
- π **Public holidays** β cantonal holiday sets, not just the federal minimum (Berchtoldstag & friends)
- π **Date check** β is a given date a school or public holiday in a canton?
- π **Cross-cantonal comparison** β pairwise overlap matrix of holiday days between cantons
- πͺ **Common free windows** β date ranges where all listed cantons are simultaneously on holiday
- π **Long weekends & bridge days** β computed from federal public holidays (Nager.Date)
- ποΈ **Local & municipal holidays** β district- and municipality-level specifics such as Zurich's SechselΓ€uten and Knabenschiessen, with a `scope` marker so they are never mistaken for canton-wide
- π **iCal / ICS export** β a canton's holidays for a year as a ready-to-import `.ics` calendar
- π **Holiday feed resource** β `holidays://<canton>/<year>` MCP resource with a Markdown summary
- π **"Is today a holiday?"** β one-call convenience for the everyday question
- π©Ί **Source health** β reachability and latency of both upstreams, always evaluable
- π **No authentication required** β both data sources are publicly accessible
- βοΈ **Dual transport** β stdio for Claude Desktop, Streamable HTTP/SSE for cloud deployment
- π§Ύ **Provenance on every response** β `live_api` | `cached` | `degraded`, never a silent empty list
---
## Data Sources
| Source | Data | Licence |
|---|---|---|
| [OpenHolidays API](https://www.openholidaysapi.org/) | Cantons, *Schularten*, school holidays, public holidays | CC BY 4.0 |
| [Nager.Date](https://date.nager.at/) | Long weekends and required bridge days | MIT |
Both sources are publicly accessible, no authentication required.
**Attribution required:** OpenHolidays (CC BY 4.0) and Nager.Date must be cited as the source when using their data.
---
## Tools
| Tool | Purpose | Data Source |
|---|---|---|
| `list_cantons` | The 26 cantons with ISO codes and official languages | OpenHolidays |
| `list_school_types` | *Schulart* groups per canton (`CH-ZH-VS` etc.) | OpenHolidays |
| `get_school_holidays` | School holidays for one canton and date range | OpenHolidays |
| `get_public_holidays` | Public holidays for one canton and year | OpenHolidays |
| `get_local_holidays` | Public holidays for one municipality or district, incl. local specifics | OpenHolidays |
| `check_date` | Is a given date a school or public holiday? | OpenHolidays |
| `compare_school_holidays` | Pairwise overlap matrix across cantons | OpenHolidays |
| `find_common_free_window` | Windows where all listed cantons are on holiday | OpenHolidays |
| `next_school_holidays` | The next upcoming holiday periods | OpenHolidays |
| `get_long_weekends` | Long weekends and required bridge days | Nager.Date |
| `export_holidays_ics` | A canton's holidays for a year as an iCalendar (`.ics`) document | OpenHolidays |
| `is_holiday_today` | Is today a school or public holiday in a canton? | OpenHolidays |
| `source_status` | Reachability and latency of both upstreams | Built-in |
### Resources
| Resource URI | Content |
|---|---|
| `holidays://{canton}/{year}` | Markdown summary of all public + school holidays, e.g. `holidays://CH-ZH/2026` |
All tools carry the full annotation set β `readOnlyHint: true`, `destructiveHint: false`, `idempotentHint: true`, `openWorldHint: true` (they reach an external API). No tool writes anywhere. Inputs are schema-validated (canton codes against the 26 known cantons, dates as `YYYY-MM-DD`, `year` bounded, `language`/`school_type` whitelisted).
### Example Use Cases
| Query | Tool |
|---|---|
| *"Which cantons are there, and what are their codes?"* | `list_cantons` |
| *"Show Zurich's compulsory-school holidays for spring 2026"* | `get_school_holidays` |
| *"Is 3 April 2026 a public holiday in Ticino?"* | `check_date` |
| *"Do Zurich and Zug school holidays overlap this year?"* | `compare_school_holidays` |
| *"When can all of ZH, ZG, AG plan a joint week off school?"* | `find_common_free_window` |
| *"What are the next holidays for Basel-Stadt schools?"* | `next_school_holidays` |
| *"Which long weekends does 2026 have, and which bridge days do they need?"* | `get_long_weekends` |
| *"Which local holidays does the city of Zurich keep that the rest of the canton doesn't?"* | `get_local_holidays` |
| *"Export Zurich's 2026 holidays as an .ics calendar I can import"* | `export_holidays_ics` |
| *"Is today a holiday in Aargau?"* | `is_holiday_today` |
---
## π‘οΈ Safety & Limits
| Aspect | Details |
|--------|---------|
| **Access** | Read-only (`readOnlyHint: true`) β the server cannot modify or delete any data |
| **Personal data** | No personal data β all sources are aggregated, public holiday calendars |
| **Caching** | 12-hour in-memory TTL (holiday tables change a handful of times per year) |
| **Retry** | Exponential backoff 2s / 4s / 8s; 4xx except 429 are not retried |
| **Timeout** | 20 seconds per API call (8 seconds for health probes) |
| **Authentication** | No API keys required β both upstreams are publicly accessible |
| **Degradation** | Upstream failure yields a `degraded` envelope with an explanatory `note`, never a silent empty list |
| **Terms of Service** | Subject to the ToS of the respective data sources: [OpenHolidays](https://www.openholidaysapi.org/), [Nager.Date](https://date.nager.at/) |
---
## Architecture
This server uses **Architecture A (live API only, with in-memory cache)**.
```
ββββββββββββββββββββββββββββ
Claude / any ββββΆβ swiss-holidays-mcp β
MCP host β (MCPServer Β· 13 tools) β
ββββββββββ¬ββββββββββββββββββ
β retry 2s/4s/8s Β· 12h cache
ββββββββββ΄ββββββββββ
βΌ βΌ
OpenHolidays API Nager.Date
(CC BY 4.0) (MIT)
cantons Β· Schularten long weekends
school + public bridge days
```
**Rationale (verified live on 2026-07-19):**
- All ten documented OpenHolidays endpoints answered HTTP 200 with plausible payloads; `/Subdivisions?countryIsoCode=CH` returns exactly 26 cantons, matching the official count.
- No public bulk dump could be verified at build time (`openpotato/openholidays.data` raw access returned 404), so Architecture B was not available.
- Holiday tables change a handful of times per year, so a 12-hour in-memory TTL removes almost all upstream load without risking staleness.
**Consequences:**
- Every response carries `provenance` (`live_api` | `cached` | `degraded`).
- Upstream failure yields a `degraded` envelope with an explanatory `note`, never a silent empty list.
- `source_status` always returns an evaluable health report.
---
## Live-probe findings (2026-07-19)
| Endpoint | HTTP | Status | Records | Note |
|---|---|---|---|---|
| `/Countries` | 200 | β
works | 36 | |
| `/Subdivisions?countryIsoCode=CH` | 200 | β
works | 26 | matches official canton count |
| `/Groups?countryIsoCode=CH` | 200 | β
works | 11 | *Schulart* groups, only 6 cantons |
| `/PublicHolidays` (CH, 2026) | 200 | β
works | 39 | cantonal scope included |
| `/SchoolHolidays` (CH, 2026) | 200 | β
works | 193 | 183 distinct after school-type split |
| `/SchoolHolidaysByDate` | 200 | β
works | β | |
| `/SchoolHolidays?countryIsoCode=XX` | 200 | β οΈ silently empty | 0 | invalid country β error |
| `/Subdivisions?languageIsoCode=ZZ` | 200 | β οΈ silent EN fallback | 26 | invalid language β error |
| `/SchoolHolidays` without date range | 400 | β
correct error | β | RFC 9110 problem+json |
| Nager `/PublicHolidays/2026/CH` | 200 | β
works | 33 | 29 rows carry `counties` |
| Nager `/LongWeekend/2026/CH` | 200 | β
works | 3 | |
| Nager `/PublicHolidays/2026/XX` | 404 | β
correct error | β | stricter than OpenHolidays |
### Known findings
1. **Apparent duplicates are school types.** Zurich returns *FrΓΌhlingsferien 2026* twice: once for `CH-ZH-VS` (Volksschulen, tagged `Recommended`) and once for `CH-ZH-BS` + `CH-ZH-MS` (Berufsfach- and Mittelschulen). Use the `school_type` parameter (`VS` / `MS` / `BS` / `EO`) rather than de-duplicating.
2. **Only six cantons differentiate** by school type (AI, AR, BE, GR, SO, ZH). Elsewhere `groups` is absent and one table covers everything. The filter therefore treats an absent `groups` field as "applies to all".
3. **Subdivision codes mix levels.** Records may carry `CH-AI-AP` or `CH-BE-TH-BL`. Always match on the `CH-XX` prefix, never on string equality.
4. **An empty list is not an answer.** An unknown country or canton code yields HTTP 200 with `[]`. This server sets an explanatory `note` so that "no holidays" and "bad filter" stay distinguishable.
---
## Prerequisites
- Python 3.10 or higher
- [uv](https://docs.astral.sh/uv/) / uvx (recommended) or pip
- Internet access (both APIs are publicly available)
---
## Installation
Run via [`uv`](https://docs.astral.sh/uv/)'s `uvx` β no clone or manual install needed:
```bash
uvx swiss-holidays-mcp
```
### Development
```bash
git clone https://github.com/malkreide/swiss-holidays-mcp
cd swiss-holidays-mcp
pip install -e ".[dev]"
```
---
## Configuration
### Claude Desktop
Add to `claude_desktop_config.json`:
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"swiss-holidays": {
"command": "uvx",
"args": ["swiss-holidays-mcp"]
}
}
}
```
Restart Claude Desktop β the server starts automatically on first use.
### Cloud Deployment (SSE / Streamable HTTP for browser access)
For use via **claude.ai in the browser** (e.g. on managed workstations without local software):
```bash
MCP_TRANSPORT=sse PORT=8000 python -m swiss_holidays_mcp
```
The SDK exposes SSE at `/sse`, not `/mcp`.
| Variable | Default | Description |
|---|---|---|
| `MCP_TRANSPORT` | `stdio` | Transport: `stdio`, `sse`, `streamable-http` (aka `http`) |
| `PORT` / `MCP_PORT` | `8000` | Port for HTTP transports |
| `MCP_HOST` | `127.0.0.1` | Bind address for HTTP transports. Loopback by default; `0.0.0.0` is opt-in and logs a warning β run behind an authenticating reverse proxy. |
| `MCP_CORS_ORIGINS` | *(empty)* | Comma-separated extra CORS origins for browser clients (audit SDK-004). Loopback origins are always allowed; add the public origin your UI is served from, e.g. `https://ui.example.ch`. Never `*`. |
The HTTP transports attach an explicit CORS layer that exposes the
`Mcp-Session-Id` header, so a browser MCP client can read the session id and
make follow-up requests. The allow-list is never a wildcard.
Running **more than one HTTP instance** behind a load balancer requires sticky
sessions keyed on `Mcp-Session-Id` β see [`docs/scaling.md`](docs/scaling.md)
for nginx/Traefik/Kubernetes examples. A single instance (the common case) needs
no affinity configuration.
> π‘ *"stdio for the developer laptop, SSE for the browser."*
---
## Project Structure
```
swiss-holidays-mcp/
βββ src/
β βββ swiss_holidays_mcp/
β βββ __init__.py # Package init
β βββ __main__.py # Entry point: stdio / SSE / Streamable HTTP
β βββ server.py # MCPServer: lifespan, 13 tools, 1 resource, op_* logic
β βββ client.py # Shared HTTP client: retry, 12h cache, egress guard
β βββ guard.py # Egress / SSRF guard (HTTPS + allow-list + IP blocklist)
β βββ pinning.py # DNS-pinning transport (TOCTOU-free connect, SEC-005)
β βββ ical.py # RFC 5545 iCalendar (.ics) writer
β βββ settings.py # Pydantic-Settings config (loopback default)
β βββ logging_setup.py # Structured logging to stderr
β βββ constants.py # Canton codes, Schulart suffixes, API bases, allow-list
β βββ models.py # Pydantic v2 response envelopes
βββ tests/
β βββ conftest.py # respx fixtures
β βββ test_tools.py # Tool unit tests (mocked, no network)
β βββ test_resilience.py # Degradation / retry / cache behaviour
β βββ test_live.py # Live smoke tests (marker: live)
βββ docs/ # roadmap.md, security.md, network-egress.md
βββ deploy/ # Network-layer egress manifests (Cilium / NetworkPolicy)
βββ audits/ # mcp-audit run artifacts
βββ Dockerfile # Non-root multi-stage container
βββ .github/
β βββ dependabot.yml # Monthly dependency / action update PRs
β βββ workflows/ # ci.yml, live-tests.yml, publish.yml
βββ pyproject.toml
βββ CHANGELOG.md
βββ CONTRIBUTING.md # Contributing guide (English)
βββ CONTRIBUTING.de.md # Contributing guide (German)
βββ SECURITY.md # Security policy (English)
βββ SECURITY.de.md # Security policy (German)
βββ EXAMPLES.md # Use cases by audience
βββ server.json # MCP registry manifest
βββ LICENSE
βββ README.md # This file (English)
βββ README.de.md # German version
```
**On the single-file `server.py` (audit ARCH-011).** The 13 tools deliberately
live in one module rather than a `tools/` package. Each tool is a thin, uniform
wrapper (`@mcp.tool` β `@_safe_tool` β `op_*`) over a transport-agnostic `op_*`
operation, and every operation shares the same small set of helpers
(`_to_period`, `_matches_school_type`, `_require_known_canton`, β¦) and the one
`HolidayClient`. Splitting these across files would scatter that shared core and
duplicate imports for no isolation benefit β the file is uniformly sectioned
(aliases β helpers β `op_*` logic β tool wrappers β resource) and every `op_*`
is unit-tested directly without a transport. A `tools/` split is the planned
step **only** if Phase 2 pushes the tool count materially higher.
---
## Lifecycle Phase
This server is in **Phase 1 (read-only)** β all tools read-only, no auth, no side
effects. The 13-tool budget (of the 15β20 recommended maximum) still leaves
headroom. Local and municipal specifics β including Zurich's SechselΓ€uten and
Knabenschiessen β are covered directly from OpenHolidays via `get_local_holidays`
(a live probe showed they are published upstream at Gemeinde level), so no
separate city data source is required for them.
---
## MCP Primitives & Protocol Version
- **Primitives β Tools + Resources.** The 13 tools are idempotent,
side-effect-free `GET`s. A **Resource** exposes a stable URI feed
(`holidays://<canton>/<year>`) so clients can read a canton's calendar as
cacheable context without a tool call. There are no recurring templated
workflows, so **Prompts** are not used (revisited if that changes).
- **MCP protocol version β two eras, both served natively.** `mcp` 2.x serves
both over the same server, and the client's first request on a connection
decides which applies: the `initialize` handshake caps at **`2025-11-25`**;
the per-request envelope era (spec **`2026-07-28`**) has no handshake at all.
There every request carries its protocol version, client info and client
capabilities in `params._meta`, mirrored by the `Mcp-Protocol-Version` /
`Mcp-Method` / `Mcp-Name` routing headers, and `server/discover` takes the
place of `initialize`.
`source_status` reports in `mcp_protocol_version` the revision **this request
came in on**, read per request from `Context.protocol_version`. It used to
report one constant β the handshake ceiling β to every caller, so a client
that had negotiated `2026-07-28` was told `2025-11-25` about its own
connection. The constant survives only as the fallback for the `op_*` layer,
where nothing was negotiated; it is derived from the SDK's
`LATEST_HANDSHAKE_VERSION` rather than written down, so it cannot drift the
way it once did β it stood at `2025-06-18` for two revisions while every call
reported it as fact.
Measured, not inferred:
[`tests/test_protocol_version.py`](tests/test_protocol_version.py) drives both
eras through the same ASGI app this repo serves in production and compares the
delivered field per era. The previous version of that file stated the repo
could not build such an app β it can, and the claim outlived the measurement
by two revisions. A client asking the handshake for `2026-07-28` still gets
`2025-11-25` back, now measured here rather than at a sister server. The wire
version is negotiated by the pinned `mcp` SDK (`mcp>=2.0.0,<3`).
- **Server identity travels with every result.** `2026-07-28` has no
`initialize` result, so `serverInfo` rides along as a `_meta` stamp on each
one β the only place a caller learns which server and which build answered.
The server hands `MCPServer` its package version and homepage; without them
the SDK stamps `version: ""`, and it never substitutes one of its own. An
empty version is worse than an absent one: it looks like an answer.
- **Freshness hints on the listing methods** (SEP-2549): `tools/list`,
`resources/list`, `resources/templates/list` and `server/discover` answer with
`ttlMs` 300000 and `cacheScope: public`. Without them the SDK's default is
"stale immediately, never shared", which makes every client re-list on every
connection β for lists that are fixed at import. `resources/read` carries no
hint: `holidays://{canton}/{year}` is a live query, not a directory.
- **Update policy.** SDK and dependency bumps land via Dependabot (monthly, grouped);
protocol-version or tool-definition changes are recorded in
[`CHANGELOG.md`](CHANGELOG.md) with a version bump.
## Data classification
All data is **Γffentlich / Public Open Data** β aggregated holiday calendars,
no personal data (DSG/DSGVO). This is the highest classification the server
handles; the full model is in [`docs/security.md`](docs/security.md).
## Known Limitations
- **Unofficial source.** OpenHolidays aggregates cantonal publications. For legally binding dates, the cantonal authority remains authoritative. Every response says so.
- **Municipal coverage depends on the upstream.** OpenHolidays does carry district- and municipality-level public holidays (e.g. SechselΓ€uten, Knabenschiessen at `CH-ZH-ZH-ZH`), exposed through `get_local_holidays`. Completeness at Gemeinde level is only as good as the upstream data, which varies by canton. Municipal *school* holidays are not separately modelled.
- **Nager long weekends ignore cantonal holidays.** They are computed from nationwide holidays only.
- **No historical depth guarantee.** Coverage of years before roughly 2020 is uneven.
---
## Testing
```bash
# Unit tests (no network required β respx-mocked)
PYTHONPATH=src pytest tests/ -m "not live"
# Live smoke tests (hits the real upstream APIs)
PYTHONPATH=src pytest tests/ -m "live"
# Linting
ruff check src/ tests/ scripts/
ruff format --check src/ tests/ scripts/
```
---
## Contributing
Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) (English) Β· [CONTRIBUTING.de.md](CONTRIBUTING.de.md) (German) for guidelines on reporting bugs, setting up the development environment, code style and test requirements.
This project follows the conventions of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide).
---
## Security
To report a vulnerability, please follow the responsible disclosure process in [SECURITY.md](SECURITY.md) (English) Β· [SECURITY.de.md](SECURITY.de.md) (German). The server is read-only and requires no API key; see the *Safety & Limits* section above for the security model.
---
## Changelog
See [CHANGELOG.md](CHANGELOG.md)
---
## Deployment for Swiss Public Administration
If you self-host this server for a Swiss school authority or municipal use case:
- **Data residency:** the query patterns themselves (which cantons a civil servant compares) may reveal ongoing planning and are best kept on Swiss or trusted infrastructure.
- **Upstream calls** go to OpenHolidays (EU-hosted OGD project) and Nager.Date. No personal data leaves your environment; only holiday calendars are requested.
- **Logging:** logs are written to stderr; configure your IT retention policy accordingly.
- **HTTP transport** should run behind a reverse proxy with authentication and per-IP rate limits β the server has no built-in authentication.
---
## License
MIT License β see [LICENSE](LICENSE)
Source data is subject to the terms of OpenHolidays (CC BY 4.0) and Nager.Date (MIT); attribution to these sources is required when using their data.
---
## Author
Hayal Oezkan Β· [github.com/malkreide](https://github.com/malkreide)
---
## Credits & Related Projects
- **Data:** [OpenHolidays API](https://www.openholidaysapi.org/) (CC BY 4.0) Β· [Nager.Date](https://date.nager.at/) (MIT)
- **Protocol:** [Model Context Protocol](https://modelcontextprotocol.io/) β Anthropic / Linux Foundation
- **Built following** the `mcp-data-source-probe` methodology: *live probe before design, dump fallback before API dependency, retry before defeatism.*
- **Portfolio:** [Swiss Public Data MCP Portfolio](https://github.com/malkreide)
| Server | Description |
|--------|-------------|
| [`zh-education-mcp`](https://github.com/malkreide/zh-education-mcp) | Canton of Zurich education data |
| [`zurich-opendata-mcp`](https://github.com/malkreide/zurich-opendata-mcp) | City of Zurich Open Data |
| [`swiss-statistics-mcp`](https://github.com/malkreide/swiss-statistics-mcp) | BFS STAT-TAB β Swiss federal statistics |
| [`swisstopo-mcp`](https://github.com/malkreide/swisstopo-mcp) | Swiss federal geodata (swisstopo) |
MIT licensed. Public money, public code.
<!-- mcp-name: io.github.malkreide/swiss-holidays-mcp -->
TDQS
Scored across 13 tools
The tools are largely distinct: each targets a specific resource or query type (e.g., list_cantons for codes, get_public_holidays for yearly public holidays, get_local_holidays for municipality specifics). Slight overlap exists between check_date and is_holiday_today (both check a date), and between get_school_holidays and next_school_holidays (both return school holiday periods), but use cases and parameters clearly separate them.
The majority follow a clear verb_noun snake_case pattern (get_school_holidays, check_date, compare_school_holidays). A few deviate: source_status (noun_noun), next_school_holidays (adjective_noun), and is_holiday_today (verb but with an adjective). These are minor exceptions and still readable, but the pattern is not perfectly uniform.
13 tools is well within the ideal 3-15 range for a focused domain. Each tool addresses a distinct need: canton resolution, holiday lookups, comparisons, convenience functions, health checks, and export. None feel redundant or missing, and the count matches the complexity of Swiss cantonal holiday variations.
The tool surface covers the full lifecycle of holiday queries: listing cantons, resolving codes, checking single dates, retrieving periods by range or upcoming, comparing across cantons, finding common windows, and exporting to .ics. It also handles public, school, and local holidays with type differentiation (Schulart). No obvious gaps for the stated use case of Swiss holiday planning.