算工期总把周末算进去:写个函数只算工作日

举报
柠檬-夏日清爽 发表于 2026/09/28 14:32:46 2026/09/28
【摘要】 说好"十个工作日内完成",到底是哪天?手翻日历太蠢。这篇用 Python 写个计算工作日的函数:datetime 逐日遍历、怎么跳过周末、为什么国内的节假日和调休必须靠外部数据而不能自己推,以及那些"看着对其实差一天"的边界情况。

合同上写着"交付周期 15 个工作日",我掐指一算,两周多一点。结果对方说不对,得按日历数——中间夹着周末,实际是三周。

最开始我以为减掉周末就行,写完发现还有国庆、春节,以及那种"周末要上班"的调休。这篇讲怎么用 Python 算准。

一、最朴素的版本

先只用标准库,减去周末:

from datetime import date, timedelta

def workdays(start, end):
    """算 start 到 end 之间的工作日天数(含首尾)"""
    count = 0
    cur = start
    while cur <= end:
        if cur.weekday() < 5:      # 0=周一 ... 4=周五,5、6 是周末
            count += 1
        cur += timedelta(days=1)
    return count

print(workdays(date(2026, 9, 1), date(2026, 9, 15)))

date.weekday() 返回 0 到 6,周一是 0、周日是 6。所以 < 5 就是周一到周五。

这个版本处理"纯工作日"没问题,但它有个大前提:假设中间没有任何节假日。

二、节假日没法自己推

一开始我想着"把每年的节假日写进代码里不就行了",很快发现不现实:

  • 春节、清明、端午、中秋这些是农历相关的,每年公历日期都不一样。
  • 国庆和元旦虽然固定,但放几天、怎么调休每年由官方通知决定。
  • 最麻烦的是调休:某个周六本来休息,但被调成上班日。

所以硬编码节假日列表是个维护噩梦,每年都要改,而且容易漏。

常见的做法有三种:

方式 说明 适合
手动维护节假日列表 每年更新一次 内部系统,能接受人工维护
用第三方库(如 chinesecalendar) 库里维护好了每年的安排 推荐,装个包就完事
调用节假日 API 实时但需要联网 有网络依赖的场景

个人和小项目,我建议直接用现成的库:

pip install chinesecalendar
import chinese_calendar as cc
from datetime import date

def is_workday(d):
    return cc.is_workday(d)

def workdays(start, end):
    count = 0
    cur = start
    while cur <= end:
        if cc.is_workday(cur):
            count += 1
        cur += timedelta(days=1)
    return count

它把节假日和调休都处理好了,每年更新一下库版本就行。

三、反过来:从今天起 N 个工作日是哪天

更常见的需求其实是这样:“15 个工作日之后是哪天?”

def add_workdays(start, n):
    """从 start 开始,第 n 个工作日是哪天(n=1 表示 start 本身如果是工作日就算第一天)"""
    count = 0
    cur = start
    while count < n:
        if is_workday(cur):
            count += 1
            if count == n:
                return cur
        cur += timedelta(days=1)
    return cur

跑一下:

d = add_workdays(date(2026, 9, 15), 15)
print(d, d.strftime('%A'))     # 看看落在星期几

起始日算不算第一天这个一定要跟业务确认清楚。有的场景"从明天开始算 15 个工作日",有的"包括今天"。差一天的事,说清楚省麻烦。

四、数据量大的时候别一天天循环

如果跨度是十年,逐天循环要跑三千多次,虽然也不慢,但可以优化一下:先按周算,再处理零头。

def workdays_fast(start, end):
    # 先算完整的周
    days = (end - start).days + 1
    weeks, remainder = divmod(days, 7)

    count = weeks * 5
    cur = start + timedelta(days=weeks * 7)
    for _ in range(remainder):
        if cur.weekday() < 5:
            count += 1
        cur += timedelta(days=1)
    return count

不过说实话,十年跨度也就三千多次循环,毫秒级的事,普通场景直接逐天循环完全够用,代码还清楚。别为了这点性能把逻辑搞复杂。

五、几个容易出错的地方

  • 含不含首尾。date(9,1) 到 date(9,1) 是 1 天还是 0 天?上面我的实现是 1(因为 while cur <= end)。如果业务说"间隔天数",那应该是 0。这个歧义是最常见的扯皮来源,函数名和注释里写清楚。
  • 跨年要能处理。从 2026-12-20 算到 2027-01-10,别只按一个年份过滤节假日。
  • timedelta 只知道时间差,不知道节假日。有些同学以为 (end - start).days * 5 / 7 能估算工作日,那是平均值,具体到某个月误差很大(比如 2 月和 10 月差很多)。
  • date 和 datetime 别混。datetime 带时分秒,比较和加减时容易出微妙的问题。只关心日期的话用 date,需要时间部分再转。
  • 时区。如果涉及跨时区的团队,"工作日"的定义还得看对方在哪儿。一般小项目不用管,真有需求就把所有时间统一转到一个时区再算。
  • 半天假、特殊排班。这些属于考勤系统的范畴了,普通的工作日计算不管这些,遇到只能单独处理。

六、顺手做个小工具

把常用的两个功能包一下,放在项目里随时用:

from datetime import date, timedelta
import chinese_calendar as cc

def workdays_between(start, end):
    """含首尾的工作日天数"""
    return sum(1 for i in range((end - start).days + 1)
               if cc.is_workday(start + timedelta(days=i)))

def workdays_after(start, n):
    """start 之后第 n 个工作日(不含 start 当天)"""
    cur, count = start, 0
    while count < n:
        cur += timedelta(days=1)
        if cc.is_workday(cur):
            count += 1
    return cur

文章总结

算工作日这件事,代码本身不难——timedelta 逐天加、weekday() < 5 判断就行。真正的坑是节假日和调休没法自己推导,必须靠外部数据(推荐用现成库,别硬编码)。另外一个高频翻车点是首尾含不含的歧义,函数命名和注释里一定要写清楚,不然十天半个月的误差就是这么来的。写完给业务方跑几个例子对一遍确认没问题。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。