[我有这些日期,2020年2月2日和2020年6月30日,我想检查一下它们之间有多少天,跳过指定的日期,例如12月25日或5月1日,以及周末。
例如,上述两个日期之间为147天(结束日期不算在内),但是这些日期之间有21个周末,因此只有105个工作日。如果5月1日(星期五)是假期,那么最终答案将是104个工作日。
我已经完成了以下操作以跳过周末,但是我仍然对如何跳过假期一无所知;有没有一种方法可以创建一种“黑名单”,因此,如果该差异通过该列表中的任何一天,它将减去一天。起初我虽然使用字典,但是我不知道如何使用。
这是周末的“解决方法”:
import math
from datetime import datetime
date_input = '4/2/2020'
date_end = '30/6/2020'
start = datetime.strptime(date_input, "%d-%m-%Y").date()
end = datetime.strptime(date_end, "%d-%m-%Y").date()
Gap = (end - start).days
N_weeks = Gap / 7
weekends = (math.trunc(N_weeks)) * 2
final_result = str((Gap) - weekends)
如何从此计数中删除假期日期?
如果您有list个应该跳过的日期,那么您可以测试其中是否有一个属于您的开始日期和结束日期的范围。 date
对象是可排序的,因此可以使用:
# list of holiday dates
dates_to_skip = [date(2020, 5, 1), date(2020, 12, 25)]
skip_count = 0
for to_skip in dates_to_skip:
if start <= to_skip < end:
skip_count += 1
start <= to_skip < end
链式比较只有在to_skip
日期介于两个值之间时才为真。对于您的示例日期,只有5月1日才是这种情况:
>>> from datetime import date
>>> start = date(2020, 2, 4)
>>> end = date(2020, 6, 30)
>>> dates_to_skip = [date(2020, 5, 1), date(2020, 12, 25)]
>>> for to_skip in dates_to_skip:
... if start <= to_skip < end:
... print(f"{to_skip} falls between {start} and {end}")
...
2020-05-01 falls between 2020-02-04 and 2020-06-30
如果您要跳过的日期列表为大,则上述操作可能会花费很长时间,因此单独测试列表中的每个日期并不是那么有效。
在那种情况下,您要使用bisection快速确定start
和end
之间的匹配日期数,方法是确保要跳过的日期列表保持为sorted order,然后使用bisect
module查找要插入bisect
和start
的索引;这两个索引之间的差异是您要从范围计数中减去的匹配日期数:
end
注意,from bisect import bisect_left
def count_skipped(start, end, dates_to_skip):
"""Count how many dates in dates_to_skip fall between start and end
start is inclusive, end is exclusive
"""
if start >= end:
return 0
start_idx = bisect_left(dates_to_skip, start)
end_idx = bisect_left(dates_to_skip, end, start_idx)
return end_idx - start_idx
为您提供所有值的索引bisect.bisect_left()
中的等于或大于开始日期。对于结束日期,dates_to_skip[start_idx:]
中的所有值都将更低(dates_to_skip[:end_idx]
本身可以等于dates_to_skip[end_idx]
,但不包括end
)。而且,一旦您知道开始日期的索引,我们也可以告诉end
跳过所有直到该点的值,因为结束日期将会更高(尽管bisect_left()
处的值可能高于两个开始日期并结束)。这两个索引之间的区别是开始日期和结束日期之间的日期数。
使用dates_to_skip[start_idx]
的优势在于,需要O(logN)步骤来计算N个日期列表中有多少个日期落在bisect
和start
之间,而上面的简单化end
循环需要O(N)个步骤。有5个或10个要测试的日期无关紧要,但是如果有1000个日期,则for to_skip in dates_to_skip:
方法仅需要10个步骤,而不是1000个就变得很重要。
请注意,您的周末计算是不正确,这太简单了。这是一个示例,显示周末日期的数量在11天的两个不同时段中有所不同:
假设您的开始日期是一个星期一,结束日期是又一个星期的星期五,您之间只有1个周末,所以11-2 = 9个工作日(不计算结束日期):
bisect
在上表中,| M | T | W | T | F | S | S |
|-----|---|---|---|-----|---- |-----|
| [1] | 2 | 3 | 4 | 5 | _1_ | _2_ |
| 6 | 7 | 8 | 9 | (E) | | |
是开始日期,[1]
是结束日期,数字表示工作日;跳过的周末数以(E)
,_1_
数字计算。
但是如果开始日期是星期五,而结束日期是下一个第二周的星期二,那么您在开始和结束之间的整天数相同,但是现在您必须减去two个周末;这两天之间只有7个工作日:
_2_
要计算整个周末,请从开始日期和结束日期中找出最近的星期六(向前),找到它们之间的星期数,然后,如果开始或结束是移动之前的星期日,则调整该数字(开始时为一个星期天,在总数中加1,结束日期为星期日,从总数中减去1天。]
您可以通过取| M | T | W | T | F | S | S |
|---|-----|---|---|-----|-----|-----|
| | | | | [1] | _1_ | _2_ |
| 2 | 3 | 4 | 5 | 6 | _3_ | _4_ |
| 7 | (E) | | | | | |
,然后从5中减去,然后将该值模数7作为要添加的天数,找到任何给定日期的最近星期六。这将始终为您提供一周中任何一天的正确价值;对于周末(0-4),date.weekday()
value是到星期六要跳过的正天数,对于星期六(5),结果是0(没有要跳过的天数),对于星期日(6),date.weekday()
是5 - date.weekday()
,但是5 - 6
模运算将其转换为-1
,所以6天。
以下函数实现了这些技巧,以使您在两个日期% 7
和(7 - 1)
之间正确的周末天数,其中start
低于end
:
start
调整逻辑可能需要更多说明。向前移动两个边界比在时间上向前和向后移动终点要容易得多。无需对计数进行其他调整,同时使两个日期之间的整个周末计数变得微不足道。
[如果end
和from datetime import timedelta
def count_weekend_days(start, end):
"""Count the number of weekend days (Saturday, Sunday)
Start and end dates are inclusive.
"""
if start >= end:
return 0
# If either start or end are a Sunday, count these manually
# Boolean results have either a 0 (false) or 1 (true) integer
# value, so we can do arithmetic with these:
boundary_sundays = (start.weekday() == 6) - (end.weekday() == 6)
# find the nearest Saturday from the start and end, going forward
start += timedelta(days=(5 - start.weekday()) % 7)
end += timedelta(days=(5 - end.weekday()) % 7)
# start and end are Saturdays, the difference between
# these days is going to be a whole multiple of 7.
# Floor division by 7 gives the number of whole weekends
weekends = (end - start).days // 7
return boundary_sundays + (weekends * 2)
均为工作日(其start
方法结果为0到4之间的值),那么移动到下一个星期六将在两个日期之间保持相同的整个周末数他们从什么工作日开始。以这种方式向前移动日期不会使周末的日数产生偏差,但是确实可以更轻松地获取正确的数字。
[如果end
落在一个星期日,则向前移动到下一个星期六将需要分别考虑该跳过的星期日;您希望将半个周末添加到结果中,以便将总数加1。如果date.weekday()
落在星期日,则该天不应计入总数(结束日期在该范围内),但移至下一个星期六将将其包括在计数中,因此您要减去这个额外的周末。
在上面的代码中,我简单地使用两个布尔测试加上减法来进行初始start
值计算。在Python中,end
类型是boundary_sundays
的子类,并且bool
和int
具有整数值。减去两个布尔值将得到一个整数值。 False
将是True
,boundary_sundays
或-1
,具体取决于我们找到多少个星期日。
将它们放在一起:
0
演示:
1