From 4fce98a920f47504e834057cd6606bad9b591ea9 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 6 Mar 2026 10:23:11 +0100 Subject: [PATCH 1/3] gh-141510: Change marshal version to 6 (#145551) Fix SliceTestCase: test also that version 4 fails with ValueError. --- Doc/library/marshal.rst | 11 +++++++++-- Include/cpython/marshal.h | 2 +- Lib/test/test_marshal.py | 11 ++++++++++- .../2026-03-05-16-06-09.gh-issue-141510.dFPAQS.rst | 2 ++ Programs/_freeze_module.c | 2 +- Python/marshal.c | 6 ++++++ 6 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-03-05-16-06-09.gh-issue-141510.dFPAQS.rst diff --git a/Doc/library/marshal.rst b/Doc/library/marshal.rst index ed182ea24e8f3c..25902622b8730b 100644 --- a/Doc/library/marshal.rst +++ b/Doc/library/marshal.rst @@ -51,8 +51,9 @@ this module. The following types are supported: * Strings (:class:`str`) and :class:`bytes`. :term:`Bytes-like objects ` like :class:`bytearray` are marshalled as :class:`!bytes`. -* Containers: :class:`tuple`, :class:`list`, :class:`set`, :class:`frozenset`, - and (since :data:`version` 5), :class:`slice`. +* Containers: :class:`tuple`, :class:`list`, :class:`dict`, :class:`frozendict` + (since :data:`version` 6), :class:`set`, :class:`frozenset`, and + :class:`slice` (since :data:`version` 5). It should be understood that these are supported only if the values contained therein are themselves supported. Recursive containers are supported since :data:`version` 3. @@ -71,6 +72,10 @@ this module. The following types are supported: Added format version 5, which allows marshalling slices. +.. versionchanged:: next + + Added format version 6, which allows marshalling :class:`frozendict`. + The module defines these functions: @@ -173,6 +178,8 @@ In addition, the following constants are defined: 4 Python 3.4 Efficient representation of short strings ------- --------------- ---------------------------------------------------- 5 Python 3.14 Support for :class:`slice` objects + ------- --------------- ---------------------------------------------------- + 6 Python 3.15 Support for :class:`frozendict` objects ======= =============== ==================================================== diff --git a/Include/cpython/marshal.h b/Include/cpython/marshal.h index 6c1f7f96b6a2e8..159459fcaec3d9 100644 --- a/Include/cpython/marshal.h +++ b/Include/cpython/marshal.h @@ -6,7 +6,7 @@ PyAPI_FUNC(PyObject *) PyMarshal_ReadObjectFromString(const char *, Py_ssize_t); PyAPI_FUNC(PyObject *) PyMarshal_WriteObjectToString(PyObject *, int); -#define Py_MARSHAL_VERSION 5 +#define Py_MARSHAL_VERSION 6 PyAPI_FUNC(long) PyMarshal_ReadLongFromFile(FILE *); PyAPI_FUNC(int) PyMarshal_ReadShortFromFile(FILE *); diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py index 28f24d0fc59cb0..78db4219e2997c 100644 --- a/Lib/test/test_marshal.py +++ b/Lib/test/test_marshal.py @@ -570,6 +570,15 @@ def testDict(self): self.helper(dictobj) self.helper3(dictobj) + def testFrozenDict(self): + for obj in self.keys: + dictobj = frozendict({"hello": obj, "goodbye": obj, obj: "hello"}) + self.helper(dictobj) + + for version in range(6): + with self.assertRaises(ValueError): + marshal.dumps(dictobj, version) + def testModule(self): with open(__file__, "rb") as f: code = f.read() @@ -635,7 +644,7 @@ def test_slice(self): with self.subTest(obj=str(obj)): self.helper(obj) - for version in range(4): + for version in range(5): with self.assertRaises(ValueError): marshal.dumps(obj, version) diff --git a/Misc/NEWS.d/next/Library/2026-03-05-16-06-09.gh-issue-141510.dFPAQS.rst b/Misc/NEWS.d/next/Library/2026-03-05-16-06-09.gh-issue-141510.dFPAQS.rst new file mode 100644 index 00000000000000..280a7b3632ddae --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-05-16-06-09.gh-issue-141510.dFPAQS.rst @@ -0,0 +1,2 @@ +:mod:`marshal` now supports :class:`frozendict` objects. The marshal format +version was increased to 6. Patch by Victor Stinner. diff --git a/Programs/_freeze_module.c b/Programs/_freeze_module.c index a5809b37b6b493..27a60171f3eca8 100644 --- a/Programs/_freeze_module.c +++ b/Programs/_freeze_module.c @@ -134,7 +134,7 @@ compile_and_marshal(const char *name, const char *text) return NULL; } - assert(Py_MARSHAL_VERSION >= 5); + assert(Py_MARSHAL_VERSION >= 6); PyObject *marshalled = PyMarshal_WriteObjectToString(code, Py_MARSHAL_VERSION); Py_CLEAR(code); if (marshalled == NULL) { diff --git a/Python/marshal.c b/Python/marshal.c index a71909f103ebfc..59db6456552c35 100644 --- a/Python/marshal.c +++ b/Python/marshal.c @@ -580,6 +580,12 @@ w_complex_object(PyObject *v, char flag, WFILE *p) Py_ssize_t pos; PyObject *key, *value; if (PyFrozenDict_CheckExact(v)) { + if (p->version < 6) { + w_byte(TYPE_UNKNOWN, p); + p->error = WFERR_UNMARSHALLABLE; + return; + } + W_TYPE(TYPE_FROZENDICT, p); } else { From 349639cfa46180b18c2a3299db08cff253c7a959 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 6 Mar 2026 10:25:09 +0100 Subject: [PATCH 2/3] gh-141510: Use frozendict in the stdlib (#144909) Co-authored-by: Donghee Na --- Lib/functools.py | 4 +- Lib/gettext.py | 5 +- Lib/json/decoder.py | 4 +- Lib/json/tool.py | 4 +- Lib/opcode.py | 152 ++++++++++++++++++++++---------------------- Lib/optparse.py | 10 +-- Lib/platform.py | 8 +-- Lib/plistlib.py | 2 +- Lib/ssl.py | 3 +- Lib/symtable.py | 2 +- Lib/tarfile.py | 4 +- 11 files changed, 101 insertions(+), 97 deletions(-) diff --git a/Lib/functools.py b/Lib/functools.py index 9bc2ee7e8c894c..cd374631f16792 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -170,7 +170,7 @@ def _lt_from_ge(self, other): return op_result return not op_result -_convert = { +_convert = frozendict({ '__lt__': [('__gt__', _gt_from_lt), ('__le__', _le_from_lt), ('__ge__', _ge_from_lt)], @@ -183,7 +183,7 @@ def _lt_from_ge(self, other): '__ge__': [('__le__', _le_from_ge), ('__gt__', _gt_from_ge), ('__lt__', _lt_from_ge)] -} +}) def total_ordering(cls): """Class decorator that fills in missing ordering methods""" diff --git a/Lib/gettext.py b/Lib/gettext.py index 6c11ab2b1eb570..2f77f0e849e9ae 100644 --- a/Lib/gettext.py +++ b/Lib/gettext.py @@ -111,8 +111,9 @@ def _error(value): ('+', '-'), ('*', '/', '%'), ) -_binary_ops = {op: i for i, ops in enumerate(_binary_ops, 1) for op in ops} -_c2py_ops = {'||': 'or', '&&': 'and', '/': '//'} +_binary_ops = frozendict({op: i for i, ops in enumerate(_binary_ops, 1) + for op in ops}) +_c2py_ops = frozendict({'||': 'or', '&&': 'and', '/': '//'}) def _parse(tokens, priority=-1): diff --git a/Lib/json/decoder.py b/Lib/json/decoder.py index 92ad6352557640..4cd6f8367a1349 100644 --- a/Lib/json/decoder.py +++ b/Lib/json/decoder.py @@ -43,11 +43,11 @@ def __reduce__(self): return self.__class__, (self.msg, self.doc, self.pos) -_CONSTANTS = { +_CONSTANTS = frozendict({ '-Infinity': NegInf, 'Infinity': PosInf, 'NaN': NaN, -} +}) HEXDIGITS = re.compile(r'[0-9A-Fa-f]{4}', FLAGS) diff --git a/Lib/json/tool.py b/Lib/json/tool.py index 050c2fe2161e3e..e0b944b197d38b 100644 --- a/Lib/json/tool.py +++ b/Lib/json/tool.py @@ -22,13 +22,13 @@ (?Pnull) ''', re.VERBOSE) -_group_to_theme_color = { +_group_to_theme_color = frozendict({ "key": "definition", "string": "string", "number": "number", "boolean": "keyword", "null": "keyword", -} +}) def _colorize_json(json_str, theme): diff --git a/Lib/opcode.py b/Lib/opcode.py index f016b8dc4a50b2..165f42baed94e3 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -46,81 +46,81 @@ hascompare = [opmap["COMPARE_OP"]] -_cache_format = { - "LOAD_GLOBAL": { - "counter": 1, - "index": 1, - "module_keys_version": 1, - "builtin_keys_version": 1, - }, - "BINARY_OP": { - "counter": 1, - "descr": 4, - }, - "UNPACK_SEQUENCE": { - "counter": 1, - }, - "COMPARE_OP": { - "counter": 1, - }, - "CONTAINS_OP": { - "counter": 1, - }, - "FOR_ITER": { - "counter": 1, - }, - "LOAD_SUPER_ATTR": { - "counter": 1, - }, - "LOAD_ATTR": { - "counter": 1, - "version": 2, - "keys_version": 2, - "descr": 4, - }, - "STORE_ATTR": { - "counter": 1, - "version": 2, - "index": 1, - }, - "CALL": { - "counter": 1, - "func_version": 2, - }, - "CALL_KW": { - "counter": 1, - "func_version": 2, - }, - "CALL_FUNCTION_EX": { - "counter": 1, - }, - "STORE_SUBSCR": { - "counter": 1, - }, - "SEND": { - "counter": 1, - }, - "JUMP_BACKWARD": { - "counter": 1, - }, - "TO_BOOL": { - "counter": 1, - "version": 2, - }, - "POP_JUMP_IF_TRUE": { - "counter": 1, - }, - "POP_JUMP_IF_FALSE": { - "counter": 1, - }, - "POP_JUMP_IF_NONE": { - "counter": 1, - }, - "POP_JUMP_IF_NOT_NONE": { - "counter": 1, - }, -} +_cache_format = frozendict( + LOAD_GLOBAL=frozendict( + counter=1, + index=1, + module_keys_version=1, + builtin_keys_version=1, + ), + BINARY_OP=frozendict( + counter=1, + descr=4, + ), + UNPACK_SEQUENCE=frozendict( + counter=1, + ), + COMPARE_OP=frozendict( + counter=1, + ), + CONTAINS_OP=frozendict( + counter=1, + ), + FOR_ITER=frozendict( + counter=1, + ), + LOAD_SUPER_ATTR=frozendict( + counter=1, + ), + LOAD_ATTR=frozendict( + counter=1, + version=2, + keys_version=2, + descr=4, + ), + STORE_ATTR=frozendict( + counter=1, + version=2, + index=1, + ), + CALL=frozendict( + counter=1, + func_version=2, + ), + CALL_KW=frozendict( + counter=1, + func_version=2, + ), + CALL_FUNCTION_EX=frozendict( + counter=1, + ), + STORE_SUBSCR=frozendict( + counter=1, + ), + SEND=frozendict( + counter=1, + ), + JUMP_BACKWARD=frozendict( + counter=1, + ), + TO_BOOL=frozendict( + counter=1, + version=2, + ), + POP_JUMP_IF_TRUE=frozendict( + counter=1, + ), + POP_JUMP_IF_FALSE=frozendict( + counter=1, + ), + POP_JUMP_IF_NONE=frozendict( + counter=1, + ), + POP_JUMP_IF_NOT_NONE=frozendict( + counter=1, + ), +) -_inline_cache_entries = { +_inline_cache_entries = frozendict({ name : sum(value.values()) for (name, value) in _cache_format.items() -} +}) diff --git a/Lib/optparse.py b/Lib/optparse.py index 5ff7f74754f9c1..de1082442ef7f2 100644 --- a/Lib/optparse.py +++ b/Lib/optparse.py @@ -407,10 +407,12 @@ def _parse_num(val, type): def _parse_int(val): return _parse_num(val, int) -_builtin_cvt = { "int" : (_parse_int, _("integer")), - "long" : (_parse_int, _("integer")), - "float" : (float, _("floating-point")), - "complex" : (complex, _("complex")) } +_builtin_cvt = frozendict({ + "int": (_parse_int, _("integer")), + "long": (_parse_int, _("integer")), + "float": (float, _("floating-point")), + "complex": (complex, _("complex")), +}) def check_builtin(option, opt, value): (cvt, what) = _builtin_cvt[option.type] diff --git a/Lib/platform.py b/Lib/platform.py index 3a71b669985f13..9d7aa5c66a91cb 100644 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -127,7 +127,7 @@ # Based on the description of the PHP's version_compare(): # http://php.net/manual/en/function.version-compare.php -_ver_stages = { +_ver_stages = frozendict({ # any string not found in this dict, will get 0 assigned 'dev': 10, 'alpha': 20, 'a': 20, @@ -136,7 +136,7 @@ 'RC': 50, 'rc': 50, # number, will get 100 assigned 'pl': 200, 'p': 200, -} +}) def _comparable_version(version): @@ -705,11 +705,11 @@ def _syscmd_file(target, default=''): # Default values for architecture; non-empty strings override the # defaults given as parameters -_default_architecture = { +_default_architecture = frozendict({ 'win32': ('', 'WindowsPE'), 'win16': ('', 'Windows'), 'dos': ('', 'MSDOS'), -} +}) def architecture(executable=sys.executable, bits='', linkage=''): diff --git a/Lib/plistlib.py b/Lib/plistlib.py index cae38672f641b7..3c6a6b7bdc44d2 100644 --- a/Lib/plistlib.py +++ b/Lib/plistlib.py @@ -453,7 +453,7 @@ class InvalidFileException (ValueError): def __init__(self, message="Invalid file"): ValueError.__init__(self, message) -_BINARY_FORMAT = {1: 'B', 2: 'H', 4: 'L', 8: 'Q'} +_BINARY_FORMAT = frozendict({1: 'B', 2: 'H', 4: 'L', 8: 'Q'}) _undefined = object() diff --git a/Lib/ssl.py b/Lib/ssl.py index 612b32cd0765ec..896db17baeb3db 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -150,7 +150,8 @@ source=_ssl) PROTOCOL_SSLv23 = _SSLMethod.PROTOCOL_SSLv23 = _SSLMethod.PROTOCOL_TLS -_PROTOCOL_NAMES = {value: name for name, value in _SSLMethod.__members__.items()} +_PROTOCOL_NAMES = frozendict({ + value: name for name, value in _SSLMethod.__members__.items()}) _SSLv2_IF_EXISTS = getattr(_SSLMethod, 'PROTOCOL_SSLv2', None) diff --git a/Lib/symtable.py b/Lib/symtable.py index 45610fd5612995..c7152a70f5aa0b 100644 --- a/Lib/symtable.py +++ b/Lib/symtable.py @@ -414,7 +414,7 @@ def get_namespace(self): _flags = [('USE', USE)] _flags.extend(kv for kv in globals().items() if kv[0].startswith('DEF_')) _scopes_names = ('FREE', 'LOCAL', 'GLOBAL_IMPLICIT', 'GLOBAL_EXPLICIT', 'CELL') -_scopes_value_to_name = {globals()[n]: n for n in _scopes_names} +_scopes_value_to_name = frozendict({globals()[n]: n for n in _scopes_names}) def main(args): diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 75984bf8b262b9..7abda3653e764b 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -859,11 +859,11 @@ def data_filter(member, dest_path): return member.replace(**new_attrs, deep=False) return member -_NAMED_FILTERS = { +_NAMED_FILTERS = frozendict({ "fully_trusted": fully_trusted_filter, "tar": tar_filter, "data": data_filter, -} +}) #------------------ # Exported Classes From d3b6faf9758dce236d45c708a450437cdc3e97cd Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 6 Mar 2026 13:18:11 +0200 Subject: [PATCH 3/3] Docs: `import datetime as dt` in examples (#145315) --- Doc/includes/tzinfo_examples.py | 127 ++++++------- Doc/library/datetime.rst | 303 +++++++++++++++++--------------- 2 files changed, 224 insertions(+), 206 deletions(-) diff --git a/Doc/includes/tzinfo_examples.py b/Doc/includes/tzinfo_examples.py index 1fa6e615e46a76..762b1b62fc871d 100644 --- a/Doc/includes/tzinfo_examples.py +++ b/Doc/includes/tzinfo_examples.py @@ -1,68 +1,70 @@ -from datetime import tzinfo, timedelta, datetime - -ZERO = timedelta(0) -HOUR = timedelta(hours=1) -SECOND = timedelta(seconds=1) +import datetime as dt # A class capturing the platform's idea of local time. # (May result in wrong values on historical times in # timezones where UTC offset and/or the DST rules had # changed in the past.) -import time as _time +import time + +ZERO = dt.timedelta(0) +HOUR = dt.timedelta(hours=1) +SECOND = dt.timedelta(seconds=1) -STDOFFSET = timedelta(seconds = -_time.timezone) -if _time.daylight: - DSTOFFSET = timedelta(seconds = -_time.altzone) +STDOFFSET = dt.timedelta(seconds=-time.timezone) +if time.daylight: + DSTOFFSET = dt.timedelta(seconds=-time.altzone) else: DSTOFFSET = STDOFFSET DSTDIFF = DSTOFFSET - STDOFFSET -class LocalTimezone(tzinfo): - def fromutc(self, dt): - assert dt.tzinfo is self - stamp = (dt - datetime(1970, 1, 1, tzinfo=self)) // SECOND - args = _time.localtime(stamp)[:6] +class LocalTimezone(dt.tzinfo): + + def fromutc(self, when): + assert when.tzinfo is self + stamp = (when - dt.datetime(1970, 1, 1, tzinfo=self)) // SECOND + args = time.localtime(stamp)[:6] dst_diff = DSTDIFF // SECOND # Detect fold - fold = (args == _time.localtime(stamp - dst_diff)) - return datetime(*args, microsecond=dt.microsecond, - tzinfo=self, fold=fold) + fold = (args == time.localtime(stamp - dst_diff)) + return dt.datetime(*args, microsecond=when.microsecond, + tzinfo=self, fold=fold) - def utcoffset(self, dt): - if self._isdst(dt): + def utcoffset(self, when): + if self._isdst(when): return DSTOFFSET else: return STDOFFSET - def dst(self, dt): - if self._isdst(dt): + def dst(self, when): + if self._isdst(when): return DSTDIFF else: return ZERO - def tzname(self, dt): - return _time.tzname[self._isdst(dt)] + def tzname(self, when): + return time.tzname[self._isdst(when)] - def _isdst(self, dt): - tt = (dt.year, dt.month, dt.day, - dt.hour, dt.minute, dt.second, - dt.weekday(), 0, 0) - stamp = _time.mktime(tt) - tt = _time.localtime(stamp) + def _isdst(self, when): + tt = (when.year, when.month, when.day, + when.hour, when.minute, when.second, + when.weekday(), 0, 0) + stamp = time.mktime(tt) + tt = time.localtime(stamp) return tt.tm_isdst > 0 + Local = LocalTimezone() # A complete implementation of current DST rules for major US time zones. -def first_sunday_on_or_after(dt): - days_to_go = 6 - dt.weekday() +def first_sunday_on_or_after(when): + days_to_go = 6 - when.weekday() if days_to_go: - dt += timedelta(days_to_go) - return dt + when += dt.timedelta(days_to_go) + return when # US DST Rules @@ -75,21 +77,22 @@ def first_sunday_on_or_after(dt): # # In the US, since 2007, DST starts at 2am (standard time) on the second # Sunday in March, which is the first Sunday on or after Mar 8. -DSTSTART_2007 = datetime(1, 3, 8, 2) +DSTSTART_2007 = dt.datetime(1, 3, 8, 2) # and ends at 2am (DST time) on the first Sunday of Nov. -DSTEND_2007 = datetime(1, 11, 1, 2) +DSTEND_2007 = dt.datetime(1, 11, 1, 2) # From 1987 to 2006, DST used to start at 2am (standard time) on the first # Sunday in April and to end at 2am (DST time) on the last # Sunday of October, which is the first Sunday on or after Oct 25. -DSTSTART_1987_2006 = datetime(1, 4, 1, 2) -DSTEND_1987_2006 = datetime(1, 10, 25, 2) +DSTSTART_1987_2006 = dt.datetime(1, 4, 1, 2) +DSTEND_1987_2006 = dt.datetime(1, 10, 25, 2) # From 1967 to 1986, DST used to start at 2am (standard time) on the last # Sunday in April (the one on or after April 24) and to end at 2am (DST time) # on the last Sunday of October, which is the first Sunday # on or after Oct 25. -DSTSTART_1967_1986 = datetime(1, 4, 24, 2) +DSTSTART_1967_1986 = dt.datetime(1, 4, 24, 2) DSTEND_1967_1986 = DSTEND_1987_2006 + def us_dst_range(year): # Find start and end times for US DST. For years before 1967, return # start = end for no DST. @@ -100,17 +103,17 @@ def us_dst_range(year): elif 1966 < year < 1987: dststart, dstend = DSTSTART_1967_1986, DSTEND_1967_1986 else: - return (datetime(year, 1, 1), ) * 2 + return (dt.datetime(year, 1, 1), ) * 2 start = first_sunday_on_or_after(dststart.replace(year=year)) end = first_sunday_on_or_after(dstend.replace(year=year)) return start, end -class USTimeZone(tzinfo): +class USTimeZone(dt.tzinfo): def __init__(self, hours, reprname, stdname, dstname): - self.stdoffset = timedelta(hours=hours) + self.stdoffset = dt.timedelta(hours=hours) self.reprname = reprname self.stdname = stdname self.dstname = dstname @@ -118,45 +121,45 @@ def __init__(self, hours, reprname, stdname, dstname): def __repr__(self): return self.reprname - def tzname(self, dt): - if self.dst(dt): + def tzname(self, when): + if self.dst(when): return self.dstname else: return self.stdname - def utcoffset(self, dt): - return self.stdoffset + self.dst(dt) + def utcoffset(self, when): + return self.stdoffset + self.dst(when) - def dst(self, dt): - if dt is None or dt.tzinfo is None: + def dst(self, when): + if when is None or when.tzinfo is None: # An exception may be sensible here, in one or both cases. # It depends on how you want to treat them. The default # fromutc() implementation (called by the default astimezone() - # implementation) passes a datetime with dt.tzinfo is self. + # implementation) passes a datetime with when.tzinfo is self. return ZERO - assert dt.tzinfo is self - start, end = us_dst_range(dt.year) + assert when.tzinfo is self + start, end = us_dst_range(when.year) # Can't compare naive to aware objects, so strip the timezone from - # dt first. - dt = dt.replace(tzinfo=None) - if start + HOUR <= dt < end - HOUR: + # when first. + when = when.replace(tzinfo=None) + if start + HOUR <= when < end - HOUR: # DST is in effect. return HOUR - if end - HOUR <= dt < end: - # Fold (an ambiguous hour): use dt.fold to disambiguate. - return ZERO if dt.fold else HOUR - if start <= dt < start + HOUR: + if end - HOUR <= when < end: + # Fold (an ambiguous hour): use when.fold to disambiguate. + return ZERO if when.fold else HOUR + if start <= when < start + HOUR: # Gap (a non-existent hour): reverse the fold rule. - return HOUR if dt.fold else ZERO + return HOUR if when.fold else ZERO # DST is off. return ZERO - def fromutc(self, dt): - assert dt.tzinfo is self - start, end = us_dst_range(dt.year) + def fromutc(self, when): + assert when.tzinfo is self + start, end = us_dst_range(when.year) start = start.replace(tzinfo=self) end = end.replace(tzinfo=self) - std_time = dt + self.stdoffset + std_time = when + self.stdoffset dst_time = std_time + HOUR if end <= dst_time < end + HOUR: # Repeated hour diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index ebe3c3576c0979..73217136f14472 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -230,8 +230,8 @@ A :class:`timedelta` object represents a duration, the difference between two *days*, *seconds* and *microseconds* are "merged" and normalized into those three resulting attributes:: - >>> from datetime import timedelta - >>> delta = timedelta( + >>> import datetime as dt + >>> delta = dt.timedelta( ... days=50, ... seconds=27, ... microseconds=10, @@ -244,6 +244,12 @@ A :class:`timedelta` object represents a duration, the difference between two >>> delta datetime.timedelta(days=64, seconds=29156, microseconds=10) + .. tip:: + ``import datetime as dt`` instead of ``import datetime`` or + ``from datetime import datetime`` to avoid confusion between the module + and the class. See `How I Import Python’s datetime Module + `__. + If any argument is a float and there are fractional microseconds, the fractional microseconds left over from all arguments are combined and their sum is rounded to the nearest microsecond using @@ -257,8 +263,8 @@ A :class:`timedelta` object represents a duration, the difference between two Note that normalization of negative values may be surprising at first. For example:: - >>> from datetime import timedelta - >>> d = timedelta(microseconds=-1) + >>> import datetime as dt + >>> d = dt.timedelta(microseconds=-1) >>> (d.days, d.seconds, d.microseconds) (-1, 86399, 999999) @@ -321,8 +327,8 @@ Instance attributes (read-only): .. doctest:: - >>> from datetime import timedelta - >>> duration = timedelta(seconds=11235813) + >>> import datetime as dt + >>> duration = dt.timedelta(seconds=11235813) >>> duration.days, duration.seconds (130, 3813) >>> duration.total_seconds() @@ -461,10 +467,10 @@ Examples of usage: :class:`!timedelta` An additional example of normalization:: >>> # Components of another_year add up to exactly 365 days - >>> from datetime import timedelta - >>> year = timedelta(days=365) - >>> another_year = timedelta(weeks=40, days=84, hours=23, - ... minutes=50, seconds=600) + >>> import datetime as dt + >>> year = dt.timedelta(days=365) + >>> another_year = dt.timedelta(weeks=40, days=84, hours=23, + ... minutes=50, seconds=600) >>> year == another_year True >>> year.total_seconds() @@ -472,8 +478,8 @@ An additional example of normalization:: Examples of :class:`timedelta` arithmetic:: - >>> from datetime import timedelta - >>> year = timedelta(days=365) + >>> import datetime as dt + >>> year = dt.timedelta(days=365) >>> ten_years = 10 * year >>> ten_years datetime.timedelta(days=3650) @@ -565,12 +571,12 @@ Other constructors, all class methods: Examples:: - >>> from datetime import date - >>> date.fromisoformat('2019-12-04') + >>> import datetime as dt + >>> dt.date.fromisoformat('2019-12-04') datetime.date(2019, 12, 4) - >>> date.fromisoformat('20191204') + >>> dt.date.fromisoformat('20191204') datetime.date(2019, 12, 4) - >>> date.fromisoformat('2021-W01-1') + >>> dt.date.fromisoformat('2021-W01-1') datetime.date(2021, 1, 4) .. versionadded:: 3.7 @@ -611,9 +617,9 @@ Other constructors, all class methods: .. doctest:: - >>> from datetime import date + >>> import datetime as dt >>> date_string = "02/29" - >>> when = date.strptime(f"{date_string};1984", "%m/%d;%Y") # Avoids leap year bug. + >>> when = dt.date.strptime(f"{date_string};1984", "%m/%d;%Y") # Avoids leap year bug. >>> when.strftime("%B %d") # doctest: +SKIP 'February 29' @@ -728,8 +734,8 @@ Instance methods: Example:: - >>> from datetime import date - >>> d = date(2002, 12, 31) + >>> import datetime as dt + >>> d = dt.date(2002, 12, 31) >>> d.replace(day=26) datetime.date(2002, 12, 26) @@ -787,10 +793,10 @@ Instance methods: For example, 2004 begins on a Thursday, so the first week of ISO year 2004 begins on Monday, 29 Dec 2003 and ends on Sunday, 4 Jan 2004:: - >>> from datetime import date - >>> date(2003, 12, 29).isocalendar() + >>> import datetime as dt + >>> dt.date(2003, 12, 29).isocalendar() datetime.IsoCalendarDate(year=2004, week=1, weekday=1) - >>> date(2004, 1, 4).isocalendar() + >>> dt.date(2004, 1, 4).isocalendar() datetime.IsoCalendarDate(year=2004, week=1, weekday=7) .. versionchanged:: 3.9 @@ -801,8 +807,8 @@ Instance methods: Return a string representing the date in ISO 8601 format, ``YYYY-MM-DD``:: - >>> from datetime import date - >>> date(2002, 12, 4).isoformat() + >>> import datetime as dt + >>> dt.date(2002, 12, 4).isoformat() '2002-12-04' @@ -815,8 +821,8 @@ Instance methods: Return a string representing the date:: - >>> from datetime import date - >>> date(2002, 12, 4).ctime() + >>> import datetime as dt + >>> dt.date(2002, 12, 4).ctime() 'Wed Dec 4 00:00:00 2002' ``d.ctime()`` is equivalent to:: @@ -849,13 +855,13 @@ Examples of usage: :class:`!date` Example of counting days to an event:: >>> import time - >>> from datetime import date - >>> today = date.today() + >>> import datetime as dt + >>> today = dt.date.today() >>> today datetime.date(2007, 12, 5) - >>> today == date.fromtimestamp(time.time()) + >>> today == dt.date.fromtimestamp(time.time()) True - >>> my_birthday = date(today.year, 6, 24) + >>> my_birthday = dt.date(today.year, 6, 24) >>> if my_birthday < today: ... my_birthday = my_birthday.replace(year=today.year + 1) ... @@ -869,8 +875,8 @@ More examples of working with :class:`date`: .. doctest:: - >>> from datetime import date - >>> d = date.fromordinal(730920) # 730920th day after 1. 1. 0001 + >>> import datetime as dt + >>> d = dt.date.fromordinal(730920) # 730920th day after 1. 1. 0001 >>> d datetime.date(2002, 3, 11) @@ -1123,24 +1129,24 @@ Other constructors, all class methods: Examples:: - >>> from datetime import datetime - >>> datetime.fromisoformat('2011-11-04') + >>> import datetime as dt + >>> dt.datetime.fromisoformat('2011-11-04') datetime.datetime(2011, 11, 4, 0, 0) - >>> datetime.fromisoformat('20111104') + >>> dt.datetime.fromisoformat('20111104') datetime.datetime(2011, 11, 4, 0, 0) - >>> datetime.fromisoformat('2011-11-04T00:05:23') + >>> dt.datetime.fromisoformat('2011-11-04T00:05:23') datetime.datetime(2011, 11, 4, 0, 5, 23) - >>> datetime.fromisoformat('2011-11-04T00:05:23Z') + >>> dt.datetime.fromisoformat('2011-11-04T00:05:23Z') datetime.datetime(2011, 11, 4, 0, 5, 23, tzinfo=datetime.timezone.utc) - >>> datetime.fromisoformat('20111104T000523') + >>> dt.datetime.fromisoformat('20111104T000523') datetime.datetime(2011, 11, 4, 0, 5, 23) - >>> datetime.fromisoformat('2011-W01-2T00:05:23.283') + >>> dt.datetime.fromisoformat('2011-W01-2T00:05:23.283') datetime.datetime(2011, 1, 4, 0, 5, 23, 283000) - >>> datetime.fromisoformat('2011-11-04 00:05:23.283') + >>> dt.datetime.fromisoformat('2011-11-04 00:05:23.283') datetime.datetime(2011, 11, 4, 0, 5, 23, 283000) - >>> datetime.fromisoformat('2011-11-04 00:05:23.283+00:00') + >>> dt.datetime.fromisoformat('2011-11-04 00:05:23.283+00:00') datetime.datetime(2011, 11, 4, 0, 5, 23, 283000, tzinfo=datetime.timezone.utc) - >>> datetime.fromisoformat('2011-11-04T00:05:23+04:00') # doctest: +NORMALIZE_WHITESPACE + >>> dt.datetime.fromisoformat('2011-11-04T00:05:23+04:00') # doctest: +NORMALIZE_WHITESPACE datetime.datetime(2011, 11, 4, 0, 5, 23, tzinfo=datetime.timezone(datetime.timedelta(seconds=14400))) @@ -1187,9 +1193,9 @@ Other constructors, all class methods: .. doctest:: - >>> from datetime import datetime + >>> import datetime as dt >>> date_string = "02/29" - >>> when = datetime.strptime(f"{date_string};1984", "%m/%d;%Y") # Avoids leap year bug. + >>> when = dt.datetime.strptime(f"{date_string};1984", "%m/%d;%Y") # Avoids leap year bug. >>> when.strftime("%B %d") # doctest: +SKIP 'February 29' @@ -1599,24 +1605,24 @@ Instance methods: Examples:: - >>> from datetime import datetime, timezone - >>> datetime(2019, 5, 18, 15, 17, 8, 132263).isoformat() + >>> import datetime as dt + >>> dt.datetime(2019, 5, 18, 15, 17, 8, 132263).isoformat() '2019-05-18T15:17:08.132263' - >>> datetime(2019, 5, 18, 15, 17, tzinfo=timezone.utc).isoformat() + >>> dt.datetime(2019, 5, 18, 15, 17, tzinfo=dt.timezone.utc).isoformat() '2019-05-18T15:17:00+00:00' The optional argument *sep* (default ``'T'``) is a one-character separator, placed between the date and time portions of the result. For example:: - >>> from datetime import tzinfo, timedelta, datetime - >>> class TZ(tzinfo): + >>> import datetime as dt + >>> class TZ(dt.tzinfo): ... """A time zone with an arbitrary, constant -06:39 offset.""" - ... def utcoffset(self, dt): - ... return timedelta(hours=-6, minutes=-39) + ... def utcoffset(self, when): + ... return dt.timedelta(hours=-6, minutes=-39) ... - >>> datetime(2002, 12, 25, tzinfo=TZ()).isoformat(' ') + >>> dt.datetime(2002, 12, 25, tzinfo=TZ()).isoformat(' ') '2002-12-25 00:00:00-06:39' - >>> datetime(2009, 11, 27, microsecond=100, tzinfo=TZ()).isoformat() + >>> dt.datetime(2009, 11, 27, microsecond=100, tzinfo=TZ()).isoformat() '2009-11-27T00:00:00.000100-06:39' The optional argument *timespec* specifies the number of additional @@ -1640,11 +1646,11 @@ Instance methods: :exc:`ValueError` will be raised on an invalid *timespec* argument:: - >>> from datetime import datetime - >>> datetime.now().isoformat(timespec='minutes') # doctest: +SKIP + >>> import datetime as dt + >>> dt.datetime.now().isoformat(timespec='minutes') # doctest: +SKIP '2002-12-25T00:00' - >>> dt = datetime(2015, 1, 1, 12, 30, 59, 0) - >>> dt.isoformat(timespec='microseconds') + >>> my_datetime = dt.datetime(2015, 1, 1, 12, 30, 59, 0) + >>> my_datetime.isoformat(timespec='microseconds') '2015-01-01T12:30:59.000000' .. versionchanged:: 3.6 @@ -1661,8 +1667,8 @@ Instance methods: Return a string representing the date and time:: - >>> from datetime import datetime - >>> datetime(2002, 12, 4, 20, 30, 40).ctime() + >>> import datetime as dt + >>> dt.datetime(2002, 12, 4, 20, 30, 40).ctime() 'Wed Dec 4 20:30:40 2002' The output string will *not* include time zone information, regardless @@ -1699,27 +1705,27 @@ Examples of working with :class:`.datetime` objects: .. doctest:: - >>> from datetime import datetime, date, time, timezone + >>> import datetime as dt >>> # Using datetime.combine() - >>> d = date(2005, 7, 14) - >>> t = time(12, 30) - >>> datetime.combine(d, t) + >>> d = dt.date(2005, 7, 14) + >>> t = dt.time(12, 30) + >>> dt.datetime.combine(d, t) datetime.datetime(2005, 7, 14, 12, 30) >>> # Using datetime.now() - >>> datetime.now() # doctest: +SKIP + >>> dt.datetime.now() # doctest: +SKIP datetime.datetime(2007, 12, 6, 16, 29, 43, 79043) # GMT +1 - >>> datetime.now(timezone.utc) # doctest: +SKIP + >>> dt.datetime.now(dt.timezone.utc) # doctest: +SKIP datetime.datetime(2007, 12, 6, 15, 29, 43, 79060, tzinfo=datetime.timezone.utc) >>> # Using datetime.strptime() - >>> dt = datetime.strptime("21/11/06 16:30", "%d/%m/%y %H:%M") - >>> dt + >>> my_datetime = dt.datetime.strptime("21/11/06 16:30", "%d/%m/%y %H:%M") + >>> my_datetime datetime.datetime(2006, 11, 21, 16, 30) >>> # Using datetime.timetuple() to get tuple of all attributes - >>> tt = dt.timetuple() + >>> tt = my_datetime.timetuple() >>> for it in tt: # doctest: +SKIP ... print(it) ... @@ -1734,7 +1740,7 @@ Examples of working with :class:`.datetime` objects: -1 # dst - method tzinfo.dst() returned None >>> # Date in ISO format - >>> ic = dt.isocalendar() + >>> ic = my_datetime.isocalendar() >>> for it in ic: # doctest: +SKIP ... print(it) ... @@ -1743,55 +1749,55 @@ Examples of working with :class:`.datetime` objects: 2 # ISO weekday >>> # Formatting a datetime - >>> dt.strftime("%A, %d. %B %Y %I:%M%p") + >>> my_datetime.strftime("%A, %d. %B %Y %I:%M%p") 'Tuesday, 21. November 2006 04:30PM' - >>> 'The {1} is {0:%d}, the {2} is {0:%B}, the {3} is {0:%I:%M%p}.'.format(dt, "day", "month", "time") + >>> 'The {1} is {0:%d}, the {2} is {0:%B}, the {3} is {0:%I:%M%p}.'.format(my_datetime, "day", "month", "time") 'The day is 21, the month is November, the time is 04:30PM.' The example below defines a :class:`tzinfo` subclass capturing time zone information for Kabul, Afghanistan, which used +4 UTC until 1945 and then +4:30 UTC thereafter:: - from datetime import timedelta, datetime, tzinfo, timezone + import datetime as dt - class KabulTz(tzinfo): + class KabulTz(dt.tzinfo): # Kabul used +4 until 1945, when they moved to +4:30 - UTC_MOVE_DATE = datetime(1944, 12, 31, 20, tzinfo=timezone.utc) + UTC_MOVE_DATE = dt.datetime(1944, 12, 31, 20, tzinfo=dt.timezone.utc) - def utcoffset(self, dt): - if dt.year < 1945: - return timedelta(hours=4) - elif (1945, 1, 1, 0, 0) <= dt.timetuple()[:5] < (1945, 1, 1, 0, 30): + def utcoffset(self, when): + if when.year < 1945: + return dt.timedelta(hours=4) + elif (1945, 1, 1, 0, 0) <= when.timetuple()[:5] < (1945, 1, 1, 0, 30): # An ambiguous ("imaginary") half-hour range representing # a 'fold' in time due to the shift from +4 to +4:30. - # If dt falls in the imaginary range, use fold to decide how - # to resolve. See PEP495. - return timedelta(hours=4, minutes=(30 if dt.fold else 0)) + # If when falls in the imaginary range, use fold to decide how + # to resolve. See PEP 495. + return dt.timedelta(hours=4, minutes=(30 if when.fold else 0)) else: - return timedelta(hours=4, minutes=30) + return dt.timedelta(hours=4, minutes=30) - def fromutc(self, dt): + def fromutc(self, when): # Follow same validations as in datetime.tzinfo - if not isinstance(dt, datetime): + if not isinstance(when, dt.datetime): raise TypeError("fromutc() requires a datetime argument") - if dt.tzinfo is not self: - raise ValueError("dt.tzinfo is not self") + if when.tzinfo is not self: + raise ValueError("when.tzinfo is not self") # A custom implementation is required for fromutc as # the input to this function is a datetime with utc values # but with a tzinfo set to self. # See datetime.astimezone or fromtimestamp. - if dt.replace(tzinfo=timezone.utc) >= self.UTC_MOVE_DATE: - return dt + timedelta(hours=4, minutes=30) + if when.replace(tzinfo=dt.timezone.utc) >= self.UTC_MOVE_DATE: + return when + dt.timedelta(hours=4, minutes=30) else: - return dt + timedelta(hours=4) + return when + dt.timedelta(hours=4) - def dst(self, dt): + def dst(self, when): # Kabul does not observe daylight saving time. - return timedelta(0) + return dt.timedelta(0) - def tzname(self, dt): - if dt >= self.UTC_MOVE_DATE: + def tzname(self, when): + if when >= self.UTC_MOVE_DATE: return "+04:30" return "+04" @@ -1800,17 +1806,17 @@ Usage of ``KabulTz`` from above:: >>> tz1 = KabulTz() >>> # Datetime before the change - >>> dt1 = datetime(1900, 11, 21, 16, 30, tzinfo=tz1) + >>> dt1 = dt.datetime(1900, 11, 21, 16, 30, tzinfo=tz1) >>> print(dt1.utcoffset()) 4:00:00 >>> # Datetime after the change - >>> dt2 = datetime(2006, 6, 14, 13, 0, tzinfo=tz1) + >>> dt2 = dt.datetime(2006, 6, 14, 13, 0, tzinfo=tz1) >>> print(dt2.utcoffset()) 4:30:00 >>> # Convert datetime to another time zone - >>> dt3 = dt2.astimezone(timezone.utc) + >>> dt3 = dt2.astimezone(dt.timezone.utc) >>> dt3 datetime.datetime(2006, 6, 14, 8, 30, tzinfo=datetime.timezone.utc) >>> dt2 @@ -1946,22 +1952,22 @@ Other constructors: .. doctest:: - >>> from datetime import time - >>> time.fromisoformat('04:23:01') + >>> import datetime as dt + >>> dt.time.fromisoformat('04:23:01') datetime.time(4, 23, 1) - >>> time.fromisoformat('T04:23:01') + >>> dt.time.fromisoformat('T04:23:01') datetime.time(4, 23, 1) - >>> time.fromisoformat('T042301') + >>> dt.time.fromisoformat('T042301') datetime.time(4, 23, 1) - >>> time.fromisoformat('04:23:01.000384') + >>> dt.time.fromisoformat('04:23:01.000384') datetime.time(4, 23, 1, 384) - >>> time.fromisoformat('04:23:01,000384') + >>> dt.time.fromisoformat('04:23:01,000384') datetime.time(4, 23, 1, 384) - >>> time.fromisoformat('04:23:01+04:00') + >>> dt.time.fromisoformat('04:23:01+04:00') datetime.time(4, 23, 1, tzinfo=datetime.timezone(datetime.timedelta(seconds=14400))) - >>> time.fromisoformat('04:23:01Z') + >>> dt.time.fromisoformat('04:23:01Z') datetime.time(4, 23, 1, tzinfo=datetime.timezone.utc) - >>> time.fromisoformat('04:23:01+00:00') + >>> dt.time.fromisoformat('04:23:01+00:00') datetime.time(4, 23, 1, tzinfo=datetime.timezone.utc) @@ -2036,13 +2042,13 @@ Instance methods: Example:: - >>> from datetime import time - >>> time(hour=12, minute=34, second=56, microsecond=123456).isoformat(timespec='minutes') + >>> import datetime as dt + >>> dt.time(hour=12, minute=34, second=56, microsecond=123456).isoformat(timespec='minutes') '12:34' - >>> dt = time(hour=12, minute=34, second=56, microsecond=0) - >>> dt.isoformat(timespec='microseconds') + >>> my_time = dt.time(hour=12, minute=34, second=56, microsecond=0) + >>> my_time.isoformat(timespec='microseconds') '12:34:56.000000' - >>> dt.isoformat(timespec='auto') + >>> my_time.isoformat(timespec='auto') '12:34:56' .. versionchanged:: 3.6 @@ -2100,18 +2106,18 @@ Examples of usage: :class:`!time` Examples of working with a :class:`.time` object:: - >>> from datetime import time, tzinfo, timedelta - >>> class TZ1(tzinfo): - ... def utcoffset(self, dt): - ... return timedelta(hours=1) - ... def dst(self, dt): - ... return timedelta(0) - ... def tzname(self,dt): + >>> import datetime as dt + >>> class TZ1(dt.tzinfo): + ... def utcoffset(self, when): + ... return dt.timedelta(hours=1) + ... def dst(self, when): + ... return dt.timedelta(0) + ... def tzname(self, when): ... return "+01:00" ... def __repr__(self): ... return f"{self.__class__.__name__}()" ... - >>> t = time(12, 10, 30, tzinfo=TZ1()) + >>> t = dt.time(12, 10, 30, tzinfo=TZ1()) >>> t datetime.time(12, 10, 30, tzinfo=TZ1()) >>> t.isoformat() @@ -2219,21 +2225,25 @@ Examples of working with a :class:`.time` object:: Most implementations of :meth:`dst` will probably look like one of these two:: - def dst(self, dt): + import datetime as dt + + def dst(self, when): # a fixed-offset class: doesn't account for DST - return timedelta(0) + return dt.timedelta(0) or:: - def dst(self, dt): + import datetime as dt + + def dst(self, when): # Code to set dston and dstoff to the time zone's DST - # transition times based on the input dt.year, and expressed + # transition times based on the input when.year, and expressed # in standard local time. - if dston <= dt.replace(tzinfo=None) < dstoff: - return timedelta(hours=1) + if dston <= when.replace(tzinfo=None) < dstoff: + return dt.timedelta(hours=1) else: - return timedelta(0) + return dt.timedelta(0) The default implementation of :meth:`dst` raises :exc:`NotImplementedError`. @@ -2299,20 +2309,22 @@ There is one more :class:`tzinfo` method that a subclass may wish to override: Skipping code for error cases, the default :meth:`fromutc` implementation acts like:: - def fromutc(self, dt): - # raise ValueError error if dt.tzinfo is not self - dtoff = dt.utcoffset() - dtdst = dt.dst() + import datetime as dt + + def fromutc(self, when): + # raise ValueError error if when.tzinfo is not self + dtoff = when.utcoffset() + dtdst = when.dst() # raise ValueError if dtoff is None or dtdst is None delta = dtoff - dtdst # this is self's standard offset if delta: - dt += delta # convert to standard local time - dtdst = dt.dst() + when += delta # convert to standard local time + dtdst = when.dst() # raise ValueError if dtdst is None if dtdst: - return dt + dtdst + return when + dtdst else: - return dt + return when In the following :download:`tzinfo_examples.py <../includes/tzinfo_examples.py>` file there are some examples of @@ -2339,9 +2351,9 @@ When DST starts (the "start" line), the local wall clock leaps from 1:59 to ``astimezone(Eastern)`` won't deliver a result with ``hour == 2`` on the day DST begins. For example, at the Spring forward transition of 2016, we get:: - >>> from datetime import datetime, timezone + >>> import datetime as dt >>> from tzinfo_examples import HOUR, Eastern - >>> u0 = datetime(2016, 3, 13, 5, tzinfo=timezone.utc) + >>> u0 = dt.datetime(2016, 3, 13, 5, tzinfo=dt.timezone.utc) >>> for i in range(4): ... u = u0 + i*HOUR ... t = u.astimezone(Eastern) @@ -2364,7 +2376,9 @@ form 5:MM and 6:MM both map to 1:MM when converted to Eastern, but earlier times have the :attr:`~.datetime.fold` attribute set to 0 and the later times have it set to 1. For example, at the Fall back transition of 2016, we get:: - >>> u0 = datetime(2016, 11, 6, 4, tzinfo=timezone.utc) + >>> import datetime as dt + >>> from tzinfo_examples import HOUR, Eastern + >>> u0 = dt.datetime(2016, 11, 6, 4, tzinfo=dt.timezone.utc) >>> for i in range(4): ... u = u0 + i*HOUR ... t = u.astimezone(Eastern) @@ -2515,8 +2529,9 @@ versus :meth:`~.datetime.strptime`: These methods accept format codes that can be used to parse and format dates:: - >>> datetime.strptime('31/01/22 23:59:59.999999', - ... '%d/%m/%y %H:%M:%S.%f') + >>> import datetime as dt + >>> dt.datetime.strptime('31/01/22 23:59:59.999999', + ... '%d/%m/%y %H:%M:%S.%f') datetime.datetime(2022, 1, 31, 23, 59, 59, 999999) >>> _.strftime('%a %d %b %Y, %I:%M%p') 'Mon 31 Jan 2022, 11:59PM' @@ -2745,13 +2760,13 @@ in the format string will be pulled from the default value. .. doctest:: - >>> from datetime import datetime + >>> import datetime as dt >>> value = "2/29" - >>> datetime.strptime(value, "%m/%d") + >>> dt.datetime.strptime(value, "%m/%d") Traceback (most recent call last): ... ValueError: day 29 must be in range 1..28 for month 2 in year 1900 - >>> datetime.strptime(f"1904 {value}", "%Y %m/%d") + >>> dt.datetime.strptime(f"1904 {value}", "%Y %m/%d") datetime.datetime(1904, 2, 29, 0, 0) Using ``datetime.strptime(date_string, format)`` is equivalent to:: @@ -2897,7 +2912,7 @@ Notes: .. doctest:: >>> month_day = "02/29" - >>> datetime.strptime(f"{month_day};1984", "%m/%d;%Y") # No leap year bug. + >>> dt.datetime.strptime(f"{month_day};1984", "%m/%d;%Y") # No leap year bug. datetime.datetime(1984, 2, 29, 0, 0) .. deprecated-removed:: 3.13 3.15 @@ -2908,7 +2923,7 @@ Notes: .. rubric:: Footnotes -.. [#] If, that is, we ignore the effects of Relativity +.. [#] If, that is, we ignore the effects of relativity. .. [#] This matches the definition of the "proleptic Gregorian" calendar in Dershowitz and Reingold's book *Calendrical Calculations*,