Python2:检索给定日期范围的星期日 - 星期六周开始/结束日期

Posted

技术标签:

【中文标题】Python2:检索给定日期范围的星期日 - 星期六周开始/结束日期【英文标题】:Python2: Retrieve Sunday - Saturday Week Start/End Dates For Given Date Range 【发布时间】:2016-08-24 23:04:13 【问题描述】:

有很多帖子解决了类似的问题,但没有一个帖子具有与我的问题相同的限制。

我正在编写一个脚本,用于从数据中心获取任意数周的数据。它获取的周数取决于外部用户提供给我的脚本的日期范围。数据中心的一周从周日到周六。 Python 的一周从周一到周日。

我需要能够获取日期范围内每个日期之前的星期日和星期六之后的日期。更复杂的是,星期开始日期和星期结束日期都不能超出请求的范围。这使我无法简单地从范围内的每个日期中减去一天。

一些示例场景:

示例 1)

requested_date_range = [datetime(2016,7,1,0,0),datetime(2016,8,5,0,0)]
what I get from the various Python utilities (dateutil, datetime_periods, etc):

[
[datetime(2016,6,27,0,0),datetime(2016,7,3,0,0)],
[datetime(2016,7,4,0,0),datetime(2016,7,10,0,0)],
[datetime(2016,7,11,0,0),datetime(2016,7,17,0,0)],
[datetime(2016,7,18,0,0),datetime(2016,7,24,0,0)],
[datetime(2016,7,25,0,0),datetime(2016,7,31,0,0)],
[datetime(2016,8,1,0,0),datetime(2016,8,7,0,0)]
]

what I actually need:
[
[datetime(2016,7,1,0,0),datetime(2016,7,2,0,0)], #"week" starts on first day of requested range and ends on the following Saturday
[datetime(2016,7,3,0,0),datetime(2016,7,9,0,0)], #Sunday through Saturday
[datetime(2016,7,10,0,0),datetime(2016,7,16,0,0)], #Sunday through Saturday
[datetime(2016,7,17,0,0),datetime(2016,7,23,0,0)], #Sunday through Saturday
[datetime(2016,7,24,0,0),datetime(2016,7,30,0,0)], #Sunday through Saturday
[datetime(2016,7,31,0,0),datetime(2016,8,5,0,0)] #"week" starts on Sunday and ends on last day of requested range
] 

示例 2)

requested_date_range = [datetime(2016,7,3,0,0),datetime(2016,8,7,0,0)]
what I get from the various Python utilities (dateutil, datetime_periods, etc):
[
[datetime(2016,6,27,0,0),datetime(2016,7,3,0,0)],
[datetime(2016,7,4,0,0),datetime(2016,7,10,0,0)],
[datetime(2016,7,11,0,0),datetime(2016,7,17,0,0)],
[datetime(2016,7,18,0,0),datetime(2016,7,24,0,0)],
[datetime(2016,7,25,0,0),datetime(2016,7,31,0,0)],
[datetime(2016,8,1,0,0),datetime(2016,8,7,0,0)]
]
what I actually need: 
[
[datetime(2016,7,3,0,0),datetime(2016,7,9,0,0)], #"week" starts on first day of requested range
[datetime(2016,7,10,0,0),datetime(2016,7,16,0,0)], #Sunday through Saturday
[datetime(2016,7,17,0,0),datetime(2016,7,23,0,0)], #Sunday through Saturday
[datetime(2016,7,24,0,0),datetime(2016,7,30,0,0)], #Sunday through Saturday
[datetime(2016,7,31,0,0),datetime(2016,8,6,0,0)], #Sunday through Saturday
[datetime(2016,8,7,0,0),datetime(2016,8,7,0,0)]  #"week" ends up being only one day long because the max requested date falls on a Sunday
]

【问题讨论】:

我认为您可以设置一周的开始日期。让我深入研究一下。 您可以使用 calendar.setfirstweekday(calendar.SUNDAY)。不幸的是,日历类没有返回星期开始/结束日期的函数。至少我找不到。 【参考方案1】:

您应该可以使用dateutil.relativedelta 轻松完成此操作。下面是一个示例函数:

from dateutil.relativedelta import relativedelta
from dateutil.relativedelta import MO, TU, WE, TH, FR, SA, SU

def week_range(range_start, range_end):
    dts = []
    WEEK_START = relativedelta(weekday=SU(+2))
    WEEK_END = relativedelta(weekday=SA)

    c_wstart = range_start + relativedelta(weekday=SU(+1))
    c_wend = c_wstart + WEEK_END

    if range_start < c_wstart:
        dts.append((range_start, range_start + WEEK_END))

    while True:
        if c_wend > range_end:
            c_wend = range_end

        dts.append((c_wstart, c_wend))

        if c_wend >= range_end:
            break

        c_wstart = c_wstart + WEEK_START
        c_wend = c_wstart + WEEK_END

        if c_wstart > range_end:
            break

    return dts

在上面的函数中,我首先取范围开头并将relativedelta(weekday=SU) 添加到它,这给了我原始日期或之后的第一个星期日。然后,我将relativedelta(weekday=SU(+2)) 连续添加到“当前周”以获取当前日期或之后的第二个星期日(由于我的“周开始”始终是星期日,因此始终是下一个星期日)。

对于我生成的每个日期,我只需将relativedelta(weekday=SA) 添加到它以生成即将到来的星期六,如果我在日期范围之外,我将最后一个日期“剪辑”为日期范围。

