Невозможно воссоздать дату окончания с dateutil родственником - PullRequest
0 голосов
/ 28 ноября 2018

Я использую dateutil.relativedelta для вычисления возраста.Я проверял результат, когда выяснилось, что в некоторых случаях он неверен.Эта проблема возникает, только если день начальной даты равен 1, а конец месяца - 31, но не для всех этих месяцев!Любопытно, что результаты верны за январь и август.Я уверен, что чего-то не хватает, но не могу точно определить, что и где.

Я использую Python 2.7.10 на Win32 и dateutil 2.7.5 (но та же проблема с 2.6.1)

код для запуска:

from __future__ import print_function
import datetime
from dateutil.relativedelta import relativedelta

# Main function -- later on, its result is used in dateutil.rrule
# (all irrelevant code expurged)
def age(start, end, includeFirstDay=False):
    # includeFirstDay: whether to include the start date in the result
    if includeFirstDay:
        start += relativedelta(days=-1)
    # Documentation tells nothing about the order of arguments when the first
    # two are dates. The following seems correct.
    return relativedelta(end, start)

# Utility in external module
def eomonth(dt):
    # from the beginning of month, add one month and substract one day
    eom = dt + relativedelta(days=-dt.day+1) + relativedelta(months=+1, days=-1)
    return eom

test_values = [
    # the 3 following lines fail
    (datetime.date(2018, 10, 1), datetime.date(2018, 10, 31)),
    (datetime.date(2018, 10, 1), datetime.date(2018, 11, 1)+relativedelta(days=-1)),
    (datetime.date(2018, 10, 1), eomonth(datetime.date(2018, 10, 1))),
    # the following lines pass
    (datetime.date(2018, 10, 5), eomonth(datetime.date(2018, 10, 5))),
    (datetime.date(2016, 2, 1), datetime.date(2016, 2, 29)),
    (datetime.date(2016, 2, 1), datetime.date(2016, 3, 1)+relativedelta(days=-1)),
    (datetime.date(2016, 2, 1), eomonth(datetime.date(2016, 2, 1))),
    ]

def test(start, end, includeFirstDay=False):
    rd = age(start, end, includeFirstDay=includeFirstDay)
    # calculate end date from age
    d = rd.days + (-1 if includeFirstDay else 0)
    fin = start + relativedelta(years=rd.years, months=rd.months, days=d)
    if fin != end:  # i.e. AssertionError
        print('expected %s, got %s (includeFirstDay=%s)' %(end, fin, includeFirstDay))

for start, end in test_values:
    test(start, end, includeFirstDay=False)
    test(start, end, includeFirstDay=True)

# trying with all months in a year

def make_test(year):
    t = []
    for month in range(1, 13):
        start = datetime.date(year, month, 1)
        # Three ways to find the end of the month
        t.append((start, eomonth(start)))
        t.append((start, start + relativedelta(months=+1, days=-1)))  # only if start day == 1
        for eom in [31, 30, 29, 28]:  # brute force
            try:
                t.append((start, datetime.date(year, month, eom)))
                break
            except:
                pass
    return sorted(list(set(t)))

from pprint import pprint

print('\n*** testing 2016 (leap year)')
test_values = make_test(2016)
pprint(test_values)  # verify ends of months are correct
for start, end in test_values:
    test(start, end, includeFirstDay=False)
    test(start, end, includeFirstDay=True)

print('\n*** testing 2017')
test_values = make_test(2017)
pprint(test_values)  # verify ends of months are correct
for start, end in test_values:
    test(start, end, includeFirstDay=False)
    test(start, end, includeFirstDay=True)

Вывод:

expected 2018-10-31, got 2018-11-01 (includeFirstDay=True)
expected 2018-10-31, got 2018-11-01 (includeFirstDay=True)
expected 2018-10-31, got 2018-11-01 (includeFirstDay=True)

