classification
Title: datetime.strptime without a year fails on Feb 29
Type: enhancement Stage:
Components: Library (Lib) Versions: Python 3.6
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Sriram Rajagopalan, belopolsky, gregory.p.smith, polymorphm
Priority: normal Keywords:

Created on 2016-02-29 18:02 by Sriram Rajagopalan, last changed 2016-03-02 20:43 by polymorphm.

Messages (5)
msg261014 - (view) Author: Sriram Rajagopalan (Sriram Rajagopalan) Date: 2016-02-29 18:02
$ 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

The same issue is seen in all versions of Python
msg261024 - (view) Author: Gregory P. Smith (gregory.p.smith) * (Python committer) Date: 2016-02-29 21:26
Python's time.strptime() behavior is consistent with that of glibc 2.19:

======= strptime_c.c =======
#define _XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int
main(void)
{
  struct tm tm;
  char buf[255];

  memset(&tm, 0, sizeof(struct tm));
  strptime("Feb 29", "%b %d", &tm);
  strftime(buf, sizeof(buf), "%d %b %Y %H:%M", &tm);
  puts(buf);
  exit(EXIT_SUCCESS);
}
=======

$ gcc strptime_c.c 
$ ./a.out
29 Feb 1900 00:00


I'm not saying that the behavior is a good API, but given the unfortunate API at hand, parsing a date without specifying what year it is using strptime is a bad idea.
msg261027 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2016-02-29 22:51
This is not no more bug than

>>> from datetime import *
>>> datetime.strptime('0228', '%m%d')
datetime.datetime(1900, 2, 28, 0, 0)

Naturally, as long as datetime.strptime('0228', '%m%d') is the same as datetime.strptime('19000228', '%Y%m%d'), datetime.strptime('0229', '%m%d') should raise a ValueError as long as datetime.strptime('19000229', '%m%d') does.

The only improvement, I can think of in this situation is to point the user to time.strptime() in the error message.  The time.strptime method works just fine in the recent versions (see issue 14157.)

>>> time.strptime('0229', '%m%d')[1:3]
(2, 29)
msg261028 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2016-02-29 22:54
> Python's time.strptime() behavior is consistent with that of glibc 2.19

Gregory,

I believe OP is complaining about the way datetime.datetime.strptime() behaves, not time.strptime() which is mentioned as (preferred?) alternative.


See msg261015  in issue 14157 for context.
msg261033 - (view) Author: Gregory P. Smith (gregory.p.smith) * (Python committer) Date: 2016-02-29 23:44
time.strptime() is "working" (not raising an exception) as it appears not to validate the day of the month when a year is not specified, yet the return value from either of these APIs is a date which has no concept of an ambiguous year.

## Via the admittedly old Python 2.7.6 from Ubuntu 14.04: ##
# 1900 was not a leap year as it is not divisible by 400.
>>> time.strptime("1900 Feb 29", "%Y %b %d")
ValueError: day is out of range for month
>>> 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)

So what should the validation behavior be?

>>> datetime.datetime.strptime("Feb 29", "%b %d")
ValueError: day is out of range for month
>>> datetime.datetime.strptime("2016 Feb 29", "%Y %b %d")
datetime.datetime(2016, 2, 29, 0, 0)
>>> datetime.datetime.strptime("1900 Feb 29", "%Y %b %d")
ValueError: day is out of range for month
>>> datetime.datetime(year=1900, month=2, day=29)
ValueError: day is out of range for month

datetime objects cannot be constructed with the invalid date (as the time.strptime return value allows).

Changing the API to assume the current year or a +/- 6 months from "now" when no year is parsed is likely to break existing code.
History
Date User Action Args
2016-03-02 20:43:07polymorphmsetnosy: + polymorphm
2016-02-29 23:44:22gregory.p.smithsetmessages: + msg261033
2016-02-29 22:54:41belopolskysetmessages: + msg261028
2016-02-29 22:51:30belopolskysetversions: - Python 2.7, Python 3.2, Python 3.3, Python 3.4, Python 3.5
nosy: + belopolsky

messages: + msg261027

type: behavior -> enhancement
2016-02-29 21:26:33gregory.p.smithsetnosy: + gregory.p.smith
messages: + msg261024
2016-02-29 18:02:35Sriram Rajagopalancreate