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