如何计算给定日期范围内的周末数量?
给定一个日期范围,如何计算这个范围内的周末天数,无论是部分还是全部都算?
(这里有几个定义:周末指的是星期六和星期天。日期范围是包含结束日期的,也就是说结束日期算在内。'部分或全部'的意思是,只要周末的任何一天在这个日期范围内,就算整个周末都算在内。)
为了简单起见,我想你只需要知道这个范围的持续时间,以及开始那天是星期几……
我知道这肯定涉及到用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 个回答
要计算完整的周末,只需要调整一下天数,从星期一开始,然后用这个天数除以七。注意,如果开始的那天是工作日,就加天数到上一个星期一;如果是周末,就减天数到下一个星期一,因为这个周末你已经错过了。
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)
如果你想知道部分的周末(或者周),只需要看看除以七后的小数部分。
我对这种事情的一般看法是:别急着自己去实现日期逻辑——这很难,特别是边缘情况你很可能会搞错,结果看起来就很糟糕。提示:如果你在程序里用到了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
更简短、更清晰、更容易理解意味着你可以对自己的代码更有信心,也能继续处理更有趣的问题。
这种情况的一般处理方法是:
对于每周的每一天,计算从那一天开始的时间段需要多少天才能“包含一个周末”。比如,如果“包含一个周末”的意思是“包含星期六和星期天”,那么我们可以得到以下表格:
星期天: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)可能更容易理解。
[*] 当然,除非你使用的是无符号类型。