Нарушается ли правило ЕЖЕГОДНО для високосных дней?

Предположим, я хотел получить, когда праздновать дни рождения с помощью rrule. Тогда частота ЕЖЕГОДНО работает нормально, кроме високосных дней. Там он бывает только раз в 4 года.

Есть ли способ справиться с этим напрямую с помощью rrule?

from datetime import datetime
from dateutil.rrule import rrule, YEARLY

n = 1
print(list(rrule(freq=YEARLY, count=n + 1, dtstart=datetime(1990, 4, 28))))
print(list(rrule(freq=YEARLY, count=n + 1, dtstart=datetime(1992, 2, 29))))

дает

[datetime.datetime(1990, 4, 28, 0, 0), datetime.datetime(1991, 4, 28, 0, 0)]
[datetime.datetime(1992, 2, 29, 0, 0), datetime.datetime(1996, 2, 29, 0, 0)]

Тот факт, что високосные дни даже не упоминаются в документах, заставляет задуматься: это может быть просто ошибка.

погодудень

Это может помочь, но только на 28 февраля:

from datetime import datetime
from dateutil.rrule import rrule, YEARLY

n = 5

bday = datetime(1990, 4, 28)
print(list(rrule(freq=YEARLY,
                 byyearday=bday.timetuple().tm_yday,
                 count=n + 1,
                 dtstart=bday)))

bday = datetime(1992, 2, 29)
print(list(rrule(freq=YEARLY,
                 byyearday=bday.timetuple().tm_yday,
                 count=n + 1,
                 dtstart=bday)))

дает

[datetime.datetime(1990, 4, 28, 0, 0), datetime.datetime(1991, 4, 28, 0, 0), datetime.datetime(1992, 4, 27, 0, 0), datetime.datetime(1993, 4, 28, 0, 0), datetime.datetime(1994, 4, 28, 0, 0), datetime.datetime(1995, 4, 28, 0, 0)]
[datetime.datetime(1992, 2, 29, 0, 0), datetime.datetime(1993, 3, 1, 0, 0), datetime.datetime(1994, 3, 1, 0, 0), datetime.datetime(1995, 3, 1, 0, 0), datetime.datetime(1996, 2, 29, 0, 0), datetime.datetime(1997, 3, 1, 0, 0)]

person Martin Thoma    schedule 03.10.2018    source источник


Ответы (1)


Это сделано специально и на самом деле широко упоминается в документации rrule., в примечании, в котором говорится:

Согласно разделу 3.3.10 RFC, повторяющиеся экземпляры, попадающие в недопустимые даты и время, скорее игнорируются, чем принудительно выполняются:

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

Поскольку 29 февраля 1991 года никогда не существовало, это недопустимая дата и она пропускается.

Это ограничение устаревшего RFC 2445, который позже был заменен RFC 5545, обновленный RFC 7529. RFC 7529, среди прочего, добавляет в правила повторения параметр SKIP, который позволяет указать OMIT (по умолчанию), BACKWARD или FORWARD. dateutil предшествует RFC 7529 (и даже RFC 5545) и все еще находится в процессе обновления. Вы можете отслеживать ход выполнения ошибки № 285.

Эта конкретная проблема рассмотрена в PR #522, но в этом PR по-прежнему отсутствует поддержка одно резервное дело и не было объединено (по состоянию на октябрь 2018 г.).

Для простого случая функции, которая возвращает один и тот же день в каждом году, возвращаясь к последнему дню месяца, я рекомендую вместо этого использовать relativedelta (пока не будет выпущена версия с функцией SKIP):

from dateutil import relativedelta
from datetime import datetime

def yearly_rule(dtstart, count=None):
    n = 0
    while count is None or n < count:
        yield dtstart + relativedelta.relativedelta(years=n)
        n += 1

if __name__ == "__main__":
    for dt in yearly_rule(datetime(1992, 2, 29), count=5):
        print(dt)

    # Prints:
    # 1992-02-29 00:00:00
    # 1993-02-28 00:00:00
    # 1994-02-28 00:00:00
    # 1995-02-28 00:00:00
    # 1996-02-29 00:00:00

Обратите внимание, что я использую базовую дату и время (dtstart) в своем правиле, а не добавляю 1 год к предыдущему результату. Причина этого в том, что relativedelta имеет потери, поэтому добавление relativedelta(years=1) к datetime(1995, 2, 28) даст datetime(1996, 2, 28).

person Paul    schedule 03.10.2018
comment
А, это RFC-7529 - person Martin Thoma; 03.10.2018
comment
@MartinThoma Моя ошибка, я исходил из памяти о том, что было в 5545 и 7529. 7529 - это обновление для 5545, которое является заменой 2445. Я обновил сообщение. Спасибо! - person Paul; 03.10.2018