GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — datetime.strptime without a year fails on Feb 29 · Issue #70647 · python/cpython

datetime.strptime without a year fails on Feb 29 #70647

New issue
Copy link
New issue
Copy link
Closed
Closed
datetime.strptime without a year fails on Feb 29#70647
Copy link
Assignees
StanFromIreland
Labels
docsDocumentation in the Doc dirDocumentation in the Doc dirstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement

Description

SriramRajagopalan
mannequin
opened on Feb 29, 2016
Issue body actions
BPO 26460
Nosy @gpshead, @abalkin, @pganssle, @tirkarthi, @nickzoic

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = None
created_at = <Date 2016-02-29.18:02:35.000>
labels = ['3.7', '3.8', 'type-feature', 'library']
title = 'datetime.strptime without a year fails on Feb 29'
updated_at = <Date 2020-03-03.17:16:20.440>
user = 'https://bugs.python.org/SriramRajagopalan'

bugs.python.org fields:

activity = <Date 2020-03-03.17:16:20.440>
actor = 'nickzoic'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['Library (Lib)']
creation = <Date 2016-02-29.18:02:35.000>
creator = 'Sriram Rajagopalan'
dependencies = []
files = []
hgrepos = []
issue_num = 26460
keywords = []
message_count = 13.0
messages = ['261014', '261024', '261027', '261028', '261033', '343085', '363123', '363202', '363215', '363217', '363223', '363257', '363280']
nosy_count = 8.0
nosy_names = ['gregory.p.smith', 'belopolsky', 'polymorphm', 'Sriram Rajagopalan', 'p-ganssle', 'xtreak', '[email protected]', 'nickzoic']
pr_nums = []
priority = 'normal'
resolution = None
stage = None
status = 'open'
superseder = None
type = 'enhancement'
url = 'https://bugs.python.org/issue26460'
versions = ['Python 3.6', 'Python 3.7', 'Python 3.8']

remaining TODO list:

  • turn %e into an error in 3.17.

Linked PRs

  • gh-70647: Better promote how to safely parse yearless dates in datetime. #116179
  • gh-70647: Add detail to error message when parsing a bad day of month. #116184
  • GH-70647: Deprecate strptime day of month parsing without a year present to avoid leap-year bugs #117107
  • gh-70647: Raise a more informative error for when date is out of range #131335
  • [3.12] gh-70647: update docs to mention the datetime 1900 year default 2/29 issue #131534
  • [3.14] gh-70647: Better promote how to safely parse yearless dates in datetime. (GH-116179) #143037
  • [3.13] gh-70647: Better promote how to safely parse yearless dates in datetime. (GH-116179) #143038
  • GH-70647: Remove support for %d (and deprecate for %e) without year in strptime() #144570

Activity

