holidays.py
3.7 KB| 1 | #!/usr/bin/env python3 |
| 2 | # 中国节假日查询(timor.tech 免费 API,无需 Key) |
| 3 | import argparse, json, sys, urllib.request |
| 4 | from datetime import date, timedelta |
| 5 | |
| 6 | BASE = 'https://timor.tech/api/holiday' |
| 7 | |
| 8 | |
| 9 | def fetch(path): |
| 10 | req = urllib.request.Request(BASE + path, headers={'User-Agent': 'china-holidays-skill/1.0'}) |
| 11 | with urllib.request.urlopen(req, timeout=15) as r: |
| 12 | return json.loads(r.read().decode()) |
| 13 | |
| 14 | |
| 15 | def load_year(year): |
| 16 | data = fetch(f'/year/{year}') |
| 17 | holidays = data.get('holiday') or {} |
| 18 | out = {} |
| 19 | for k, v in holidays.items(): |
| 20 | if not isinstance(v, dict) or not v.get('date'): |
| 21 | continue |
| 22 | out[v['date']] = v |
| 23 | return out |
| 24 | |
| 25 | |
| 26 | def kind_of(v): |
| 27 | if v.get('wage') == 1: |
| 28 | return '调休上班' |
| 29 | if v.get('holiday'): |
| 30 | return '放假' |
| 31 | return '普通日' |
| 32 | |
| 33 | |
| 34 | def main(): |
| 35 | p = argparse.ArgumentParser(description='中国节假日查询') |
| 36 | p.add_argument('date', nargs='?', help='日期 YYYY-MM-DD,或 今天/明天') |
| 37 | p.add_argument('--year', type=int, help='查询年份') |
| 38 | p.add_argument('--next', action='store_true', help='下个节假日倒计时') |
| 39 | p.add_argument('--today', action='store_true', help='今天是什么日子') |
| 40 | a = p.parse_args() |
| 41 | |
| 42 | today = date.today() |
| 43 | |
| 44 | # 指定日期判断 |
| 45 | if a.date: |
| 46 | if a.date in ('今天', '今日'): |
| 47 | target = today |
| 48 | elif a.date in ('明天', '明日'): |
| 49 | target = today + timedelta(days=1) |
| 50 | else: |
| 51 | try: |
| 52 | target = date.fromisoformat(a.date) |
| 53 | except ValueError: |
| 54 | print(f'日期格式错误: {a.date}(应为 YYYY-MM-DD)') |
| 55 | sys.exit(1) |
| 56 | holidays = load_year(target.year) |
| 57 | info = holidays.get(target.isoformat()) |
| 58 | if not info: |
| 59 | print(f'{target.isoformat()}:普通工作日(非节假日)') |
| 60 | return |
| 61 | name = info.get('name', '') |
| 62 | extra = '' |
| 63 | if info.get('wage') == 1 and info.get('target'): |
| 64 | extra = f'({info["target"]}调休补班)' |
| 65 | print(f'{target.isoformat()}:{name} {kind_of(info)}{extra}') |
| 66 | return |
| 67 | |
| 68 | # --today |
| 69 | if a.today: |
| 70 | holidays = load_year(today.year) |
| 71 | info = holidays.get(today.isoformat()) |
| 72 | if info: |
| 73 | print(f'今天 {today.isoformat()}:{info.get("name","")} {kind_of(info)}') |
| 74 | else: |
| 75 | print(f'今天 {today.isoformat()}:普通工作日') |
| 76 | return |
| 77 | |
| 78 | # --next |
| 79 | if a.next: |
| 80 | for y in (today.year, today.year + 1): |
| 81 | holidays = load_year(y) |
| 82 | items = [(k, v) for k, v in holidays.items() |
| 83 | if v.get('holiday') and v.get('wage') != 1 and k >= today.isoformat()] |
| 84 | if items: |
| 85 | k, v = min(items, key=lambda kv: kv[0]) |
| 86 | d = date.fromisoformat(k) |
| 87 | days = (d - today).days |
| 88 | print(f'下个节假日:{v.get("name","")} {k},还剩 {days} 天') |
| 89 | return |
| 90 | print(f'{today.year} 年剩余时间无节假日({today.year + 1} 年节假日安排通常年底发布)') |
| 91 | return |
| 92 | |
| 93 | # 年份总览 |
| 94 | year = a.year or today.year |
| 95 | holidays = load_year(year) |
| 96 | items = [(k, v) for k, v in holidays.items() if v.get('holiday') and v.get('wage') != 1] |
| 97 | items.sort() |
| 98 | if not items: |
| 99 | print(f'{year} 年节假日数据未发布或为空') |
| 100 | return |
| 101 | print(f'{year} 年节假日总览:') |
| 102 | for k, v in items: |
| 103 | print(f' {k} {v.get("name","")} 放假') |
| 104 | future = [(k, v) for k, v in items if k >= today.isoformat()] |
| 105 | if future: |
| 106 | k, v = future[0] |
| 107 | days = (date.fromisoformat(k) - today).days |
| 108 | print(f'\n距下个节假日:{v.get("name","")} {k},还剩 {days} 天') |
| 109 | |
| 110 | |
| 111 | if __name__ == '__main__': |
| 112 | main() |
文件内容是平台检查时读出的文本,只供查看。平台不会运行包里的任何脚本或程序。

