classification
Title: time.strptime without a year fails on Feb 29
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.2, Python 3.3, Python 2.7
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: hynek Nosy List: Arfrever, Martin.Morrison, Sriram Rajagopalan, belopolsky, haypo, hynek, pconnell, pitrou, polymorphm, python-dev, swalker
Priority: normal Keywords: needs review, patch

Created on 2012-02-29 11:57 by Martin.Morrison, last changed 2016-02-29 18:04 by Sriram Rajagopalan. This issue is now closed.

Files
File name Uploaded Description Edit
strptime-on-leap-years.diff hynek, 2012-04-21 17:09 review
strptime-restore-1900.diff hynek, 2012-05-14 17:28 review
strptime-restore-1900-v2.diff hynek, 2012-05-14 17:35 review
Messages (21)
msg154621 - (view) Author: Martin Morrison (Martin.Morrison) Date: 2012-02-29 11:57
time.strptime without a year fails on Feb 29 with:

>>> time.strptime("Feb 29", "%b %d")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/_strptime.py", line 454, in _strptime_time
    return _strptime(data_string, format)[0]
  File "/usr/lib/python2.6/_strptime.py", line 440, in _strptime
    datetime_date(year, 1, 1).toordinal() + 1
ValueError: day is out of range for month

This is due to the use of "1900" as the default year when parsing. It would be nice to have an optional "defaults" keyword argument to the strptime function that can be used to override the defaults, thus allowing leap year dates to be parsed without specifying the date.

