From 05a1dca074b063f789929fd5d601d72383eb28dd Mon Sep 17 00:00:00 2001 From: "mingsheng.li" Date: Mon, 27 Apr 2026 12:09:34 +0800 Subject: [PATCH 1/3] feat: add Hong Kong public holiday support - Add scripts/fetch_hk.py to fetch HK holidays from 1823.gov.hk - Make generate_ics() cal_name/cal_desc configurable - Refactor update.py with REGIONS config; support --region cn|hk flag - Generate hk/{year}.json and hk/{year}.ics under hk/ subdirectory Co-Authored-By: Claude Sonnet 4.6 --- hk/2026.ics | 100 ++++++++++++++++++++++++++++++ hk/2026.json | 95 ++++++++++++++++++++++++++++ hk/2027.ics | 16 +++++ hk/2027.json | 9 +++ holiday-hk.ics | 100 ++++++++++++++++++++++++++++++ scripts/fetch_hk.py | 45 ++++++++++++++ scripts/generate_ics.py | 11 +++- scripts/update.py | 134 ++++++++++++++++++++++++++++++---------- 8 files changed, 473 insertions(+), 37 deletions(-) create mode 100644 hk/2026.ics create mode 100644 hk/2026.json create mode 100644 hk/2027.ics create mode 100644 hk/2027.json create mode 100644 holiday-hk.ics create mode 100644 scripts/fetch_hk.py diff --git a/hk/2026.ics b/hk/2026.ics new file mode 100644 index 0000000..06dd2a7 --- /dev/null +++ b/hk/2026.ics @@ -0,0 +1,100 @@ +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:PUBLISH +CLASS:PUBLIC +X-WR-CALDESC:香港公众假期数据,来源:香港特别行政区政 + 府 1823.gov.hk。 +X-WR-CALNAME:香港公众假期 +BEGIN:VTIMEZONE +TZID:Asia/Shanghai +BEGIN:STANDARD +DTSTART:19700101T000000 +TZOFFSETFROM:+0800 +TZOFFSETTO:+0800 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +SUMMARY:一月一日假期 +DTSTART;VALUE=DATE:20260101 +DTEND;VALUE=DATE:20260102 +DTSTAMP;VALUE=DATE:20260101 +UID:2026-01-01/2026-01-02/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:農曆年初一假期 +DTSTART;VALUE=DATE:20260217 +DTEND;VALUE=DATE:20260220 +DTSTAMP;VALUE=DATE:20260217 +UID:2026-02-17/2026-02-20/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:耶穌受難節假期 +DTSTART;VALUE=DATE:20260403 +DTEND;VALUE=DATE:20260405 +DTSTAMP;VALUE=DATE:20260403 +UID:2026-04-03/2026-04-05/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:清明節翌日假期 +DTSTART;VALUE=DATE:20260406 +DTEND;VALUE=DATE:20260408 +DTSTAMP;VALUE=DATE:20260406 +UID:2026-04-06/2026-04-08/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:勞動節假期 +DTSTART;VALUE=DATE:20260501 +DTEND;VALUE=DATE:20260502 +DTSTAMP;VALUE=DATE:20260501 +UID:2026-05-01/2026-05-02/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:佛誕翌日假期 +DTSTART;VALUE=DATE:20260525 +DTEND;VALUE=DATE:20260526 +DTSTAMP;VALUE=DATE:20260525 +UID:2026-05-25/2026-05-26/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:端午節假期 +DTSTART;VALUE=DATE:20260619 +DTEND;VALUE=DATE:20260620 +DTSTAMP;VALUE=DATE:20260619 +UID:2026-06-19/2026-06-20/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:香港特別行政區成立紀念日假期 +DTSTART;VALUE=DATE:20260701 +DTEND;VALUE=DATE:20260702 +DTSTAMP;VALUE=DATE:20260701 +UID:2026-07-01/2026-07-02/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:中秋節翌日假期 +DTSTART;VALUE=DATE:20260926 +DTEND;VALUE=DATE:20260927 +DTSTAMP;VALUE=DATE:20260926 +UID:2026-09-26/2026-09-27/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:國慶日假期 +DTSTART;VALUE=DATE:20261001 +DTEND;VALUE=DATE:20261002 +DTSTAMP;VALUE=DATE:20261001 +UID:2026-10-01/2026-10-02/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:重陽節翌日假期 +DTSTART;VALUE=DATE:20261019 +DTEND;VALUE=DATE:20261020 +DTSTAMP;VALUE=DATE:20261019 +UID:2026-10-19/2026-10-20/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:聖誕節假期 +DTSTART;VALUE=DATE:20261225 +DTEND;VALUE=DATE:20261227 +DTSTAMP;VALUE=DATE:20261225 +UID:2026-12-25/2026-12-27/NateScarlet/holiday-cn +END:VEVENT +END:VCALENDAR diff --git a/hk/2026.json b/hk/2026.json new file mode 100644 index 0000000..248d73c --- /dev/null +++ b/hk/2026.json @@ -0,0 +1,95 @@ +{ + "$schema": "https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json", + "$id": "https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/hk/2026.json", + "year": 2026, + "papers": [ + "https://www.1823.gov.hk/common/ical/tc.json" + ], + "days": [ + { + "name": "一月一日", + "date": "2026-01-01", + "isOffDay": true + }, + { + "name": "農曆年初一", + "date": "2026-02-17", + "isOffDay": true + }, + { + "name": "農曆年初二", + "date": "2026-02-18", + "isOffDay": true + }, + { + "name": "農曆年初三", + "date": "2026-02-19", + "isOffDay": true + }, + { + "name": "耶穌受難節", + "date": "2026-04-03", + "isOffDay": true + }, + { + "name": "耶穌受難節翌日", + "date": "2026-04-04", + "isOffDay": true + }, + { + "name": "清明節翌日", + "date": "2026-04-06", + "isOffDay": true + }, + { + "name": "復活節星期一翌日", + "date": "2026-04-07", + "isOffDay": true + }, + { + "name": "勞動節", + "date": "2026-05-01", + "isOffDay": true + }, + { + "name": "佛誕翌日", + "date": "2026-05-25", + "isOffDay": true + }, + { + "name": "端午節", + "date": "2026-06-19", + "isOffDay": true + }, + { + "name": "香港特別行政區成立紀念日", + "date": "2026-07-01", + "isOffDay": true + }, + { + "name": "中秋節翌日", + "date": "2026-09-26", + "isOffDay": true + }, + { + "name": "國慶日", + "date": "2026-10-01", + "isOffDay": true + }, + { + "name": "重陽節翌日", + "date": "2026-10-19", + "isOffDay": true + }, + { + "name": "聖誕節", + "date": "2026-12-25", + "isOffDay": true + }, + { + "name": "聖誕節後第一個周日", + "date": "2026-12-26", + "isOffDay": true + } + ] +} \ No newline at end of file diff --git a/hk/2027.ics b/hk/2027.ics new file mode 100644 index 0000000..b2170b8 --- /dev/null +++ b/hk/2027.ics @@ -0,0 +1,16 @@ +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:PUBLISH +CLASS:PUBLIC +X-WR-CALDESC:香港公众假期数据,来源:香港特别行政区政 + 府 1823.gov.hk。 +X-WR-CALNAME:香港公众假期 +BEGIN:VTIMEZONE +TZID:Asia/Shanghai +BEGIN:STANDARD +DTSTART:19700101T000000 +TZOFFSETFROM:+0800 +TZOFFSETTO:+0800 +END:STANDARD +END:VTIMEZONE +END:VCALENDAR diff --git a/hk/2027.json b/hk/2027.json new file mode 100644 index 0000000..4b1dd9d --- /dev/null +++ b/hk/2027.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json", + "$id": "https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/hk/2027.json", + "year": 2027, + "papers": [ + "https://www.1823.gov.hk/common/ical/tc.json" + ], + "days": [] +} \ No newline at end of file diff --git a/holiday-hk.ics b/holiday-hk.ics new file mode 100644 index 0000000..06dd2a7 --- /dev/null +++ b/holiday-hk.ics @@ -0,0 +1,100 @@ +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:PUBLISH +CLASS:PUBLIC +X-WR-CALDESC:香港公众假期数据,来源:香港特别行政区政 + 府 1823.gov.hk。 +X-WR-CALNAME:香港公众假期 +BEGIN:VTIMEZONE +TZID:Asia/Shanghai +BEGIN:STANDARD +DTSTART:19700101T000000 +TZOFFSETFROM:+0800 +TZOFFSETTO:+0800 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +SUMMARY:一月一日假期 +DTSTART;VALUE=DATE:20260101 +DTEND;VALUE=DATE:20260102 +DTSTAMP;VALUE=DATE:20260101 +UID:2026-01-01/2026-01-02/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:農曆年初一假期 +DTSTART;VALUE=DATE:20260217 +DTEND;VALUE=DATE:20260220 +DTSTAMP;VALUE=DATE:20260217 +UID:2026-02-17/2026-02-20/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:耶穌受難節假期 +DTSTART;VALUE=DATE:20260403 +DTEND;VALUE=DATE:20260405 +DTSTAMP;VALUE=DATE:20260403 +UID:2026-04-03/2026-04-05/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:清明節翌日假期 +DTSTART;VALUE=DATE:20260406 +DTEND;VALUE=DATE:20260408 +DTSTAMP;VALUE=DATE:20260406 +UID:2026-04-06/2026-04-08/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:勞動節假期 +DTSTART;VALUE=DATE:20260501 +DTEND;VALUE=DATE:20260502 +DTSTAMP;VALUE=DATE:20260501 +UID:2026-05-01/2026-05-02/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:佛誕翌日假期 +DTSTART;VALUE=DATE:20260525 +DTEND;VALUE=DATE:20260526 +DTSTAMP;VALUE=DATE:20260525 +UID:2026-05-25/2026-05-26/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:端午節假期 +DTSTART;VALUE=DATE:20260619 +DTEND;VALUE=DATE:20260620 +DTSTAMP;VALUE=DATE:20260619 +UID:2026-06-19/2026-06-20/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:香港特別行政區成立紀念日假期 +DTSTART;VALUE=DATE:20260701 +DTEND;VALUE=DATE:20260702 +DTSTAMP;VALUE=DATE:20260701 +UID:2026-07-01/2026-07-02/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:中秋節翌日假期 +DTSTART;VALUE=DATE:20260926 +DTEND;VALUE=DATE:20260927 +DTSTAMP;VALUE=DATE:20260926 +UID:2026-09-26/2026-09-27/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:國慶日假期 +DTSTART;VALUE=DATE:20261001 +DTEND;VALUE=DATE:20261002 +DTSTAMP;VALUE=DATE:20261001 +UID:2026-10-01/2026-10-02/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:重陽節翌日假期 +DTSTART;VALUE=DATE:20261019 +DTEND;VALUE=DATE:20261020 +DTSTAMP;VALUE=DATE:20261019 +UID:2026-10-19/2026-10-20/NateScarlet/holiday-cn +END:VEVENT +BEGIN:VEVENT +SUMMARY:聖誕節假期 +DTSTART;VALUE=DATE:20261225 +DTEND;VALUE=DATE:20261227 +DTSTAMP;VALUE=DATE:20261225 +UID:2026-12-25/2026-12-27/NateScarlet/holiday-cn +END:VEVENT +END:VCALENDAR diff --git a/scripts/fetch_hk.py b/scripts/fetch_hk.py new file mode 100644 index 0000000..527bee2 --- /dev/null +++ b/scripts/fetch_hk.py @@ -0,0 +1,45 @@ +"""Fetch Hong Kong public holiday data from 1823.gov.hk.""" + +import datetime +import requests + +# Traditional Chinese names, consistent with CN data format +HK_ICAL_URL = "https://www.1823.gov.hk/common/ical/tc.json" + +# Earliest year available from the 1823.gov.hk API +HK_START_YEAR = 2024 + + +def fetch_hk_holiday(year: int) -> dict: + """Fetch HK public holidays for a given year. + + HK has no makeup work day (调休) concept, so all entries are isOffDay=True. + Data coverage starts from HK_START_YEAR. + """ + response = requests.get(HK_ICAL_URL) + response.raise_for_status() + data = response.json() + + events = data["vcalendar"][0]["vevent"] + days = [] + + for event in events: + dtstart = event["dtstart"][0] # "YYYYMMDD" + date = datetime.date(int(dtstart[:4]), int(dtstart[4:6]), int(dtstart[6:8])) + if date.year != year: + continue + days.append( + { + "name": event["summary"], + "date": date.isoformat(), + "isOffDay": True, + } + ) + + days.sort(key=lambda d: d["date"]) + + return { + "year": year, + "papers": [HK_ICAL_URL], + "days": days, + } diff --git a/scripts/generate_ics.py b/scripts/generate_ics.py index 0d98db8..b2a33df 100644 --- a/scripts/generate_ics.py +++ b/scripts/generate_ics.py @@ -60,11 +60,16 @@ def _iter_date_ranges(days: Sequence[dict]) -> Iterator[Tuple[dict, dict]]: yield fr, to -def generate_ics(days: Sequence[dict], filename: Text) -> None: +def generate_ics( + days: Sequence[dict], + filename: Text, + cal_name: str = "中国法定节假日", + cal_desc: str = "中国法定节假日数据,自动每日抓取国务院公告。", +) -> None: """Generate ics from days.""" cal = Calendar() - cal.add("X-WR-CALNAME", "中国法定节假日") - cal.add("X-WR-CALDESC", "中国法定节假日数据,自动每日抓取国务院公告。") + cal.add("X-WR-CALNAME", cal_name) + cal.add("X-WR-CALDESC", cal_desc) cal.add("VERSION", "2.0") cal.add("METHOD", "PUBLISH") cal.add("CLASS", "PUBLIC") diff --git a/scripts/update.py b/scripts/update.py index 2e85e31..fbd6034 100644 --- a/scripts/update.py +++ b/scripts/update.py @@ -14,9 +14,32 @@ from zipfile import ZipFile from tqdm import tqdm from fetch import CustomJSONEncoder, fetch_holiday +from fetch_hk import HK_START_YEAR, fetch_hk_holiday from generate_ics import generate_ics from filetools import workspace_path +SCHEMA_URL = "https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json" +GITHUB_RAW_BASE = "https://raw.githubusercontent.com/NateScarlet/holiday-cn/master" + +REGIONS = { + "cn": { + "fetch": fetch_holiday, + "start_year": 2007, + "subdir": None, + "main_ics_name": "holiday-cn.ics", + "cal_name": "中国法定节假日", + "cal_desc": "中国法定节假日数据,自动每日抓取国务院公告。", + }, + "hk": { + "fetch": fetch_hk_holiday, + "start_year": HK_START_YEAR, + "subdir": "hk", + "main_ics_name": "holiday-hk.ics", + "cal_name": "香港公众假期", + "cal_desc": "香港公众假期数据,来源:香港特别行政区政府 1823.gov.hk。", + }, +} + class ChinaTimezone(tzinfo): """Timezone of china.""" @@ -31,25 +54,34 @@ class ChinaTimezone(tzinfo): return timedelta() -def update_data(year: int) -> Iterator[str]: - """Update and store data for a year.""" +def _region_paths(region: str, year: int): + """Return (json_path, ics_path, id_url) for a region and year.""" + subdir = REGIONS[region]["subdir"] + if subdir: + os.makedirs(workspace_path(subdir), exist_ok=True) + json_path = workspace_path(subdir, f"{year}.json") + ics_path = workspace_path(subdir, f"{year}.ics") + id_url = f"{GITHUB_RAW_BASE}/{subdir}/{year}.json" + else: + json_path = workspace_path(f"{year}.json") + ics_path = workspace_path(f"{year}.ics") + id_url = f"{GITHUB_RAW_BASE}/{year}.json" + return json_path, ics_path, id_url - json_filename = workspace_path(f"{year}.json") - ics_filename = workspace_path(f"{year}.ics") - with open(json_filename, "w", encoding="utf-8", newline="\n") as f: - data = fetch_holiday(year) +def update_data(year: int, region: str = "cn") -> Iterator[str]: + """Update and store data for a year and region.""" + cfg = REGIONS[region] + json_path, ics_path, id_url = _region_paths(region, year) + + data = cfg["fetch"](year) + + with open(json_path, "w", encoding="utf-8", newline="\n") as f: json.dump( dict( ( - ( - "$schema", - "https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json", - ), - ( - "$id", - f"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/{year}.json", - ), + ("$schema", SCHEMA_URL), + ("$id", id_url), *data.items(), ) ), @@ -59,25 +91,38 @@ def update_data(year: int) -> Iterator[str]: cls=CustomJSONEncoder, ) - yield json_filename - generate_ics(data["days"], ics_filename) - yield ics_filename + yield json_path + generate_ics( + data["days"], + ics_path, + cal_name=cfg["cal_name"], + cal_desc=cfg["cal_desc"], + ) + yield ics_path -def update_main_ics(fr_year, to_year): +def update_main_ics(fr_year: int, to_year: int, region: str = "cn"): + cfg = REGIONS[region] + subdir = cfg["subdir"] all_days = [] + for year in range(fr_year, to_year + 1): - filename = workspace_path(f"{year}.json") + if subdir: + filename = workspace_path(subdir, f"{year}.json") + else: + filename = workspace_path(f"{year}.json") if not os.path.isfile(filename): continue with open(filename, "r", encoding="utf8") as inf: data = json.loads(inf.read()) - all_days.extend(data.get("days")) + all_days.extend(data.get("days", [])) - filename = workspace_path("holiday-cn.ics") + filename = workspace_path(cfg["main_ics_name"]) generate_ics( all_days, filename, + cal_name=cfg["cal_name"], + cal_desc=cfg["cal_desc"], ) return filename @@ -87,30 +132,41 @@ def main(): parser.add_argument( "--all", action="store_true", - help="Update all years since 2007, default is this year and next year", + help="Update all years since each region's start year, default is this year and next year", ) parser.add_argument( "--release", action="store_true", help="create new release if repository data is not up to date", ) + parser.add_argument( + "--region", + choices=list(REGIONS.keys()), + default=None, + help="Region to update (default: all regions)", + ) args = parser.parse_args() now = datetime.now(ChinaTimezone()) is_release = args.release + regions_to_update = list(REGIONS.keys()) if args.region is None else [args.region] filenames = [] - progress = tqdm(range(2007 if args.all else now.year, now.year + 2)) - for i in progress: - progress.set_description(f"Updating {i} data") - filenames += list(update_data(i)) - progress.set_description("Updating holiday-cn.ics") - filenames.append(update_main_ics(now.year - 4, now.year + 1)) + for region in regions_to_update: + cfg = REGIONS[region] + year_start = cfg["start_year"] if args.all else max(cfg["start_year"], now.year) + progress = tqdm(range(year_start, now.year + 2)) + for year in progress: + progress.set_description(f"Updating {region} {year}") + filenames += list(update_data(year, region)) + progress.set_description(f"Updating {cfg['main_ics_name']}") + filenames.append(update_main_ics(now.year - 4, now.year + 1, region)) + print("") subprocess.run(["git", "add", *filenames], check=True) diff = subprocess.run( - ["git", "diff", "--stat", "--cached", "*.json", "*.ics"], + ["git", "diff", "--stat", "--cached"], check=True, stdout=subprocess.PIPE, encoding="utf-8", @@ -160,13 +216,23 @@ def main(): def pack_data(file): - """Pack data json in zip file.""" - + """Pack all region JSON data into a zip file.""" zip_file = ZipFile(file, "w") - for i in os.listdir(workspace_path()): - if not re.match(r"\d+\.json", i): + # Root-level {year}.json files (CN) + for name in os.listdir(workspace_path()): + if re.match(r"\d+\.json", name): + zip_file.write(workspace_path(name), name) + # Subdirectory region files (e.g. hk/{year}.json) + for region, cfg in REGIONS.items(): + subdir = cfg["subdir"] + if not subdir: continue - zip_file.write(workspace_path(i), i) + subdir_path = workspace_path(subdir) + if not os.path.isdir(subdir_path): + continue + for name in os.listdir(subdir_path): + if re.match(r"\d+\.json", name): + zip_file.write(workspace_path(subdir, name), f"{subdir}/{name}") if __name__ == "__main__": From 72ab05df3f03273d3366176f4cfb0c2429e5278d Mon Sep 17 00:00:00 2001 From: "mingsheng.li" Date: Mon, 27 Apr 2026 12:51:35 +0800 Subject: [PATCH 2/3] docs: update README to cover Hong Kong holiday data Co-Authored-By: Claude Sonnet 4.6 --- README.md | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2443fa2..c02d9d4 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,17 @@ [![JSDelivr](https://data.jsdelivr.com/v1/package/gh/NateScarlet/holiday-cn/badge?style=rounded)](https://www.jsdelivr.com/package/gh/NateScarlet/holiday-cn) ![Maintenance](https://img.shields.io/maintenance/yes/2024.svg) -中国法定节假日数据 自动每日抓取国务院公告 +中国大陆及香港节假日数据 + +- 中国大陆:自动每日抓取国务院公告 +- 香港:来源于香港特别行政区政府 [1823.gov.hk](https://www.1823.gov.hk/common/ical/tc.json),数据覆盖 2024 年起 - [x] 提供 JSON 格式节假日数据 - [x] CI 自动更新 - [x] 数据变化时自动发布新版本 ( `Watch` - `Release only` 以获取邮件提醒! ) - [x] [发布页面]提供 JSON 打包下载 -数据格式: +数据格式(中国大陆、香港通用): [JSON Schema](./schema.json) @@ -21,7 +24,7 @@ interface Holidays { /** 完整年份, 整数。*/ year: number; - /** 所用国务院文件网址列表 */ + /** 所用数据来源网址列表 */ papers: string[]; days: { /** 节日名称 */ @@ -36,15 +39,23 @@ interface Holidays { ## 注意事项 +**中国大陆** + - 年份是按照国务院文件标题年份而不是日期年份,12 月份的日期可能会被下一年的文件影响,因此应检查两个文件。 - `与周末连休` 的周末不是法定节假日,数据里不会包含,见[《全国年节及纪念日放假办法》](https://www.gov.cn/zhengce/content/202411/content_6986380.htm) [#213](https://github.com/NateScarlet/holiday-cn/issues/213#issuecomment-1869546011) [#221](https://github.com/NateScarlet/holiday-cn/issues/221) +**香港** + +- 香港不设调休补班制度,数据中不会出现 `isOffDay: false` 的条目。 +- 假期名称为繁体中文。 +- 数据覆盖范围取决于港府发布进度,通常提前公布至次年。 + ## 通过互联网使用 提示:任何第三方服务都可能故障或停止服务,如果稳定性要求高请自己搭建静态文件服务。 -数据地址格式: +### 中国大陆 `https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/{年份}.json` @@ -62,14 +73,24 @@ interface Holidays { ~~`https://natescarlet.coding.net/p/github/d/holiday-cn/git/raw/master/{年份}.json`~~ +### 香港 + +`https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/hk/{年份}.json` + ## ICalendar 订阅 -网址格式参见上一节 +### 中国大陆 `{年份}.ics` 为对应年份的节假日 `holiday-cn.ics` 为 3 年前至次年的节假日 +### 香港 + +`hk/{年份}.ics` 为对应年份的节假日 + +`holiday-hk.ics` 为 3 年前至次年的节假日 + 感谢 @retanoj 的 ics 格式转换实现 ## 作为 git 子模块使用 From 526d99f1931b372e7dca19781c88dc98d186a11b Mon Sep 17 00:00:00 2001 From: "mingsheng.li" Date: Mon, 27 Apr 2026 12:53:44 +0800 Subject: [PATCH 3/3] docs: add data generation, import, and extension guide to README Co-Authored-By: Claude Sonnet 4.6 --- README.md | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/README.md b/README.md index c02d9d4..aaebec0 100644 --- a/README.md +++ b/README.md @@ -97,4 +97,100 @@ interface Holidays { 参见 [Git 工具 - 子模块](https://git-scm.com/book/zh/v2/Git-%E5%B7%A5%E5%85%B7-%E5%AD%90%E6%A8%A1%E5%9D%97) +## 数据生成 + +安装依赖: + +```sh +pip install -r dev-requirements.txt +``` + +更新当年及次年数据(所有地区): + +```sh +python scripts/update.py +``` + +只更新指定地区: + +```sh +python scripts/update.py --region cn # 仅中国大陆 +python scripts/update.py --region hk # 仅香港 +``` + +更新所有历史年份(仅中国大陆,香港数据源仅覆盖 2024 年起): + +```sh +python scripts/update.py --all --region cn +``` + +CI 每日自动执行 `python scripts/update.py --release`,数据有变化时自动提交并发布新版本。 + +## 导入到业务系统(teramesh-backend) + +数据通过 Django management command 导入工作日历: + +```sh +# 进入应用容器 +kubectl exec -it pod/teramesh-app-0 -- /bin/sh + +# 导入所有地区(中国大陆 + 香港) +python manage.py populate_working_calendar + +# 只导入指定地区 +python manage.py populate_working_calendar --region CN +python manage.py populate_working_calendar --region HK +``` + +命令会导入当年及次年的假期数据。数据按 `country` 级别写入,部署在香港(`country=HK`)的站点自动使用香港假期,内地站点使用内地假期,无需额外配置。 + +## 扩展支持新地区 + +如需接入其他地区(如美国、日本等),按以下步骤操作: + +**1. 新增数据抓取模块** + +在 `scripts/` 下新建 `fetch_{地区代码}.py`,实现 `fetch_{地区代码}_holiday(year: int) -> dict`,返回格式与现有地区一致: + +```python +{ + "year": 2025, + "papers": ["数据来源URL"], + "days": [ + {"name": "假期名称", "date": "2025-01-01", "isOffDay": True} + ] +} +``` + +注意:若该地区无调休补班制度,`isOffDay` 始终为 `True`。 + +**2. 在 `scripts/update.py` 的 `REGIONS` 中注册** + +```python +REGIONS = { + "cn": { ... }, + "hk": { ... }, + "us": { # 新增 + "fetch": fetch_us_holiday, + "start_year": 2024, + "subdir": "us", + "main_ics_name": "holiday-us.ics", + "cal_name": "美国联邦假期", + "cal_desc": "美国联邦公共假期数据。", + }, +} +``` + +**3. 在 `teramesh-backend` 的 `HOLIDAY_SOURCES` 中注册** + +```python +HOLIDAY_SOURCES = { + "CN": f"{GITEA_BASE}/{{year}}.json", + "HK": f"{GITEA_BASE}/hk/{{year}}.json", + "US": f"{GITEA_BASE}/us/{{year}}.json", # 新增 +} +``` + +之后运行 `python manage.py populate_working_calendar --region US` 即可导入,其余地区不受影响。 + [发布页面]: https://github.com/NateScarlet/holiday-cn/releases