如何计算给定日期范围内的周末数量?

1 投票
4 回答
6994 浏览
提问于 2025-04-15 11:42

给定一个日期范围,如何计算这个范围内的周末天数,无论是部分还是全部都算?

(这里有几个定义:周末指的是星期六和星期天。日期范围是包含结束日期的,也就是说结束日期算在内。'部分或全部'的意思是,只要周末的任何一天在这个日期范围内,就算整个周末都算在内。)

为了简单起见,我想你只需要知道这个范围的持续时间,以及开始那天是星期几……

我知道这肯定涉及到用7进行整数除法,并根据余数来加1,但我还没完全搞清楚该怎么做……

如果能用Python来回答,那就更好了 ;-)

编辑

这是我最终的代码。

周末是星期五和星期六(因为我们要计算住宿的夜晚),而日期是从星期一开始的0索引。我使用了onebyone的算法和Tom的代码布局。非常感谢大家。

def calc_weekends(start_day, duration):
    days_until_weekend = [5, 4, 3, 2, 1, 1, 6]
    adjusted_duration = duration - days_until_weekend[start_day]
    if adjusted_duration < 0:
        weekends = 0
    else:
        weekends = (adjusted_duration/7)+1
    if start_day == 5 and duration % 7 == 0: #Saturday to Saturday is an exception
        weekends += 1
    return weekends

if __name__ == "__main__":
    days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    for start_day in range(0,7):
        for duration in range(1,16):
            print "%s to %s (%s days): %s weekends" % (days[start_day], days[(start_day+duration) % 7], duration, calc_weekends(start_day, duration))
        print

4 个回答

1

要计算完整的周末,只需要调整一下天数,从星期一开始,然后用这个天数除以七。注意,如果开始的那天是工作日,就加天数到上一个星期一;如果是周末,就减天数到下一个星期一,因为这个周末你已经错过了。

days = {"Saturday":-2, "Sunday":-1, "Monday":0, "Tuesday":1, "Wednesday":2, "Thursday":3, "Friday":4}

def n_full_weekends(n_days, start_day):
    n_days += days[start_day]
    if n_days <= 0:
        n_weekends = 0
    else:
        n_weekends = n_days//7
    return n_weekends

if __name__ == "__main__":
    tests = [("Tuesday", 10, 1), ("Monday", 7, 1), ("Wednesday", 21, 3), ("Saturday", 1, 0), ("Friday", 1, 0),
    ("Friday", 3, 1), ("Wednesday", 3, 0), ("Sunday", 8, 1), ("Sunday", 21, 2)]
    for start_day, n_days, expected in tests:
        print start_day, n_days, expected, n_full_weekends(n_days, start_day)

如果你想知道部分的周末(或者周),只需要看看除以七后的小数部分。

4

我对这种事情的一般看法是:别急着自己去实现日期逻辑——这很难,特别是边缘情况你很可能会搞错,结果看起来就很糟糕。提示:如果你在程序里用到了7的取模运算,或者把日期当成整数来处理:你失败了。如果我在我的代码里看到“被接受的解决方案”,那就得让某个人重新开始了。真让人难以想象,居然有人觉得自己是程序员还会给那个答案点赞。

与其这样,不如使用Python自带的日期/时间逻辑:

首先,获取你感兴趣的所有日期的列表:

from datetime import date, timedelta    
FRI = 5; SAT = 6

# a couple of random test dates
now = date.today()
start_date = now - timedelta(57)
end_date = now - timedelta(13)
print start_date, '...', end_date    # debug

days = [date.fromordinal(d) for d in  
            range( start_date.toordinal(),
                   end_date.toordinal()+1 )]

接下来,筛选出周末的日期。在你的情况下,你关心的是周五和周六的晚上,也就是5和6。(注意,我没有把这部分和前面的列表推导合并,因为那样很难验证是否正确)。

weekend_days = [d for d in days if d.weekday() in (FRI,SAT)]

for day in weekend_days:      # debug
    print day, day.weekday()  # debug

最后,你需要弄清楚你的列表里有多少个周末。这部分比较棘手,但实际上只需要考虑四种情况,分别对应周五或周六的两种情况。具体的例子能帮助你更清楚理解,而且这也是你希望在代码中有文档记录的内容:

num_weekends = len(weekend_days) // 2

# if we start on Friday and end on Saturday we're ok,
# otherwise add one weekend
#  
# F,S|F,S|F,S   ==3 and 3we, +0
# F,S|F,S|F     ==2 but 3we, +1
# S|F,S|F,S     ==2 but 3we, +1
# S|F,S|F       ==2 but 3we, +1

ends = (weekend_days[0].weekday(), weekend_days[-1].weekday())
if ends != (FRI, SAT):
    num_weekends += 1

print num_weekends    # your answer

更简短、更清晰、更容易理解意味着你可以对自己的代码更有信心,也能继续处理更有趣的问题。

5

这种情况的一般处理方法是:

对于每周的每一天,计算从那一天开始的时间段需要多少天才能“包含一个周末”。比如,如果“包含一个周末”的意思是“包含星期六和星期天”,那么我们可以得到以下表格:

星期天:8天
星期一:7天
星期二:6天
星期三:5天
星期四:4天
星期五:3天
星期六:2天

如果是“部分或全部”,我们有:

星期天:1天
星期一:6天
星期二:5天
星期三:4天
星期四:3天
星期五:2天
星期六:1天

显然,这些数据不一定要用表格来编码,因为现在已经很清楚它们的样子了。

接下来,给定你开始的那一天,从时间段的天数中减去[*]这个神奇的值(通常是结束天数减去开始天数再加1,以包括两个端点)。如果结果小于0,说明没有周末。如果结果大于或等于0,那就至少有1个周末。

然后你需要处理剩下的天数。在第一种情况下,这很简单,每7天就会多一个周末。在第二种情况下,除了星期天,其他每一天的开始也适用这个规则,星期天只需要再多6天才能包含另一个周末。所以在第二种情况下,如果时间段是从星期天开始的,你可以算上这个时间段开始时的1个周末,然后把长度减1,再从星期一重新计算。

更一般来说,对于“完整或部分”周末的情况,我们是在检查是否从有趣的部分(即“周末”)的中间开始。如果是这样,我们可以选择:

  • 1) 计算一个周末,把开始日期移动到有趣部分的结束,然后重新计算。
  • 2) 把开始日期移回到有趣部分的开始,然后重新计算。

在周末的情况下,只有一个特殊情况是从中间开始的,所以选择(1)看起来不错。但是如果你是以日期+时间的秒数来获取日期,而不是以天数,或者如果你关注的是5天的工作周而不是2天的周末,那么选择(2)可能更容易理解。

[*] 当然,除非你使用的是无符号类型。

撰写回答