Files
holiday-cn/scripts/generate_ics.py
mingsheng.li 05a1dca074 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 <noreply@anthropic.com>
2026-04-27 12:09:34 +08:00

92 lines
2.4 KiB
Python

import datetime
from typing import Any, Iterator, Sequence, Text, Tuple
from icalendar import Event, Calendar, Timezone, TimezoneStandard
def _create_timezone():
tz = Timezone()
tz.add("TZID", "Asia/Shanghai")
tz_standard = TimezoneStandard()
tz_standard.add("DTSTART", datetime.datetime(1970, 1, 1))
tz_standard.add("TZOFFSETFROM", datetime.timedelta(hours=8))
tz_standard.add("TZOFFSETTO", datetime.timedelta(hours=8))
tz.add_component(tz_standard)
return tz
def _create_event(event_name, start, end):
# 创建事件/日程
event = Event()
event.add("SUMMARY", event_name)
event.add("DTSTART", start)
event.add("DTEND", end)
# 创建时间
event.add("DTSTAMP", start)
# UID保证唯一
event["UID"] = f"{start}/{end}/NateScarlet/holiday-cn"
return event
def _cast_date(v: Any) -> datetime.date:
if isinstance(v, datetime.date):
return v
if isinstance(v, str):
return datetime.date.fromisoformat(v)
raise NotImplementedError("can not convert to date: %s" % v)
def _iter_date_ranges(days: Sequence[dict]) -> Iterator[Tuple[dict, dict]]:
if len(days) == 0:
return
if len(days) == 1:
yield days[0], days[0]
return
fr, to = days[0], days[0]
for cur in days[1:]:
if (_cast_date(cur["date"]) - _cast_date(to["date"])).days == 1 and cur[
"isOffDay"
] == to["isOffDay"]:
to = cur
else:
yield fr, to
fr, to = cur, cur
yield fr, to
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_name)
cal.add("X-WR-CALDESC", cal_desc)
cal.add("VERSION", "2.0")
cal.add("METHOD", "PUBLISH")
cal.add("CLASS", "PUBLIC")
cal.add_component(_create_timezone())
days = sorted(days, key=lambda x: x["date"])
for fr, to in _iter_date_ranges(days):
start = _cast_date(fr["date"])
end = _cast_date(to["date"]) + datetime.timedelta(days=1)
name = fr["name"] + "假期"
if not fr["isOffDay"]:
name = "上班(补" + name + ")"
cal.add_component(_create_event(name, start, end))
with open(filename, "wb") as f:
f.write(cal.to_ical())