依赖 chinesecalendar(按年收录,1.11.0 覆盖 2004-2026)。口径:非法定节假日、非周末、非调休放假;补班日(调休上班的周六周日)算工作日,纯 weekday 判断做不到。 库缺失或年份未收录(实测 2027 起抛 NotImplementedError)时降级为周一至周五判断并打 [Workday] 警告,宁可偶尔在节假日多发也不让日报停发。库按年发布,次年安排发布后需升级并重建镜像。
65 lines
2.7 KiB
Python
65 lines
2.7 KiB
Python
"""
|
||
工作日判定 —— 供定时任务决定「今天要不要发邮件」。
|
||
|
||
口径
|
||
----
|
||
「工作日」= 非法定节假日、非周末、且非法定调休放假的日子;
|
||
「补班日」(为凑长假而调休上班的周六/周日)**算工作日**。
|
||
|
||
★ 这个口径**无法用规则算出来**。中国的放假/调休安排由国务院逐年发布,
|
||
纯按 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
|