两个日期之间的时间,除了周末

时间:2015-09-22 19:20:29

标签: python datetime

我需要一个函数来计算除周末日之外的两个日期之间的时间(以秒为单位),这将是这样的:

# friday 9 PM
start_date = datetime.datetime(2015, 9, 18, 21, 0, 0)

# monday 3 AM
end_date = datetime.datetime(2015, 9, 21, 3, 0, 0)

# should return 6 hours
time = time_between_two_dates_except_weekends(start_date, end_date)

我实现了自己的功能,这是有效的,但似乎不必要地庞大而复杂。我认为它可以更简单。

import datetime

from dateutil.relativedelta import relativedelta
from dateutil.rrule import DAILY, rrule

def time_between_two_dates_except_weekends(start_date, end_date):

    WEEKEND_DAYS = [5, 6]

    result = datetime.timedelta()

    if all([start_date.year == end_date.year, start_date.month == end_date.month, start_date.day == end_date.day]):
         result += datetime.timedelta(seconds = (end_date-start_date).seconds )
         return result

    day_after_start_date = start_date + relativedelta(days=1)
    day_after_start_date = day_after_start_date.replace(hour=0, minute=0, second=0)

    day_before_end_date = end_date - relativedelta(days=1)

    if start_date.weekday() not in WEEKEND_DAYS:
        result += datetime.timedelta(seconds = (day_after_start_date - start_date).total_seconds())

    dates_range = rrule(
        DAILY,
        byhour=0,
        byminute=0,
        bysecond=0,
        dtstart=day_after_start_date,
        until=day_before_end_date
    )

    for date in dates_range:
        if date.weekday() not in WEEKEND_DAYS:
            result += datetime.timedelta(seconds=24 * 60 * 60)

    if end_date.weekday() not in WEEKEND_DAYS:
        end_date_beginning = end_date.replace(hour=0, minute=0, second=0)
        result += datetime.timedelta(seconds = (end_date - end_date_beginning).total_seconds())

    return result

有没有办法改善这个?

UPD。结果表明,不仅我的代码很复杂,而且在某些极端情况下返回错误的结果(例如,当开始或结束日期的周末日期过去时)。我建议只使用下面正确答案的代码

1 个答案:

答案 0 :(得分:4)

from datetime import timedelta
def diff(s, e):
    _diff = (end_date - start_date)
    while s < e:
        if s.weekday() in {5, 6}:
            _diff -= timedelta(days=1)
        s += timedelta(days=1)
    return timedelta(seconds=_diff.total_seconds())

如果您的日期可以在周末结束或开始,我们需要将它们移动到下一个星期一,我们可以使用帮助函数来执行:

from datetime import timedelta

def helper(d):
   if d.weekday() == 5:
        d += timedelta(days=1)
    return d.replace(hour=0, minute=0, second=0, microsecond=0)

def diff(s, e):
    if e.weekday() in {5, 6}:
        e = helper(e)
    if s.weekday() in {5, 6}:
        s = helper(s)
    _diff = (e - s)
    while s < e:
        if s.weekday() in {5, 6}:
            _diff -= timedelta(days=1)
        elif s.weekday() == 0:
            s += timedelta(days=4)
        s += timedelta(days=1)
    return timedelta(seconds=_diff.total_seconds())

仍然跑得快一点:

In [57]: timeit time_between_two_dates_except_weekends(start_date,end_date)
10 loops, best of 3: 95.5 ms per loop

In [58]: timeit diff(start_date,end_date)
100 loops, best of 3: 12.4 ms per loop

In [59]: diff(start_date,end_date)
Out[59]: datetime.timedelta(7699, 9300)

In [60]:  time_between_two_dates_except_weekends(start_date,end_date)
Out[60]: datetime.timedelta(7699, 9300)

只是做数学:

from datetime import timedelta, datetime

def helper(d):
    if d.weekday() == 5:
        d += timedelta(days=1)
    return d.replace(hour=0, minute=0, second=0, microsecond=0)


def diff(s, e):
    weekend = {5, 6}
    both = e.weekday() in weekend and s.weekday() in weekend
    is_weekend = e.weekday() in {5, 6} or s.weekday() in {5, 6}
    if e.weekday() in weekend:
        e = helper(e)
    if s.weekday() in weekend:
        s = helper(s)
    _diff = (e - s)
    wek = _diff.days / 7 * 2 + is_weekend - both
    if s.weekday() > e.weekday() and not is_weekend:
        wek += 2
    return timedelta(seconds=_diff.total_seconds()) - timedelta(wek)

运行速度要快得多:

In [2]: start_date = datetime(2016, 02, 29, 21, 25, 0)

In [3]: end_date = datetime(2045, 9, 02, 03, 56, 0)

In [4]: timeit diff(start_date,end_date)
100000 loops, best of 3: 6.8 µs per loop

In [5]: diff(start_date,end_date)
Out[5]: datetime.timedelta(7699, 9300)