说实话,这个题目第一次出现在我面前的时候,我以为又是那种“打开日历看下是不是周六日”的简单工具。直到业务方把需求列出来,我才意识到这里面的复杂度一点不比主流程低。
我当时维护的内部系统,核心诉求只有一个:接收一个日期,告诉调用方那天到底算不算工作日。后来需求慢慢发酵,从“判断今天是否上班”一路扩展到“我要算未来第30个工作日是哪天”“运营希望导出一整年的工作日历”“不同部门的大小周规则还不太一样”。整个过程走下来,我最大的感受是:工作日与节假日判定本身不难,真正难的是数据怎么来、谁来维护、缓存怎么刷、跨年怎么兜底,以及业务规则变化时系统扛不扛得住。
这篇文章我就用自己的项目经历,把整个模块从需求分析、技术选型、数据模型、核心算法到常见坑位完整梳理一遍。适合后端开发、系统架构师,也适合需要排班、时效计算、履约日期推算的产品同学参考。如果你正打算在自己的系统里加这套能力,可以直接对照着做。
1. 项目背景与需求分析
1.1 从“是不是工作日”这个问题开始
最初的需求很简单:前端页面上要显示“今日可预约”或者“今日休息”,所以要一个接口,输入一个日期,返回布尔值。我先用最朴素的方式实现了:
- 周一至周五返回“工作日”;
- 周六、周日返回“休息日”;
- 代码里预置几个固定节假日,比如元旦、国庆。
上线第一周没问题,第二周就出事了。业务方反馈说“10月1日到10月7日我们确实休息,但10月8日为什么还是显示休息”?我一看代码,哦,之前把国庆写成了“10月1日到10月8日”。修完这个Bug之后,又有下一个问题:某周末明明要补班,系统却显示休息,导致当天的履约单量预估差了一大截。
从这个阶段开始,我意识到这个模块不是一个简单的“周末判断”就能盖过去的。它背后其实是一个完整的节假日规则引擎,只是大多数系统的业务量不会一开始就逼你把它做成引擎,等出了问题再改就非常被动。
1.2 需求清单:别只做一个布尔接口
我在梳理需求的时候列了一个表,把后续所有相关场景都放了进去。这张表后来成了我设计接口和数据结构的基础。
| 场景 | 输入 | 期望输出 |
|---|---|---|
| 判断某日是否上班 | 日期 | true/false,最好带上当天类型 |
| 计算未来第N个工作日 | 起始日期、N | 一个具体的日期 |
| 计算某个时间段有几个工作日 | 起止日期 | 整数 |
| 运营导出年历 | 年份 | 每天标注工作/休息/节假日/补班 |
| 不同部门不同规则 | 日期、日历类型 | 按日历类型返回结果 |
这里特别想提醒一点:如果一开始只做“布尔接口”,后续业务要“上班但属于调休补班”这种标签时会非常尴尬。你在接口设计阶段就要把“当天类型”和“是否工作日”分开。当天类型可以有几种,并不完全等于是否上班:
- WORKDAY:普通工作日;
- WEEKEND:普通周末休息日;
- HOLIDAY:法定节假日或公告休息日;
- COMPENSATORY_WORKDAY:调休补班日。
其中 COMPENSATORY_WORKDAY 很可能落在周六或周日,但它是上班日;HOLIDAY 也可能落在工作日,但它是休息日。所以“是否为工作日”应该是根据类型推导出来的派生字段,而不是数据表里的一个独立布尔值。这样后续要区分“今天为什么休息”“今天为什么上班”都会方便很多。
1.3 关键边界条件与配置化取舍
除了基本场景,我还把一些边界条件写进了需求文档。比较典型的有几类:
- 跨年:起始日期是 12 月 29 日,要往后算 5 个工作日,结果可能落在次年 1 月,这时候必须能自动加载下一年的日历数据;
- 多日历共存:有的分公司执行单休,有的部门执行大小周,甚至有的岗位是按“国际工作日”排班,就不能一个系统只支持一套全国日历;
- 节假日数据发布时间不固定:下一年度的假期安排往往到年底才公布,系统不能因为没拿到明年的正式数据就罢工,至少要先按“周一至周五上班、周六日休息”的默认规则跑,等正式数据发布后自动覆盖;
- 历史数据需要保留:后续如果要做结算、排班留痕,必须能把某个历史日期当时属于什么类型给查出来,而不是依赖当前日历往回倒推。
关于“要不要做成配置化”,我的态度很明确:只要系统可能服务超过一年,就必须配置化。硬编码节日表看着省事,但你永远要为一个错误的上线版本付出更大代价。后面我会专门对比不同方案的取舍。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术方案选型与数据设计
2.1 三种数据管理方案对比:硬编码、开源数据、自建配置
我评估过三种主流做法。
第一种是硬编码。典型表现是代码里直接写一个 HashMap,把“2025-01-01”扔进去。优点只有“快”,缺点有一堆:年度数据一发布就要发版;数据谁改的没有痕迹;出错只能靠人肉排查;遇到多日历基本无法扩展。
第二种是引入现成的开源节日库。很多语言都有这类库,能一次性返回某一天的节日/假期状态,而且支持农历转换,确实很省事。但我在评估之后没有把它作为生产主路径,原因是它们的数据更新节奏未必跟得上业务要求,而且一旦库的数据错了,你甚至不知道怎么修正。开源库更适合作为“种子数据来源”,用它把某年的规则初始化到自建系统里,再由运营或者开发手工复核一遍,而不是让线上判定逻辑直接依赖它。
第三种就是自建配置体系。我最后选的是这条路线。核心思想非常简单:把“每年有哪些休息日、哪些补班日”视为普通业务数据,存到数据库里,做成管理后台可维护的配置。判定逻辑只依赖数据库里的规则,不关心这些规则是怎么来的。这样做的好处是数据发布后不用发版,能留审计日志,也支持后续把导入动作做成半自动。
我这里不是否定开源库,而是建议把方案二和方案三结