Next
  1. SriramRajagopalan commented on Feb 29, 2016

    @SriramRajagopalan
    SriramRajagopalanmannequin
    on Feb 29, 2016
    MannequinAuthor
    More actions
    $ 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

  2. SriramRajagopalan
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
    on Feb 29, 2016
  3. gpshead commented on Feb 29, 2016

    @gpshead
    gpshead
    on Feb 29, 2016
    Member
    More actions

    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.

  4. abalkin commented on Feb 29, 2016

    @abalkin
    abalkin
    on Feb 29, 2016
    Member
    More actions

    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', '%Y%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 bpo-14157.)

    >>> time.strptime('0229', '%m%d')[1:3]
    (2, 29)
  5. abalkin
    added
    type-featureA feature request or enhancementA feature request or enhancement
    and removed
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Feb 29, 2016
  6. abalkin commented on Feb 29, 2016

    @abalkin
    abalkin
    on Feb 29, 2016
    Member
    More actions

    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 bpo-14157 for context.

  7. gpshead commented on Feb 29, 2016

    @gpshead
    gpshead
    on Feb 29, 2016
    Member
    More actions

    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.

  8. tirkarthi commented on May 21, 2019

    @tirkarthi
    tirkarthi
    on May 21, 2019
    Member
    More actions

    See also bpo-19376. This behavior is now documented with 56027cc

  9. nickzoic commented on Mar 2, 2020

    @nickzoic
    nickzoicmannequin
    on Mar 2, 2020
    Mannequin
    More actions

    I suspect this is going to come up about this time of every leap year :-/

    The workaround is prepending "%Y " to the pattern and eg: "2020 " to the date string, but that's not very nice.

    Would adding a kwarg "default_year" be an acceptable solution?
    I can't think of any other situation other than leap years when this is going to come up. If both "default_year" and "%Y" are present throw an exception (maybe therefore just call the kwarg "year")

    In the weird case where you want to do date maths involving the month as well, you can always use a safe choice like "default_year=2020" and then fix the year up afterwards:

        dt = datetime.strptime(date_str, "%b %d", default_year=2020)
        dt = dt.replace(year=2021 if dt.month > 6 else 2022)
    
    Reacted by Gregory P. Smith and rysson
  10. nickzoic
    added
    3.7 (EOL)end of lifeend of life
    3.8 (EOL)end of lifeend of life
    on Mar 2, 2020
  11. pganssle commented on Mar 2, 2020

    @pganssle
    pganssle
    on Mar 2, 2020
    Member
    More actions

    I don't think adding a default_year parameter is the right solution here.

    The actual problem is that time.strptime, and by extension datetime.strptime has a strange and confusing interface. What should happen is either that year is set to None or some other marker of a missing value or datetime.strptime should raise an exception when it's being asked to construct something that does not contain a year.

    Since there is no concept of a partial datetime, I think our best option would be to throw an exception, except that this has been baked into the library for ages and would start to throw exceptions even when the person has correctly handled the Feb 29th case.

    I think one possible "solution" to this would be to raise a warning any time someone tries to use datetime.strptime without requesting a year to warn them that the thing they're doing only exists for backwards compatibility reasons. We could possibly eventually make that an exception, but I'm not sure it's totally worth a break in backwards compatibility when a warning should put people on notice.

  12. nickzoic commented on Mar 2, 2020

    @nickzoic
    nickzoicmannequin
    on Mar 2, 2020
    Mannequin
    More actions

    Not disagreeing with you that "%b %d" timestamps with no "%Y" are excerable, but they're fairly common in the *nix world unfortunately.

    People need to parse them, and the simple and obvious way to do this breaks every four years.

    I like the idea of having a warning for not including %Y *and* not setting a default_year kwarg though.

  13. 50 remaining items

  14. gpshead
    added a commit that references this issue on Apr 15, 2026

    GH-70647: Remove support for `%d` (and deprecate for `%e`) without ye…

    d0e7c6a
  15. gpshead commented on Apr 15, 2026

    @gpshead
    gpshead
    on Apr 15, 2026
    Member
    More actions

    remaining todo: in 3.17 turn %e usage without a year into an error.

  16. StanFromIreland commented on Apr 15, 2026

    @StanFromIreland
    StanFromIreland
    on Apr 15, 2026
    Member
    More actions

    remaining todo: in 3.17 turn %e usage without a year into an error.

    Let's make that a new issue, we won't be needing it for a while, as such, closing.

  17. StanFromIreland
    closed this as completedon Apr 15, 2026
  18. github-project-automation
    moved this from In Progress to Done in Date and time issues 🕰️on Apr 15, 2026
  19. ljfp
    added a commit that references this issue on Apr 25, 2026

    pythonGH-70647: Remove support for `%d` (and deprecate for `%e`) with…

    91f30ee
  20. nicfit
    added a commit that references this issue on May 26, 2026

    Fix Python 3.15 compatibility: strptime no longer allows %d without %Y (

    0efc904
  21. chriswmackey
    added 3 commits that reference this issue on Jul 14, 2026

    fix(sql): Implement workaround for Python datetime change

    95e21de

    fix(sql): Implement workaround for Python datetime change

    2a85be4

    fix(sql): Implement workaround for Python datetime change

    873dc2c
  22. andlaus
    added a commit that references this issue on Jul 27, 2026

    `plot`: make sure that parsing timestamps keeps working for python >=…

    9378cc0
  23. besendorf
    added a commit that references this issue on Oct 9, 2026

    Keep settings history timestamps on 29 February

    10f739d
  24. markmentovai
    added a commit that references this issue on Oct 21, 2026

    Fix _ftp_parse._parse_time for Python ≥ 3.15

    7287d26
  25. 2B-4G10
    added a commit that references this issue on Oct 28, 2026

    Keep settings history timestamps on 29 February

    e50e83c
  26. s0len
    added a commit that references this issue on Nov 1, 2026

    fix(matcher): parse partial dates with the reference year in place

    389b5fd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

  • @StanFromIreland
    StanFromIreland

Labels

docsDocumentation in the Doc dirDocumentation in the Doc dirstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement

Projects

  • Date and time issues 🕰️
    Status
    Done
    Show more project fields
  • docs issues
    Status
    Todo
    Show more project fields

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.