(Note: the code in question attempted to set the year *after* the parse so that ultimately there is a valid struct_time, but since the parse never succeeds, this can't work).
msg154642 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2012-02-29 15:48
This strikes me as an implementation artifact.  There is no reason for time.strptime() to validate date triplets.  Applications that require valid dates can use datetime.strptime().  I suggest changing time.strptime() specification to match POSIX strptime().  My understanding is that POSIX only requires field by field range checking (%d range 01 to 31, %m range 01 to 12) and not full structure validation.  This would be consistent with the way leap seconds are currently treated:

>>> time.strptime('60', '%S')[5]
60
msg154659 - (view) Author: Shawn (swalker) Date: 2012-02-29 18:54
I'm seeing this when a year *is* specified with Python 2.6 and 2.7:


import time
time.strptime("20090229T184823Z", "%Y%m%dT%H%M%SZ")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/_strptime.py", line 454, in _strptime_time
    return _strptime(data_string, format)[0]
  File "/usr/lib/python2.6/_strptime.py", line 440, in _strptime
    datetime_date(year, 1, 1).toordinal() + 1
ValueError: day is out of range for month


import datetime
datetime.datetime.strptime("20090229T184823Z", "%Y%m%dT%H%M%SZ")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/_strptime.py", line 440, in _strptime
    datetime_date(year, 1, 1).toordinal() + 1
ValueError: day is out of range for month
msg154661 - (view) Author: Shawn (swalker) Date: 2012-02-29 19:05
I'm an idiot; nevermind my comment.  The original date was bogus.
msg158207 - (view) Author: Hynek Schlawack (hynek) * (Python committer) Date: 2012-04-13 12:46
The point isn’t that time.strptime validates dates but that it uses datetime internally:

julian = datetime_date(year, month, day).toordinal() - \
                      datetime_date(year, 1, 1).toordinal() + 1

Is it worth to reimplement this functionality?  It strikes easier to me to just use a different year if year is undefined and date == Feb 29.
msg158926 - (view) Author: Hynek Schlawack (hynek) * (Python committer) Date: 2012-04-21 17:09
I gave it a shot, doesn’t look like a hack to me, what do you think?
msg159692 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2012-04-30 13:40
This is a bit of a hack, but seems to get the work done.  Does anyone have any objections to committing?
msg159736 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-04-30 23:06
Fine with me.
msg160358 - (view) Author: Roundup Robot (python-dev) Date: 2012-05-10 18:20
New changeset f2ea7505c0d7 by Antoine Pitrou in branch '3.2':
Issue #14157: Fix time.strptime failing without a year on February 29th.
http://hg.python.org/cpython/rev/f2ea7505c0d7

New changeset a5a254e8a291 by Antoine Pitrou in branch 'default':
Issue #14157: Fix time.strptime failing without a year on February 29th.
http://hg.python.org/cpython/rev/a5a254e8a291
msg160359 - (view) Author: Roundup Robot (python-dev) Date: 2012-05-10 18:23
New changeset 69d407b016c1 by Antoine Pitrou in branch '2.7':
Issue #14157: Fix time.strptime failing without a year on February 29th.
http://hg.python.org/cpython/rev/69d407b016c1
msg160360 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-05-10 18:24
Patch committed and pushed, thank you!
msg160637 - (view) Author: Martin Morrison (Martin.Morrison) Date: 2012-05-14 16:50
This solution has some very undesirable properties - namely that Mar 1st is now less than Feb 29th!

It seems like the correct follow up fix would be to adjust the date of the returned struct_time back to 1900. The struct_time object doesn't have the validation issue, so this works fine. This pair of fixes then nicely circumvents the intermediate datetime object's checking, while providing a consistent end result.
msg160638 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-05-14 16:54
That's a good point, thank you. Hynek, do you want to provide a new patch?
msg160639 - (view) Author: Hynek Schlawack (hynek) * (Python committer) Date: 2012-05-14 17:00
On it.

I wonder whether it causes trouble that we return an invalid time_struct down the road?
msg160644 - (view) Author: Hynek Schlawack (hynek) * (Python committer) Date: 2012-05-14 17:28
I have added a restoration including a short explanation + a regression test.
msg160646 - (view) Author: Hynek Schlawack (hynek) * (Python committer) Date: 2012-05-14 17:35
Small adjustments to the test as discussed in IRC.
msg160648 - (view) Author: Roundup Robot (python-dev) Date: 2012-05-14 17:50
New changeset 83598eb0d761 by Antoine Pitrou in branch '3.2':
Followup to issue #14157: respect the relative ordering of values produced by time.strptime().
http://hg.python.org/cpython/rev/83598eb0d761

New changeset d1c0b57aeb1b by Antoine Pitrou in branch 'default':
Followup to issue #14157: respect the relative ordering of values produced by time.strptime().
http://hg.python.org/cpython/rev/d1c0b57aeb1b

New changeset cbc9dc1c977e by Antoine Pitrou in branch '2.7':
Followup to issue #14157: respect the relative ordering of values produced by time.strptime().
http://hg.python.org/cpython/rev/cbc9dc1c977e
msg160649 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-05-14 17:51
Thanks, this should be fine now.
msg261002 - (view) Author: Andrei Antonov (polymorphm) * Date: 2016-02-29 10:32
$ python
    Python 3.5.1 (default, Dec  7 2015, 12:58:09) 
    [GCC 5.2.0] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> 
    >>> 
    >>> 
    >>> import time
    >>> 
    >>> time.strptime("Feb 29", "%b %d")
    time.struct_time(tm_year=1900, tm_mon=2, tm_mday=29, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=0, tm_yday=60, tm_isdst=-1)
    >>> 
    >>> 
    >>> import datetime
    >>> 
    >>> datetime.datetime.strptime("Feb 29", "%b %d")
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/usr/lib/python3.5/_strptime.py", line 511, in _strptime_datetime
        return cls(*args)
    ValueError: day is out of range for month
msg261005 - (view) Author: Sriram Rajagopalan (Sriram Rajagopalan) Date: 2016-02-29 11:45
datetime.strptime() uses the return value of _strptime() [ which returns 1900 for 29th Feb without an year ] and eventually ends up calling datetime_new()->check_date_args() [ datetimemodule.c ] with 29th Feb 1900 and eventual failure.

Should we enhance check_date_args to take a year_dont_care flag and validate the input year argument only if it is explicitly passed?
msg261015 - (view) Author: Sriram Rajagopalan (Sriram Rajagopalan) Date: 2016-02-29 18:04
Opened issue 26460 for fixing the leap day bug in datetime.datetime.strptime()
History
Date User Action Args
2016-02-29 18:04:45Sriram Rajagopalansetmessages: + msg261015
2016-02-29 11:45:24Sriram Rajagopalansetnosy: + Sriram Rajagopalan
messages: + msg261005
2016-02-29 10:32:27polymorphmsetnosy: + polymorphm
messages: + msg261002
2013-10-23 16:42:07pconnellsetnosy: + pconnell
2012-05-14 17:51:17pitrousetstatus: open -> closed
resolution: fixed
messages: + msg160649

stage: patch review -> resolved
2012-05-14 17:50:50python-devsetmessages: + msg160648
2012-05-14 17:35:04hyneksetfiles: + strptime-restore-1900-v2.diff
type: enhancement -> behavior
messages: + msg160646

assignee: hynek
resolution: fixed -> (no value)
stage: resolved -> patch review
2012-05-14 17:28:40hyneksetfiles: + strptime-restore-1900.diff

messages: + msg160644
2012-05-14 17:00:30hyneksetmessages: + msg160639
2012-05-14 16:54:39pitrousetstatus: closed -> open

messages: + msg160638
2012-05-14 16:50:16Martin.Morrisonsetmessages: + msg160637
2012-05-10 18:24:16pitrousetstatus: open -> closed
messages: + msg160360

assignee: belopolsky -> (no value)
resolution: fixed
stage: commit review -> resolved
2012-05-10 18:23:04python-devsetmessages: + msg160359
2012-05-10 18:20:56python-devsetnosy: + python-dev
messages: + msg160358
2012-04-30 23:06:29pitrousetnosy: + pitrou

messages: + msg159736
stage: patch review -> commit review
2012-04-30 13:40:47belopolskysetmessages: + msg159692
2012-04-30 13:29:39Arfreversetnosy: + Arfrever
2012-04-29 15:41:01hyneksetkeywords: + needs review
stage: needs patch -> patch review
2012-04-21 17:09:49hyneksetfiles: + strptime-on-leap-years.diff
keywords: + patch
messages: + msg158926
2012-04-13 12:46:46hyneksetmessages: + msg158207
2012-02-29 19:05:44swalkersetmessages: + msg154661
2012-02-29 18:54:24swalkersetnosy: + swalker
messages: + msg154659
2012-02-29 15:48:41belopolskysetassignee: belopolsky
type: behavior -> enhancement
messages: + msg154642
stage: needs patch
2012-02-29 14:40:09pitrousetnosy: + belopolsky, haypo, hynek

versions: + Python 3.2, Python 3.3
2012-02-29 11:57:47Martin.Morrisoncreate