feat(scheduler): 新增工作日判定工具(法定节假日与调休)
依赖 chinesecalendar(按年收录,1.11.0 覆盖 2004-2026)。口径:非法定节假日、非周末、非调休放假;补班日(调休上班的周六周日)算工作日,纯 weekday 判断做不到。 库缺失或年份未收录(实测 2027 起抛 NotImplementedError)时降级为周一至周五判断并打 [Workday] 警告,宁可偶尔在节假日多发也不让日报停发。库按年发布,次年安排发布后需升级并重建镜像。
This commit is contained in:
64
inventory-backend/app/utils/workday.py
Normal file
64
inventory-backend/app/utils/workday.py
Normal file
@ -0,0 +1,64 @@
|
||||
"""
|
||||
工作日判定 —— 供定时任务决定「今天要不要发邮件」。
|
||||
|
||||
口径
|
||||
----
|
||||
「工作日」= 非法定节假日、非周末、且非法定调休放假的日子;
|
||||
「补班日」(为凑长假而调休上班的周六/周日)**算工作日**。
|
||||
|
||||
★ 这个口径**无法用规则算出来**。中国的放假/调休安排由国务院逐年发布,
|
||||
纯按 weekday() 判断必然错判:国庆前后的补班周末会被漏发,长假中间的
|
||||
工作日会被误发 —— 两种都是「该发的不发 / 不该发的发了」的实际故障。
|
||||
故依赖 chinese-calendar,它内置国务院公布的节假日与调休数据。
|
||||
|
||||
★ 但它**按年收录**(1.11.0 覆盖 2004–2026),超出范围会抛
|
||||
NotImplementedError。因此这里必须兜底:库缺失或该年份尚未收录时,
|
||||
退化为「周一至周五即工作日」并打警告 ——
|
||||
宁可偶尔在节假日多发一封,也绝不能让日报因第三方库没更新而**整个停发**:
|
||||
收不到报表是比多收一封更难排查的静默故障(发件侧一切正常,没人知道
|
||||
为什么报表没了)。
|
||||
"""
|
||||
import logging
|
||||
from datetime import date, datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from chinese_calendar import is_workday as _cc_is_workday
|
||||
_CC_AVAILABLE = True
|
||||
except ImportError: # 依赖未装(例如镜像未随 requirements 重建)
|
||||
_cc_is_workday = None
|
||||
_CC_AVAILABLE = False
|
||||
|
||||
|
||||
def is_workday(day=None):
|
||||
"""
|
||||
判断某天是否为工作日。
|
||||
|
||||
day:date 或 datetime,缺省取今天(**调用方若需北京时间口径,必须自己
|
||||
传入 `datetime.now(beijing_tz).date()`** —— 本函数不做任何时区换算,
|
||||
容器时区多为 UTC,直接依赖缺省值会在凌晨前后算错一天)。
|
||||
"""
|
||||
if day is None:
|
||||
day = date.today()
|
||||
if isinstance(day, datetime):
|
||||
day = day.date()
|
||||
|
||||
if _CC_AVAILABLE:
|
||||
try:
|
||||
return bool(_cc_is_workday(day))
|
||||
except NotImplementedError:
|
||||
# 该年份尚未被 chinese-calendar 收录(通常是次年安排还没发布)
|
||||
logger.warning(
|
||||
"[Workday] chinese-calendar 未收录 %s 的放假安排,"
|
||||
"本次退化为「仅按周末判断」;请升级 chinesecalendar 包", day
|
||||
)
|
||||
except Exception as e: # noqa: BLE001 —— 第三方库任何异常都不该阻断日报
|
||||
logger.warning(
|
||||
f"[Workday] chinese-calendar 判定 {day} 出错,退化为周末判断: {e}"
|
||||
)
|
||||
else:
|
||||
logger.warning("[Workday] 未安装 chinese-calendar,退化为「仅按周末判断」")
|
||||
|
||||
# 兜底:周一至周五算工作日(无节假日感知,可能误发)
|
||||
return day.weekday() < 5
|
||||
@ -35,4 +35,8 @@ tqdm>=4.66.0
|
||||
# [新增] pgvector 向量数据库支持(以图搜图 / 实时向量提取)
|
||||
pgvector>=0.2.0
|
||||
# [新增] HTTP 客户端(Track 系统 Webhook 异步通知)
|
||||
httpx>=0.24.0
|
||||
httpx>=0.24.0
|
||||
# [新增] 中国法定节假日/调休数据(日报仅工作日发送)。
|
||||
# 按年收录(1.11.0 覆盖 2004–2026),次年数据发布后需 pip install -U chinesecalendar;
|
||||
# 代码侧已兜底(见 app/utils/workday.py),未升级不会导致日报停发。
|
||||
chinesecalendar>=1.11.0
|
||||
Reference in New Issue
Block a user