2015-09-22 2 views
4

мне нужна функция для расчета времени в секундах между двумя датами, за исключением выходных дней, то, что будет работать так:Время между двумя датами, кроме выходных

# 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. оказалось, что не только мой код сложный, но и неправильный результат в некоторых случаях (например, когда выходные дни передаются либо для начала, либо для даты окончания). Я рекомендую использовать только код из правильного ответа ниже

+4

Пожалуйста, примите это к группе обзора кода. – Prune

+2

Я голосую, чтобы закрыть этот вопрос не по теме, потому что он принадлежит к Code Review StackExchange. https://codereview.stackexchange.com –

+0

Вы можете сделать свой код короче, уменьшив проверку выходных дней в конце вашего кода, добавив параметр byweekday = range (5) в ваш rrule. – avenet

ответ

3
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) 
+0

отлично! точно, что я надеялся на – merthay

+0

На самом деле, поцарапайте это, что должно произойти, если день выходного дня пройдет либо для начала конца, должен ли он вызвать ошибку? –

+0

Я думаю, что 'end_date' должно быть' e', а 'start_date' должно быть' s'. – Travis

0

Я думаю, что это следует сделать трюк:

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() с даты начала, номер должен «обернуться», и наступит выходное время.

Смежные вопросы