Open-source project
NateScarlet/holiday-cn avatar
NateScarlet/holiday-cn

holiday-cn: Chinese statutory holidays as committed JSON, crawled from State Council notices

Chinese public holiday data, automatically scraped daily from State Council announcements.

2,203 stars219 forksPythonMIT

At a glance

What is it?
A 2,180 star repository that publishes nothing but data files, one JSON and ICS pair per year, refreshed by a scheduled crawl of the Chinese government gazette.
Who is it for?
holiday-cn solves a narrow problem with no moving parts, and the interesting decisions are all in the data rather than the code. A consumer gets a JSON array per year with provenance URLs back to the source notices, an ICS file for calendar clients, and a stated rule explaining why some adjacent weekend days are missing on purpose.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 8, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One JSON and ICS pair per year, checked into the repository

There is no API here and no service to deploy. The repository tree is the product: `2007.json` through `2027.json`, each with a matching `.ics` twin, plus a rolling `holiday-cn.ics` at the root. Every year of data that exists is a committed file, which means consumption is a plain HTTP GET against a CDN rather than a client library or a query.

The README gives the address format for the raw GitHub path and two jsDelivr hostnames, and it offers a ghproxy prefix as a workaround for networks where GitHub is slow. It also documents a domestic mirror that was struck through and retired with a dated note explaining why: the host began requiring a login to download open source repository files on 2022-08-05. That crossed-out section is more informative than it looks, because it records the moment a free mirror turned into an authentication wall.

The scale is modest and the maintenance is steady. The project reports 2,180 stars, 217 forks, 4 open issues, and a last push on 2026-09-27.

The rolling calendar file the README still describes

The ICalendar section says `holiday-cn.ics` covers the holidays from three years ago through the following year, and that `{year}.ics` covers a single year. The release history complicates the first half of that sentence. The January 2026 release record is a diffstat: `2027.ics` gains 16 lines, `2027.json` gains 7, and `holiday-cn.ics` loses 98 of them, across three changed files.

Both statements sit in the same repository at the same time. The README still documents the rolling file, and the file name still appears in the tree listing, while the most recent tagged release emptied it. Rather than guess which is intended, the useful check is cheap: fetch `holiday-cn.ics` from `master` and count the events. If it returns a multi-year calendar, the README is accurate and the January diff was a one-off regeneration. If it returns a stub, then the per-year `.ics` files are the only reliable entry point and the README section is stale.

For calendar subscriptions specifically, that distinction is the whole integration. A subscription URL that silently resolves to nothing looks identical to a subscription URL that resolved and produced no events.

Year keys follow notice titles, not calendar dates

The most consequential sentence in the README is a caveat, not a feature. The year field follows the year in the title of the State Council document rather than the year of the dates themselves, so a December date can be governed by the following year's notice. Consumers are told to check two files rather than one.

A second caveat covers the gap most people notice first. Days that are given off because they were joined to a weekend are not statutory holidays and are not in the data at all. The README links the national rules on festival and commemorative day holidays and two issue threads where this distinction was argued out. For a developer building a payroll cut-off or a shipping scheduler, that means the inverse of `isOffDay` is not a working day either. Weekends joined to a holiday simply vanish from the array, and adjusted working weekends are not represented by any field, because the schema has only `name`, `date` and `isOffDay` per day.

A build file that only formats and tests

The repository is tagged Python, and the Makefile reflects that, but it does nothing except run Black and pytest. There is no make target that fetches anything, and no crawler module at the root. The fetching logic lives under `scripts/` and the scheduled workflow under `.github/`, which is consistent with a project whose output is committed data rather than a distributable library.

code
.PHONY: default lint format
default: format
ifeq ($(OS),Windows_NT)
PYTHON?=py -3.12
else
PYTHON?=python3
endif
.PHONY: test
test:

