Bug report
Bug description
In the pure-Python datetime implementation (Lib/_pydatetime.py),
datetime.time.strftime() raises AttributeError for the directives %Y, %G,
%C and %F, while the C accelerator returns the correct values. A time has no
date, so strftime fills the 1900-01-01 placeholder, and these directives should
format that placeholder.
>>> import _pydatetime # pure implementation >>> _pydatetime.time(12, 30).strftime('%Y') Traceback (most recent call last): ... File ".../Lib/_pydatetime.py", line 276, in _wrap_strftime object.year < 1000 and _need_normalize_century()): AttributeError: 'time' object has no attribute 'year'
The C accelerator is correct:
>>> import datetime # C accelerator >>> datetime.time(12, 30).strftime('%Y') '1900' >>> datetime.time(12, 30).strftime('%C') '19' >>> datetime.time(12, 30).strftime('%F') '1900-01-01' >>> datetime.time(12, 30).strftime('%G') '1900'
Date-only directives such as %j already agree between the two implementations
(both give '001').
Root cause
_wrap_strftime(object, format, timetuple) reads object.year in the
year-normalization branch:
elif ((ch in 'YG' or ch in 'FC') and object.year < 1000 and _need_normalize_century()): ... year = object.year
time.strftime passes the time object as object (it has no .year) together
with the fixed placeholder timetuple whose year is 1900. Because
object.year < 1000 is evaluated first in the and chain (before
_need_normalize_century()), the AttributeError is raised on every platform,
including ones where _need_normalize_century() is False.
The year actually being formatted is timetuple[0], which equals 1900 for a
time and equals object.year for date/datetime. Using timetuple[0] fixes
time and leaves date/datetime unchanged.
Regression
Introduced by gh-120713 (the %Y/%G normalization branch) and widened to %F
and %C by gh-122272.
Affected versions
3.13+ (pure-Python implementation). The C accelerator is not affected.