Files
KCGL/inventory-backend/app/utils/workday.py
yueli 27ac1e4e82 feat(scheduler): 新增工作日判定工具(法定节假日与调休)
依赖 chinesecalendar(按年收录,1.11.0 覆盖 2004-2026)。口径:非法定节假日、非周末、非调休放假;补班日(调休上班的周六周日)算工作日,纯 weekday 判断做不到。

库缺失或年份未收录(实测 2027 起抛 NotImplementedError)时降级为周一至周五判断并打 [Workday] 警告,宁可偶尔在节假日多发也不让日报停发。库按年发布,次年安排发布后需升级并重建镜像。
2026-10-03 09:48:38 +08:00

65 lines
2.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
工作日判定 —— 供定时任务决定「今天要不要发邮件」。
口径
----
「工作日」= 非法定节假日、非周末、且非法定调休放假的日子;
「补班日」(为凑长假而调休上班的周六/周日)**算工作日**。
★ 这个口径**无法用规则算出来**。中国的放假/调休安排由国务院逐年发布,
纯按 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