generate_ics.py 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import datetime
  2. from typing import Any, Iterator, Sequence, Text, Tuple
  3. from icalendar import Event, Calendar, Timezone, TimezoneStandard
  4. def _create_timezone():
  5. tz = Timezone()
  6. tz.add("TZID", "Asia/Shanghai")
  7. tz_standard = TimezoneStandard()
  8. tz_standard.add("DTSTART", datetime.datetime(1970, 1, 1))
  9. tz_standard.add("TZOFFSETFROM", datetime.timedelta(hours=8))
  10. tz_standard.add("TZOFFSETTO", datetime.timedelta(hours=8))
  11. tz.add_component(tz_standard)
  12. return tz
  13. def _create_event(event_name, start, end):
  14. # 创建事件/日程
  15. event = Event()
  16. event.add("SUMMARY", event_name)
  17. event.add("DTSTART", start)
  18. event.add("DTEND", end)
  19. # 创建时间
  20. event.add("DTSTAMP", start)
  21. # UID保证唯一
  22. event["UID"] = f"{start}/{end}/NateScarlet/holiday-cn"
  23. return event
  24. def _cast_date(v: Any) -> datetime.date:
  25. if isinstance(v, datetime.date):
  26. return v
  27. if isinstance(v, str):
  28. return datetime.date.fromisoformat(v)
  29. raise NotImplementedError("can not convert to date: %s" % v)
  30. def _iter_date_ranges(days: Sequence[dict]) -> Iterator[Tuple[dict, dict]]:
  31. if len(days) == 0:
  32. return
  33. if len(days) == 1:
  34. yield days[0], days[0]
  35. return
  36. fr, to = days[0], days[0]
  37. for cur in days[1:]:
  38. if (_cast_date(cur["date"]) - _cast_date(to["date"])).days == 1 and cur[
  39. "isOffDay"
  40. ] == to["isOffDay"]:
  41. to = cur
  42. else:
  43. yield fr, to
  44. fr, to = cur, cur
  45. yield fr, to
  46. def generate_ics(days: Sequence[dict], filename: Text) -> None:
  47. """Generate ics from days."""
  48. cal = Calendar()
  49. cal.add("X-WR-CALNAME", "中国法定节假日")
  50. cal.add("X-WR-CALDESC", "中国法定节假日数据,自动每日抓取国务院公告。")
  51. cal.add("VERSION", "2.0")
  52. cal.add("METHOD", "PUBLISH")
  53. cal.add("CLASS", "PUBLIC")
  54. cal.add_component(_create_timezone())
  55. days = sorted(days, key=lambda x: x["date"])
  56. for fr, to in _iter_date_ranges(days):
  57. start = _cast_date(fr["date"])
  58. end = _cast_date(to["date"]) + datetime.timedelta(days=1)
  59. name = fr["name"] + "假期"
  60. if not fr["isOffDay"]:
  61. name = "上班(补" + name + ")"
  62. cal.add_component(_create_event(name, start, end))
  63. with open(filename, "wb") as f:
  64. f.write(cal.to_ical())