feat: 集成 apisbo.com 中国节假日 API 自动获取+缓存

- 新增 holidays.py 服务:按年获取节假日数据,缓存到 system_config
- 区分 holiday(工作日假日)和 workday(调休上班日)两种类型
- is_working_day() 支持调休判断:周末在 workdays 集合中视为工作日
- 启动时自动从 API 刷新当年+明年数据,失败则使用缓存
- 系统设置新增「从 API 刷新」按钮,支局长可手动触发
- 支持 2010-2026 年数据,API 免费免鉴权

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-12 14:17:31 +08:00
parent a63bd6874c
commit 7dcb22f573
7 changed files with 199 additions and 33 deletions
+17 -6
View File
@@ -13,19 +13,30 @@ def parse_date(s: str) -> date:
return date.fromisoformat(s[:10])
def is_working_day(d: date | None = None, holidays: set[date] | None = None) -> bool:
def is_working_day(d: date | None = None, holidays: set[date] | None = None, workdays: set[date] | None = None) -> bool:
"""Check if a date is a Chinese working day.
Returns False for:
- Saturdays and Sundays (weekday >= 5)
- Dates in the holidays set (configurable via system_config)
- Saturdays and Sundays (weekday >= 5), UNLESS in the workdays set (调休)
- Dates in the holidays set (weekday holidays like 春节/国庆)
Note: makeup workdays (调休, where Saturday/Sunday becomes a workday)
are not currently handled. If needed, add a 'workdays' config set.
Returns True for:
- Monday-Friday, unless in holidays set
- Saturday/Sunday that are in the workdays set (调休 makeup days)
"""
d = d or today_cst()
if d.weekday() >= 5: # Saturday or Sunday
is_weekend = d.weekday() >= 5
# 调休: weekend that becomes a workday
if is_weekend and workdays and d in workdays:
return True
# Normal weekend
if is_weekend:
return False
# Weekday holiday
if holidays and d in holidays:
return False
return True