Почему `datetime.strptime` получает неверную дату во вторник на неделе 0 2015 года?
Я нашел ошибку в функции python datetime.strptime
.
Я создал базу объектов datetime
на номер недели (%W
), год (%Y
) и день недели (%W
). Дата вторника в первую неделю 2015 года неверна:
>>> from datetime import datetime
>>> datetime.strptime('%s %s %s' % (0, 2015, 1), '%W %Y %w').date()
datetime.date(2014, 12, 29) # OK
>>> datetime.strptime('%s %s %s' % (0, 2015, 2), '%W %Y %w').date()
datetime.date(2015, 1, 1) # WRONG !!!
>>> datetime.strptime('%s %s %s' % (0, 2015, 3), '%W %Y %w').date()
datetime.date(2014, 12, 31) # OK
>>> datetime.strptime('%s %s %s' % (0, 2015, 4), '%W %Y %w').date()
datetime.date(2015, 1, 1) # OK
>>> datetime.strptime('%s %s %s' % (0, 2015, 5), '%W %Y %w').date()
datetime.date(2015, 1, 2) # OK
>>> datetime.strptime('%s %s %s' % (0, 2015, 6), '%W %Y %w').date()
datetime.date(2015, 1, 3) # OK
>>> datetime.strptime('%s %s %s' % (0, 2015, 0), '%W %Y %w').date()
datetime.date(2015, 1, 4) # OK
Что мне делать с этой информацией?
Ответы
Ответ 1
Я смотрел больше лет, и у меня такое же загадочное поведение, но я нашел некоторую логику.
После чтения документов, я понимаю это немного лучше:
% W - номер недели года (понедельник как первый день недели) в виде десятичного числа. Все дни в новом году, предшествующие первому понедельнику, считаются на неделе 0.
Итак, %W
заполняет только правильные значения в неделю 0 для дней в новом году! Это полностью соответствует следующим результатам:
2015
>>> for i in range(7):
... datetime.strptime('%s %s %s' % (0, 2015, i), '%W %Y %w').date()
...
datetime.date(2015, 1, 4)
datetime.date(2014, 12, 29)
datetime.date(2015, 1, 1)
datetime.date(2014, 12, 31)
datetime.date(2015, 1, 1) # start of year
datetime.date(2015, 1, 2)
datetime.date(2015, 1, 3)
2016
>>> for i in range(7):
... datetime.strptime('%s %s %s' % (0, 2016, i), '%W %Y %w').date()
...
datetime.date(2016, 1, 3)
datetime.date(2015, 12, 28)
datetime.date(2015, 12, 29)
datetime.date(2016, 1, 1)
datetime.date(2015, 12, 31)
datetime.date(2016, 1, 1) # start of year
datetime.date(2016, 1, 2)
2017
>>> for i in range(7):
... datetime.strptime('%s %s %s' % (0, 2017, i), '%W %Y %w').date()
...
datetime.date(2017, 1, 1)
datetime.date(2016, 12, 26)
datetime.date(2016, 12, 27)
datetime.date(2016, 12, 28)
datetime.date(2016, 12, 29)
datetime.date(2017, 1, 1)
datetime.date(2016, 12, 31)
# ... start of year
2018
>>> for i in range(7):
... datetime.strptime('%s %s %s' % (0, 2018, i), '%W %Y %w').date()
...
datetime.date(2018, 1, 7)
datetime.date(2018, 1, 1) # start of year
datetime.date(2018, 1, 2)
datetime.date(2018, 1, 3)
datetime.date(2018, 1, 4)
datetime.date(2018, 1, 5)
datetime.date(2018, 1, 6)
Итак, после начала года поведение кажется предсказуемым и согласуется с документами.
Ответ 2
Я смог подтвердить, что это ошибка. Я изучил модуль _strptime.py
и могу подтвердить его краевое условие, как он обрабатывает вычисления юлианских дат.
Проблема связана с тем, что вызовы _calc_julian_from_U_or_W()
могут возвращать -1, что при нормальных обстоятельствах недействительно. Функция strptime()
проверяет и корректирует, когда значения julian равны -1... но это НЕ должно делать это, когда week_of_year равно нулю.
BTW: Тот факт, что он тестирует ТОЛЬКО -1, является причиной того, что вы видите проблему в 2015 году. Это условие существует только в том случае, если первый день года ровно на два дня раньше даты, на которую вы тестируете.
Следующий патч исправляет условие края
--- _strptime.py.orig 2014-12-30 15:47:05.069835336 -0500
+++ _strptime.py 2014-12-30 15:47:21.509139500 -0500
@@ -441,7 +441,7 @@
# Cannot pre-calculate datetime_date() since can change in Julian
# calculation and thus could have different value for the day of the week
# calculation.
- if julian == -1:
+ if julian == -1 and week_of_year != 0:
# Need to add 1 to result since first day of the year is 1, not 0.
julian = datetime_date(year, month, day).toordinal() - \
datetime_date(year, 1, 1).toordinal() + 1
Я применил этот патч к своей локальной машине, и теперь я вижу, что я считаю, что OP хотел:
>>> datetime.strptime('%s %s %s' % (0, 2015, 2), '%W %Y %w').date()
datetime.date(2014, 12, 30)
Отправленный отчет об ошибке http://bugs.python.org/issue23136