Time between two dates except weekends

I need a function to calculate the time in seconds between two dates except weekends, something that will work as follows:

# 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)

I implemented my own function, which works, but seems unnecessarily huge and complex. I think it might be easier.

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

Is there any way to improve this?

UPD it turned out that not only my code is complicated, but it also returns an incorrect result in some cases (for example, when days off are passed either for the start or for the end date). I recommend just using the code from the correct answer below

+4
source share
2 answers
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)
+3

, :

import datetime
from dateutil.relativedelta import relativedelta, MO, SA

def time_between_two_dates_except_weekends(start_date, end_date):
    weekend = set([5, 6])

    if start_date.weekday() in weekend:
        start_date += relativedelta(weekday=MO(1))
        start_date = start_date.replace(hour=0,minute=0,second=0,microsecond=0)

    if end_date.weekday() in weekend:
        # One microsecond before Saturday at midnight.
        end_date = end_date + relativedelta(weekday=SA(-1)) - datetime.timedelta(microsecond=1)
        end_date = end_date.replace(hour=0,minute=0,second=0,microsecond=0) 

    number_of_weekends = (end_date - start_date).days / 7    
    if start_date.weekday() > end_date.weekday():
        number_of_weekends += 1

    return end_date - start_date -  datetime.timedelta(days=(number_of_weekends * 2)) 

start_date = datetime.datetime(2015, 8, 22, 14, 24, 29, 894810)
end_date = datetime.datetime.today()

print time_between_two_dates_except_weekends(start_date, end_date)

-, , , :

  • ( )

, .

, weekday() , weekday() , "", .

0

Source: https://habr.com/ru/post/1608572/


All Articles