The lint and format targets pass `-t py312` to Black, and the Makefile branches on the operating system to pick `py -3.12` on Windows, so the toolchain assumption is Python 3.12 even though the runtime requirement for consumers is nothing at all. Someone reading the repository to reuse the scraper should note that the scraper itself is not what gets published: what ships is the accumulated result, twenty-one years of it.

CalVer tags that appear only when the data changes

The badge declares a CalVer scheme of `YYYY.0M.0D`, and the three most recent releases follow it. The May 2025 tag is three one-line corrections applied to three ICS files. The November 2025 tag is the large one, adding the whole 2026 JSON file alongside its ICS and the rolling calendar. The January 2026 tag then pushed the window forward to 2027.

The gaps between those tags matter as much as the tags. The repository was pushed on 2026-09-27, roughly nine months after the last release, and a project that crawls a gazette daily will produce many days with no change at all. The README states that a new version is published when the data changes, so an unchanged stretch produces no tag and no notification. Watch subscribers get an email, as the README points out, which is the intended mechanism for the rare case where a date is corrected after the fact.

The practical split is between pinning a tag for reproducible reads and following `master` for the freshest state. Both URLs are in the README, and they answer different questions.

What the schema settles and what it leaves open

The data contract is small enough to read in one screen. Each year file carries `year`, a `papers` array of the State Council document URLs that were used, and a `days` array where each entry has a `name`, an ISO 8601 `date`, and the `isOffDay` boolean. A JSON Schema file is linked as the normative definition, and a TypeScript interface is shown in the README to illustrate the shape.

The `papers` field is the part worth respecting. It records provenance per year, which means any consumer can open the underlying government notice and confirm a date rather than trusting the crawl. That is the difference between a holiday library you can audit and one you take on faith, and it is unusual for a data-as-a-repository project to carry it.

What the schema cannot express is the compensating working day. If a Saturday becomes a workday to create a longer break, nothing in this format records it, so a consumer who needs true working-day arithmetic has to bring its own rule set on top. The README is also Chinese only, with no `docs/` directory in the tree, so the international audience reads the notes through translation. With 4 open issues, the project is small enough that a correction is likely to arrive as a commit rather than a discussion.

Editorial conclusion

holiday-cn solves a narrow problem with no moving parts, and the interesting decisions are all in the data rather than the code. A consumer gets a JSON array per year with provenance URLs back to the source notices, an ICS file for calendar clients, and a stated rule explaining why some adjacent weekend days are missing on purpose. What it does not give you is any way to know a working Saturday was moved, which is why the isOffDay flag should not be read as a complete answer about who is working. Fetch the year you need, check the papers array, and treat the file as a snapshot of a crawl rather than a live feed.

Frequently asked questions

Does holiday-cn include weekend days joined to a holiday?

No. The project states that days given off because they were attached to a weekend are not statutory holidays and are excluded from the data, citing the national rules on festival holidays. A consumer computing working days has to add those joins back from its own rules, since the schema has no field for them.

How should I tell whether a Chinese date in the data is a working day?

You cannot from this dataset alone. The `isOffDay` flag covers statutory holidays only, weekend joins are absent, and adjusted working weekends have no representation. Treat a missing date as unknown rather than as a working day, and pair the data with your own rule set.

Can I verify a holiday date against a government source?

Yes. Every year file includes a `papers` array holding the State Council document URLs used to build it, so a questionable date can be checked against the original notice. Remember that the year key follows the document title year, which is why a December date may require looking at two files.

Is there a hosted API for this holiday data, or only files?

Only files. The project publishes per-year JSON and ICS documents plus a rolling calendar, served over raw GitHub, jsDelivr and similar CDNs. The README explicitly advises self-hosting a static file service if stability matters, because any third party in that chain can fail.

Official sources

  1. Issues
  2. License: MIT
  3. NateScarlet/holiday-cn on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/natescarlet-holiday-cn.svg)](https://hysenlabs.com/projects/natescarlet-holiday-cn)