2016-07-12 5 views
1

Я новичок в Python, а также dateutil модуль. Я передаю следующие аргументы:dateutils rrule возвращает даты, что на 2 месяца друг от друга

disclosure_start_date = resultsDict['fd_disclosure_start_date'] 
disclosure_end_date = datetime.datetime.now() 
disclosure_dates = [dt for dt in rrule(MONTHLY, dtstart=disclosure_start_date, until=disclosure_end_date)] 

Здесь , который преобразуется в DateTime является datetime.datetime(2012, 10, 31, 0, 0)

дата окончания, как сейчас.

Когда я использую:

disclosure_dates = [dt for dt in rrule(MONTHLY, dtstart=disclosure_start_date, until=disclosure_end_date)] 

Я получаю даты для любой другой месяц или 2 месяца друг от друга. Результат:

>>> list(disclosure_dates) 
[datetime.datetime(2012, 10, 31, 0, 0), 
datetime.datetime(2012, 12, 31, 0, 0), 
datetime.datetime(2013, 1, 31, 0, 0), 
datetime.datetime(2013, 3, 31, 0, 0), 
datetime.datetime(2013, 5, 31, 0, 0), 
datetime.datetime(2013, 7, 31, 0, 0), 
datetime.datetime(2013, 8, 31, 0, 0), 
datetime.datetime(2013, 10, 31, 0, 0), 
datetime.datetime(2013, 12, 31, 0, 0), 
datetime.datetime(2014, 1, 31, 0, 0), 
datetime.datetime(2014, 3, 31, 0, 0), 
datetime.datetime(2014, 5, 31, 0, 0), 
datetime.datetime(2014, 7, 31, 0, 0), 
datetime.datetime(2014, 8, 31, 0, 0), 
datetime.datetime(2014, 10, 31, 0, 0), 
datetime.datetime(2014, 12, 31, 0, 0), 
datetime.datetime(2015, 1, 31, 0, 0), 
datetime.datetime(2015, 3, 31, 0, 0), 
datetime.datetime(2015, 5, 31, 0, 0), 
datetime.datetime(2015, 7, 31, 0, 0), 
datetime.datetime(2015, 8, 31, 0, 0), 
datetime.datetime(2015, 10, 31, 0, 0), 
datetime.datetime(2015, 12, 31, 0, 0), 
datetime.datetime(2016, 1, 31, 0, 0), 
datetime.datetime(2016, 3, 31, 0, 0), 
datetime.datetime(2016, 5, 31, 0, 0)] 

Я не уверен, что я делаю неправильно. Может кто-нибудь, пожалуйста, укажет на ошибку здесь?

ответ

2

Проблема, с которой вы сталкиваетесь, связана с тем фактом, что datetime.datetime(2012, 10, 31, 0, 0) является 31-м месяца, и не все месяцы имеют 31-й. Поскольку rrule модуля является реализацией RFC 2445. Пер RFC 3.3.10:

правил Рекуррентного может генерировать рекуррентные экземпляры с недействительной датой (например, 30 февраля) или несуществующего по местному время (например, 1:30 AM в день, когда местное время перемещается вперед на час в 1:00 утра). Такие экземпляры повторения ДОЛЖНЫ быть проигнорированы и НЕ ДОЛЖНЫ считаться частью набора повторений.

Поскольку у вас есть ежемесячное правило, которое генерирует 31-е число месяца, оно пропустит все месяцы с 30 или менее днями. Вы можете увидеть this bug report в dateutil об этой проблеме.

Если вы просто хотите, в последний день месяца, вы должны использовать bymonthday=-1 аргумент:

from dateutil.rrule import rrule, MONTHLY 
from datetime import datetime 

disclosure_start_date = datetime(2012, 10, 31, 0, 0) 

rr = rrule(freq=MONTHLY, dtstart=disclosure_start_date, bymonthday=-1) 
# >>>rr.between(datetime(2013, 1, 1), datetime(2013, 5, 1)) 
# [datetime.datetime(2013, 1, 31, 0, 0), 
# datetime.datetime(2013, 2, 28, 0, 0), 
# datetime.datetime(2013, 3, 31, 0, 0), 
# datetime.datetime(2013, 4, 30, 0, 0)] 

К сожалению, я не думаю, что есть RFC-совместимый способ создания простого RRULE, что просто падает вернитесь к концу месяца if-and-only - если это необходимо (например, что вы делаете с 30 января - вам нужно вернуться в феврале, но вы не хотите использовать bymonthday=-2, потому что это даст вам 27 февраля, и т.д).

В качестве альтернативы, для простого ежемесячного правила, как это, лучший вариант, вероятно, просто использовать relativedelta, который делает падение назад до конца месяца:

from dateutil.relativedelta import relativedelta 
from datetime import datetime 

def disclosure_dates(dtstart, rd, dtend=None): 
    ii = 0 
    while True: 
     cdate = dtstart + ii*rd 
     ii += 1 

     yield cdate 
     if dtend is not None and cdate >= dtend: 
      break 


dtstart = datetime(2013, 1, 31, 0, 0) 
rd = relativedelta(months=1) 
rr = disclosure_dates(dtstart, rd, dtend=datetime(2013, 5, 1)) 

# >>> list(rr) 
# [datetime.datetime(2013, 1, 31, 0, 0), 
# datetime.datetime(2013, 2, 28, 0, 0), 
# datetime.datetime(2013, 3, 31, 0, 0), 
# datetime.datetime(2013, 4, 30, 0, 0), 
# datetime.datetime(2013, 5, 31, 0, 0)] 

Обратите внимание, что я специально используется cdate = dtstart + ii * rd , вы не хотите просто сохранить «бегущая бирку», как это будет приколоть к кратчайшему месяц индикаторная видел:

dt_base = datetime(2013, 1, 31) 
dt = dt_base 
for ii in range(5): 
    cdt = dt_base + ii*rd 
    print('{} | {}'.format(dt, cdt)) 
    dt += rd 

Re Sult:

2013-01-31 00:00:00 | 2013-01-31 00:00:00 
2013-02-28 00:00:00 | 2013-02-28 00:00:00 
2013-03-28 00:00:00 | 2013-03-31 00:00:00 
2013-04-28 00:00:00 | 2013-04-30 00:00:00 
2013-05-28 00:00:00 | 2013-05-31 00:00:00 
+3

FYI, есть [RFC 7529] (https://tools.ietf.org/html/rfc7529), которое (помимо всего прочего) простирается 'RRULE' на режим, который падает назад к последний день месяца, если день не существует. Правило «FREQ = MONTHLY», RSCALE = GREGORIAN, SKIP = BACKWARD, BYMONTHDAY = 30' сделает это. В феврале это приведет к 28-му (или 29-м годам високосного года). К сожалению, пока не так много поддержки. – Marten

+0

@Marten Awesome, не знал об этом. Я буду читать и внедрять в dateutil. – Paul

+0

Спасибо, Пол. Подтвердил ваш ответ. – DrBug