使用你的例子:

>>> week_range(datetime(2016, 7, 1), datetime(2016, 8, 5))
[(datetime.datetime(2016, 7, 1, 0, 0), datetime.datetime(2016, 7, 2, 0, 0)),
 (datetime.datetime(2016, 7, 3, 0, 0), datetime.datetime(2016, 7, 9, 0, 0)),
 (datetime.datetime(2016, 7, 10, 0, 0), datetime.datetime(2016, 7, 16, 0, 0)),
 (datetime.datetime(2016, 7, 17, 0, 0), datetime.datetime(2016, 7, 23, 0, 0)),
 (datetime.datetime(2016, 7, 24, 0, 0), datetime.datetime(2016, 7, 30, 0, 0)),
 (datetime.datetime(2016, 7, 31, 0, 0), datetime.datetime(2016, 8, 5, 0, 0))]
>>> week_range(datetime(2016, 7, 3), datetime(2016, 8, 7))
[(datetime.datetime(2016, 7, 3, 0, 0), datetime.datetime(2016, 7, 9, 0, 0)),
 (datetime.datetime(2016, 7, 10, 0, 0), datetime.datetime(2016, 7, 16, 0, 0)),
 (datetime.datetime(2016, 7, 17, 0, 0), datetime.datetime(2016, 7, 23, 0, 0)),
 (datetime.datetime(2016, 7, 24, 0, 0), datetime.datetime(2016, 7, 30, 0, 0)),
 (datetime.datetime(2016, 7, 31, 0, 0), datetime.datetime(2016, 8, 6, 0, 0)),
 (datetime.datetime(2016, 8, 7, 0, 0), datetime.datetime(2016, 8, 7, 0, 0))]

根据您的喜好,您也可以使用rruleset 完成类似的操作:

from dateutil.rrule import rrule, rruleset
from dateutil.rrule import WEEKLY, SU, SA
from datetime import timedelta

from itertools import zip_longest, chain
def week_range_rrule(range_start, range_end, weekday_start=SU, weekday_end=SA):
    # Beginning of the week rule
    rr1 = rrule(WEEKLY, byweekday=weekday_start,
                dtstart=range_start, until=range_end)

    # End of the week rule - adding 1 second to the range end because
    # "until" isn't inclusive
    rr2 = rrule(WEEKLY, byweekday=weekday_end,
                dtstart=range_start+relativedelta(SA),
                until=range_end+timedelta(seconds=1))

    # Combine these into a rule set
    rrs = rruleset()
    rrs.rrule(rr1)
    rrs.rrule(rr2)

    # Explicitly add range start and end to the rules, in case they don't
    # fall on neat week boundaries
    rrs.rdate(range_start)
    rrs.rdate(range_end)

    if next(iter(rr2)) == range_start:
        rrs = chain((range_start, ), rrs)

    # Modified version of the "grouper" recipe from itertools
    args = [iter(rrs)] * 2

    return list(zip_longest(*args, fillvalue=range_end))

请注意,如果您希望第一个是惰性的,只需将 dts.append(x) 的所有实例替换为 yield x。如果您希望第二个是惰性的,只需在 return 语句中删除 zip_longest 周围的 list() 包装器即可。

【讨论】:

这太棒了!两种解决方案都非常有效。解释也很棒!我希望我能投不止一个赞成票。谢谢! @NickMiller 您可以,因为您提出了这个问题 - 您可以通过选择投票按钮下方的绿色复选标记来接受答案。 完成!再次感谢保罗。【参考方案2】:

这里有一个稍微不那么冗长但简洁的答案。

import datetime as dt

if __name__ == "__main__":
    weekend_index = (6, 5)  # Sunday, Saturday
    requested_range = (dt.datetime(2016, 7, 9, 0, 0), dt.datetime(2016, 8, 11, 0, 0))

    start, end = requested_range
    sun, sat = weekend_index
    cur = start
    my_range = []

    while cur < end:
            cr = []
            cr.append(cur)
            cur = end if end < cur+dt.timedelta(days=6) else (cur+dt.timedelta(days=(sun if cur.weekday() == sun else (sat-cur.weekday()))))
            cr.append(cur)
            cur += dt.timedelta(days=1)
            my_range.append(cr)

print(my_range)  # Returns:
# [[datetime.datetime(2016, 7, 9, 0, 0), datetime.datetime(2016, 7, 9, 0, 0)],
#  [datetime.datetime(2016, 7, 10, 0, 0), datetime.datetime(2016, 7, 16, 0, 0)],
#  [datetime.datetime(2016, 7, 17, 0, 0), datetime.datetime(2016, 7, 23, 0, 0)],
#  [datetime.datetime(2016, 7, 24, 0, 0), datetime.datetime(2016, 7, 30, 0, 0)],
#  [datetime.datetime(2016, 7, 31, 0, 0), datetime.datetime(2016, 8, 6, 0, 0)],
#  [datetime.datetime(2016, 8, 7, 0, 0), datetime.datetime(2016, 8, 11, 0, 0)]]

【讨论】:

以上是关于Python2:检索给定日期范围的星期日 - 星期六周开始/结束日期的主要内容,如果未能解决你的问题,请参考以下文章

统计日期范围内的天数

在ACCESS中返回给定日期星期几的植的函数

用SQL语句计算出给定日期是星期几?

用SQL语句计算出给定日期是星期几?

Redshift:提取给定星期和年份的星期六日期

BigQuery 从日期字段中提取星期作为日期范围