*** testing 2016 (leap year)
[(datetime.date(2016, 1, 1), datetime.date(2016, 1, 31)),
 (datetime.date(2016, 2, 1), datetime.date(2016, 2, 29)),
 (datetime.date(2016, 3, 1), datetime.date(2016, 3, 31)),
 (datetime.date(2016, 4, 1), datetime.date(2016, 4, 30)),
 (datetime.date(2016, 5, 1), datetime.date(2016, 5, 31)),
 (datetime.date(2016, 6, 1), datetime.date(2016, 6, 30)),
 (datetime.date(2016, 7, 1), datetime.date(2016, 7, 31)),
 (datetime.date(2016, 8, 1), datetime.date(2016, 8, 31)),
 (datetime.date(2016, 9, 1), datetime.date(2016, 9, 30)),
 (datetime.date(2016, 10, 1), datetime.date(2016, 10, 31)),
 (datetime.date(2016, 11, 1), datetime.date(2016, 11, 30)),
 (datetime.date(2016, 12, 1), datetime.date(2016, 12, 31))]
expected 2016-03-31, got 2016-04-02 (includeFirstDay=True)
expected 2016-05-31, got 2016-06-01 (includeFirstDay=True)
expected 2016-07-31, got 2016-08-01 (includeFirstDay=True)
expected 2016-10-31, got 2016-11-01 (includeFirstDay=True)
expected 2016-12-31, got 2017-01-01 (includeFirstDay=True)

*** testing 2017
[(datetime.date(2017, 1, 1), datetime.date(2017, 1, 31)),
 (datetime.date(2017, 2, 1), datetime.date(2017, 2, 28)),
 (datetime.date(2017, 3, 1), datetime.date(2017, 3, 31)),
 (datetime.date(2017, 4, 1), datetime.date(2017, 4, 30)),
 (datetime.date(2017, 5, 1), datetime.date(2017, 5, 31)),
 (datetime.date(2017, 6, 1), datetime.date(2017, 6, 30)),
 (datetime.date(2017, 7, 1), datetime.date(2017, 7, 31)),
 (datetime.date(2017, 8, 1), datetime.date(2017, 8, 31)),
 (datetime.date(2017, 9, 1), datetime.date(2017, 9, 30)),
 (datetime.date(2017, 10, 1), datetime.date(2017, 10, 31)),
 (datetime.date(2017, 11, 1), datetime.date(2017, 11, 30)),
 (datetime.date(2017, 12, 1), datetime.date(2017, 12, 31))]
expected 2017-03-31, got 2017-04-03 (includeFirstDay=True)
expected 2017-05-31, got 2017-06-01 (includeFirstDay=True)
expected 2017-07-31, got 2017-08-01 (includeFirstDay=True)
expected 2017-10-31, got 2017-11-01 (includeFirstDay=True)
expected 2017-12-31, got 2018-01-01 (includeFirstDay=True)

Более того, даты окончания марта даже неправильнее, если это возможно (и слово существует).

Если бы кто-то мог просветить меня, это было бы высоко оценено.Заранее спасибо.

1 Ответ

0 голосов
/ 29 ноября 2018

На самом деле, я делаю что-то не так.Причины, по которым январь и август ведут себя «нормально»: для этих месяцев у предыдущих месяцев также есть 31 день.Причина, по которой март выглядит для меня странно: февраль на 2 или 3 дня меньше, чем 31 день

Например:

start = datetime.date(2018, 3, 1)
end = start + relativedelta(months=+1, days=-1)
a = age(start, end, True)
print 'start:', start
print 'end  :', end
print 'age  :', a

Вывод:

start: 2018-03-01
end  : 2018-03-31
age  : relativedelta(months=+1, days=+3)

Я ожидалчто-то вроде относительные (дни = + 31).

В зависимости от того, как указаны даты начала и окончания, относительная дельта возвращает разные результаты.Например:

relativedelta(datetime.date(2018, 4, 1), datetime.date(2018, 3, 1))

дает

relativedelta(months=+1)

и

relativedelta(datetime.date(2018, 3, 31), datetime.date(2018, 2, 28))

дает

relativedelta(months=+1, days=+3)

Независимо от того, как это сделано, разницамежду начальной и конечной датами всегда должно быть равно 31 дню.

Мой вывод состоит в том, чтобы не использовать относительную дельту для вычисления возраста для отдельных месяцев между 1-м и последним днем ​​(включая все дни), когда наступила одна из дат

Или измените функцию age (), что я, вероятно, сделаю.

EDIT

В недалеком будущем яЯ удалю этот вопрос, потому что я думаю, что этот вопрос заслуживает включения в документацию по dateutil в категории "угловые случаи".

...