From 7d95910cd2c000b58f63531dd0c34351d49a508a Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 14 Aug 2022 18:30:33 +0200 Subject: [PATCH 01/16] add %:z strftime format code datetime.isoformat generates the tzoffset with colons, but there was no format code to make strftime output the same format. for simplicity and consistency the %:z formatting behaves mostly as %z, with the exception of adding colons. this includes the dynamic behaviour of adding seconds and microseconds only when needed (when not 0). this fixes the still open "generate" part of this issue: https://github.com/python/cpython/issues/69142 --- Doc/library/datetime.rst | 6 ++++ Lib/test/datetimetester.py | 13 +++---- ...2-08-14-18-59-54.gh-issue-69142.6is5Pq.rst | 1 + Modules/_datetimemodule.c | 35 ++++++++++++------- 4 files changed, 37 insertions(+), 18 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-08-14-18-59-54.gh-issue-69142.6is5Pq.rst diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 0f042374b6472d..a67737dafdf0f7 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2386,6 +2386,12 @@ requires, and these work on all platforms with a standard C implementation. | | string if the object is | +063415, | | | | naive). | -030712.345216 | | +-----------+--------------------------------+------------------------+-------+ +| ``%:z`` | UTC offset in the form | (empty), +00:00, | | +| | ``±HH:MM[:SS[.ffffff]]`` | -04:00, +10:30, | | +| | (empty string if the object is | +06:34:15, | | +| | naive). | -03:07:12.345216 | | +| | .. versionadded:: 3.12 | | | ++-----------+--------------------------------+------------------------+-------+ | ``%Z`` | Time zone name (empty string | (empty), UTC, GMT | \(6) | | | if the object is naive). | | | +-----------+--------------------------------+------------------------+-------+ diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 7e7f4f33d6e57c..1090bf148fced2 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -1463,8 +1463,8 @@ def test_strftime(self): # test that unicode input is allowed (issue 2782) self.assertEqual(t.strftime("%m"), "03") - # A naive object replaces %z and %Z w/ empty strings. - self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") + # A naive object replaces %z, %:z and %Z w/ empty strings. + self.assertEqual(t.strftime("'%z' '%:z' '%Z'"), "'' '' ''") #make sure that invalid format specifiers are handled correctly #self.assertRaises(ValueError, t.strftime, "%e") @@ -1528,7 +1528,7 @@ def strftime(self, format_spec): for fmt in ["m:%m d:%d y:%y", "m:%m d:%d y:%y H:%H M:%M S:%S", - "%z %Z", + "%z %:z %Z", ]: self.assertEqual(dt.__format__(fmt), dt.strftime(fmt)) self.assertEqual(a.__format__(fmt), dt.strftime(fmt)) @@ -2134,7 +2134,7 @@ def strftime(self, format_spec): for fmt in ["m:%m d:%d y:%y", "m:%m d:%d y:%y H:%H M:%M S:%S", - "%z %Z", + "%z %:z %Z", ]: self.assertEqual(dt.__format__(fmt), dt.strftime(fmt)) self.assertEqual(a.__format__(fmt), dt.strftime(fmt)) @@ -2777,6 +2777,7 @@ def test_more_strftime(self): tz = timezone(-timedelta(hours=2, seconds=s, microseconds=us)) t = t.replace(tzinfo=tz) self.assertEqual(t.strftime("%z"), "-0200" + z) + self.assertEqual(t.strftime("%:z"), "-02:00:" + z) # bpo-34482: Check that surrogates don't cause a crash. try: @@ -3515,8 +3516,8 @@ def test_1653736(self): def test_strftime(self): t = self.theclass(1, 2, 3, 4) self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004") - # A naive object replaces %z and %Z with empty strings. - self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") + # A naive object replaces %z, %:z and %Z with empty strings. + self.assertEqual(t.strftime("'%z' '%:z' '%Z'"), "'' '' ''") # bpo-34482: Check that surrogates don't cause a crash. try: diff --git a/Misc/NEWS.d/next/Library/2022-08-14-18-59-54.gh-issue-69142.6is5Pq.rst b/Misc/NEWS.d/next/Library/2022-08-14-18-59-54.gh-issue-69142.6is5Pq.rst new file mode 100644 index 00000000000000..ced7177abd61dc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-08-14-18-59-54.gh-issue-69142.6is5Pq.rst @@ -0,0 +1 @@ +add %:z strftime format code (generates tzoffset with colons as separator) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index eca7c6b29d7a36..8ea9a99770aa5b 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -1578,6 +1578,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, PyObject *result = NULL; /* guilty until proved innocent */ PyObject *zreplacement = NULL; /* py string, replacement for %z */ + PyObject *colonzreplacement = NULL; /* py string, replacement for %:z */ PyObject *Zreplacement = NULL; /* py string, replacement for %Z */ PyObject *freplacement = NULL; /* py string, replacement for %f */ @@ -1631,32 +1632,42 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, ntoappend = 1; } /* A % has been seen and ch is the character after it. */ - else if (ch == 'z') { - if (zreplacement == NULL) { + else if (ch == 'z' || (ch == ':' && *pin == 'z' && pin++)) { + /* %z -> +HHMM, %:z -> +HH:MM */ + PyObject **replacement_p; + char *sep; + if (ch == ':') { + sep = ":"; + replacement_p = &colonzreplacement; + } else { + sep = ""; + replacement_p = &zreplacement; + } + if (*replacement_p == NULL) { /* format utcoffset */ char buf[100]; PyObject *tzinfo = get_tzinfo_member(object); - zreplacement = PyBytes_FromStringAndSize("", 0); - if (zreplacement == NULL) goto Done; + *replacement_p = PyBytes_FromStringAndSize("", 0); + if (*replacement_p == NULL) goto Done; if (tzinfo != Py_None && tzinfo != NULL) { assert(tzinfoarg != NULL); if (format_utcoffset(buf, sizeof(buf), - "", + sep, tzinfo, tzinfoarg) < 0) goto Done; - Py_DECREF(zreplacement); - zreplacement = + Py_DECREF(*replacement_p); + *replacement_p = PyBytes_FromStringAndSize(buf, strlen(buf)); - if (zreplacement == NULL) + if (*replacement_p == NULL) goto Done; } } - assert(zreplacement != NULL); - ptoappend = PyBytes_AS_STRING(zreplacement); - ntoappend = PyBytes_GET_SIZE(zreplacement); + assert(*replacement_p != NULL); + ptoappend = PyBytes_AS_STRING(*replacement_p); + ntoappend = PyBytes_GET_SIZE(*replacement_p); } else if (ch == 'Z') { /* format tzname */ @@ -1686,7 +1697,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, ntoappend = PyBytes_GET_SIZE(freplacement); } else { - /* percent followed by neither z nor Z */ + /* percent followed by something else */ ptoappend = pin - 2; ntoappend = 2; } From 44b4419260632008985e5423adfb816310cb6c44 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 14 Aug 2022 23:34:47 +0200 Subject: [PATCH 02/16] more tests --- Lib/test/datetimetester.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 1090bf148fced2..bba96698e9e2eb 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -3935,10 +3935,10 @@ def test_zones(self): self.assertEqual(repr(t4), d + "(0, 0, 0, 40)") self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)") - self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"), - "07:47:00 %Z=EST %z=-0500") - self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000") - self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100") + self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z %%:z=%:z"), + "07:47:00 %Z=EST %z=-0500 %:z=-05:00") + self.assertEqual(t2.strftime("%H:%M:%S %Z %z %:z"), "12:47:00 UTC +0000 +00:00") + self.assertEqual(t3.strftime("%H:%M:%S %Z %z %:z"), "13:47:00 MET +0100 +01:00") yuck = FixedOffset(-1439, "%z %Z %%z%%Z") t1 = time(23, 59, tzinfo=yuck) From 7053390124b10b04dc07a7f883f25567e4e5e56b Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 15 Aug 2022 11:34:23 +0200 Subject: [PATCH 03/16] fix: decref new variable --- Modules/_datetimemodule.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 8ea9a99770aa5b..c61fc8fa5f56a6 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -1744,6 +1744,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, Done: Py_XDECREF(freplacement); Py_XDECREF(zreplacement); + Py_XDECREF(colonzreplacement); Py_XDECREF(Zreplacement); Py_XDECREF(newfmt); return result; From a174d675e04bd13a284e04e7d5c0687514680dd0 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 15 Aug 2022 11:36:58 +0200 Subject: [PATCH 04/16] update comment in 1569 --- Modules/_datetimemodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index c61fc8fa5f56a6..581ff778f70b26 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -1566,7 +1566,7 @@ make_freplacement(PyObject *object) /* I sure don't want to reproduce the strftime code from the time module, * so this imports the module and calls it. All the hair is due to - * giving special meanings to the %z, %Z and %f format codes via a + * giving special meanings to the %z, %:z, %Z and %f format codes via a * preprocessing step on the format string. * tzinfoarg is the argument to pass to the object's tzinfo method, if * needed. From fd1c8213e74c14ed81aead77e4f9ce09f7778b09 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 15 Aug 2022 15:48:07 +0200 Subject: [PATCH 05/16] add assert (as in other branches) --- Modules/_datetimemodule.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 581ff778f70b26..5305b1bc73703b 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -1666,6 +1666,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, } } assert(*replacement_p != NULL); + assert(PyBytes_Check(*replacement_p)); ptoappend = PyBytes_AS_STRING(*replacement_p); ntoappend = PyBytes_GET_SIZE(*replacement_p); } From 14f935c1c4112f1340afde7c2401a3cd24a8a3c9 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 15 Aug 2022 17:45:18 +0200 Subject: [PATCH 06/16] cleanup: move zreplacement to own function (as seen for the other replacements) --- Modules/_datetimemodule.c | 50 +++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 5305b1bc73703b..1fc1e069ae6add 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -1506,6 +1506,32 @@ format_utcoffset(char *buf, size_t buflen, const char *sep, return 0; } +static PyObject * +make_somezreplacement(PyObject *object, char *sep, PyObject *tzinfoarg) +{ + char buf[100]; + PyObject *tzinfo = get_tzinfo_member(object); + PyObject *replacement = PyBytes_FromStringAndSize(NULL, 0); + + if (replacement == NULL) + return NULL; + if (tzinfo == Py_None || tzinfo == NULL) + return replacement; + + Py_DECREF(replacement); + + assert(tzinfoarg != NULL); + if (format_utcoffset(buf, + sizeof(buf), + sep, + tzinfo, + tzinfoarg) < 0) + return NULL; + + replacement = PyBytes_FromStringAndSize(buf, strlen(buf)); + return replacement; +} + static PyObject * make_Zreplacement(PyObject *object, PyObject *tzinfoarg) { @@ -1644,26 +1670,10 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, replacement_p = &zreplacement; } if (*replacement_p == NULL) { - /* format utcoffset */ - char buf[100]; - PyObject *tzinfo = get_tzinfo_member(object); - *replacement_p = PyBytes_FromStringAndSize("", 0); - if (*replacement_p == NULL) goto Done; - if (tzinfo != Py_None && tzinfo != NULL) { - assert(tzinfoarg != NULL); - if (format_utcoffset(buf, - sizeof(buf), - sep, - tzinfo, - tzinfoarg) < 0) - goto Done; - Py_DECREF(*replacement_p); - *replacement_p = - PyBytes_FromStringAndSize(buf, - strlen(buf)); - if (*replacement_p == NULL) - goto Done; - } + *replacement_p = make_somezreplacement(object, + sep, tzinfoarg); + if (*replacement_p == NULL) + goto Done; } assert(*replacement_p != NULL); assert(PyBytes_Check(*replacement_p)); From b22f05a9a4da0ad48f2f1400cd7de09fa198b525 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 15 Aug 2022 18:20:14 +0200 Subject: [PATCH 07/16] cleanup: datetime.py: reduce code duplication, reuse _format_offset --- Lib/datetime.py | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/Lib/datetime.py b/Lib/datetime.py index 00ded32cc3e3cb..99d1d2538583ca 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -179,7 +179,7 @@ def _format_time(hh, mm, ss, us, timespec='auto'): else: return fmt.format(hh, mm, ss, us) -def _format_offset(off): +def _format_offset(off, sep=':'): s = '' if off is not None: if off.days < 0: @@ -189,9 +189,9 @@ def _format_offset(off): sign = "+" hh, mm = divmod(off, timedelta(hours=1)) mm, ss = divmod(mm, timedelta(minutes=1)) - s += "%s%02d:%02d" % (sign, hh, mm) + s += "%s%02d%s%02d" % (sign, hh, sep, mm) if ss or ss.microseconds: - s += ":%02d" % ss.seconds + s += "%s%02d" % (sep, ss.seconds) if ss.microseconds: s += '.%06d' % ss.microseconds @@ -222,24 +222,10 @@ def _wrap_strftime(object, format, timetuple): newformat.append(freplace) elif ch == 'z': if zreplace is None: - zreplace = "" if hasattr(object, "utcoffset"): - offset = object.utcoffset() - if offset is not None: - sign = '+' - if offset.days < 0: - offset = -offset - sign = '-' - h, rest = divmod(offset, timedelta(hours=1)) - m, rest = divmod(rest, timedelta(minutes=1)) - s = rest.seconds - u = offset.microseconds - if u: - zreplace = '%c%02d%02d%02d.%06d' % (sign, h, m, s, u) - elif s: - zreplace = '%c%02d%02d%02d' % (sign, h, m, s) - else: - zreplace = '%c%02d%02d' % (sign, h, m) + zreplace = _format_offset(object.utcoffset(), sep="") + else: + zreplace = "" assert '%' not in zreplace newformat.append(zreplace) elif ch == 'Z': From 8f29d9da17f92958ac0556590ee7cebf1177e06f Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 15 Aug 2022 18:33:12 +0200 Subject: [PATCH 08/16] add %:z strftime format code (pure python implementation) --- Lib/datetime.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Lib/datetime.py b/Lib/datetime.py index 99d1d2538583ca..89999438358310 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -202,9 +202,10 @@ def _wrap_strftime(object, format, timetuple): # Don't call utcoffset() or tzname() unless actually needed. freplace = None # the string to use for %f zreplace = None # the string to use for %z + colonzreplace = None # the string to use for %:z Zreplace = None # the string to use for %Z - # Scan format for %z and %Z escapes, replacing as needed. + # Scan format for %z, %:z and %Z escapes, replacing as needed. newformat = [] push = newformat.append i, n = 0, len(format) @@ -228,6 +229,22 @@ def _wrap_strftime(object, format, timetuple): zreplace = "" assert '%' not in zreplace newformat.append(zreplace) + elif ch == ':': + if i < n: + ch2 = format[i] + i += 1 + if ch2 == 'z': + if colonzreplace is None: + if hasattr(object, "utcoffset"): + colonzreplace = _format_offset(object.utcoffset(), sep=":") + else: + colonzreplace = "" + assert '%' not in colonzreplace + newformat.append(colonzreplace) + else: + push('%') + push(ch) + push(ch2) elif ch == 'Z': if Zreplace is None: Zreplace = "" From 1a7e182a170841d2e35e2a755ecfc882744236b3 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 15 Aug 2022 19:08:45 +0200 Subject: [PATCH 09/16] fix indentation --- Lib/datetime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/datetime.py b/Lib/datetime.py index 89999438358310..007114ae622031 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -236,7 +236,7 @@ def _wrap_strftime(object, format, timetuple): if ch2 == 'z': if colonzreplace is None: if hasattr(object, "utcoffset"): - colonzreplace = _format_offset(object.utcoffset(), sep=":") + colonzreplace = _format_offset(object.utcoffset(), sep=":") else: colonzreplace = "" assert '%' not in colonzreplace From b59b83f92ae487a8e3089a3089e3e0a538933d37 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 15 Aug 2022 21:06:01 +0200 Subject: [PATCH 10/16] simplify code by accepting some duplication --- Modules/_datetimemodule.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 1fc1e069ae6add..19f34eb7c576ba 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -1658,27 +1658,29 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, ntoappend = 1; } /* A % has been seen and ch is the character after it. */ - else if (ch == 'z' || (ch == ':' && *pin == 'z' && pin++)) { - /* %z -> +HHMM, %:z -> +HH:MM */ - PyObject **replacement_p; - char *sep; - if (ch == ':') { - sep = ":"; - replacement_p = &colonzreplacement; - } else { - sep = ""; - replacement_p = &zreplacement; + else if (ch == 'z') { + /* %z -> +HHMM */ + if (zreplacement == NULL) { + zreplacement = make_somezreplacement(object, "", tzinfoarg); + if (zreplacement == NULL) + goto Done; } - if (*replacement_p == NULL) { - *replacement_p = make_somezreplacement(object, - sep, tzinfoarg); - if (*replacement_p == NULL) + assert(zreplacement != NULL); + assert(PyBytes_Check(zreplacement)); + ptoappend = PyBytes_AS_STRING(zreplacement); + ntoappend = PyBytes_GET_SIZE(zreplacement); + } + else if (ch == ':' && *pin == 'z' && pin++) { + /* %:z -> +HH:MM */ + if (colonzreplacement == NULL) { + colonzreplacement = make_somezreplacement(object, ":", tzinfoarg); + if (colonzreplacement == NULL) goto Done; } - assert(*replacement_p != NULL); - assert(PyBytes_Check(*replacement_p)); - ptoappend = PyBytes_AS_STRING(*replacement_p); - ntoappend = PyBytes_GET_SIZE(*replacement_p); + assert(colonzreplacement != NULL); + assert(PyBytes_Check(colonzreplacement)); + ptoappend = PyBytes_AS_STRING(colonzreplacement); + ntoappend = PyBytes_GET_SIZE(colonzreplacement); } else if (ch == 'Z') { /* format tzname */ From a95dc165f51ebf8cf822ba915575d3e5690e2290 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Thu, 18 Aug 2022 20:35:39 +0200 Subject: [PATCH 11/16] fixup NEWS.d entry --- .../next/Library/2022-08-14-18-59-54.gh-issue-69142.6is5Pq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2022-08-14-18-59-54.gh-issue-69142.6is5Pq.rst b/Misc/NEWS.d/next/Library/2022-08-14-18-59-54.gh-issue-69142.6is5Pq.rst index ced7177abd61dc..0db8b3730cf54d 100644 --- a/Misc/NEWS.d/next/Library/2022-08-14-18-59-54.gh-issue-69142.6is5Pq.rst +++ b/Misc/NEWS.d/next/Library/2022-08-14-18-59-54.gh-issue-69142.6is5Pq.rst @@ -1 +1 @@ -add %:z strftime format code (generates tzoffset with colons as separator) +Add ``%:z`` strftime format code (generates tzoffset with colons as separator), see :ref:`strftime-strptime-behavior`. From 335bb1dd480d54402fe50ec038441c72edd0fcce Mon Sep 17 00:00:00 2001 From: TW Date: Sat, 27 Aug 2022 14:26:44 +0200 Subject: [PATCH 12/16] simplify make_somezreplacement Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> --- Modules/_datetimemodule.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 19f34eb7c576ba..d86418af0dc1a8 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -1511,15 +1511,11 @@ make_somezreplacement(PyObject *object, char *sep, PyObject *tzinfoarg) { char buf[100]; PyObject *tzinfo = get_tzinfo_member(object); - PyObject *replacement = PyBytes_FromStringAndSize(NULL, 0); - - if (replacement == NULL) - return NULL; - if (tzinfo == Py_None || tzinfo == NULL) - return replacement; - - Py_DECREF(replacement); + if (tzinfo == Py_None || tzinfo == NULL) { + return PyBytes_FromStringAndSize(NULL, 0); + } + assert(tzinfoarg != NULL); if (format_utcoffset(buf, sizeof(buf), @@ -1527,9 +1523,8 @@ make_somezreplacement(PyObject *object, char *sep, PyObject *tzinfoarg) tzinfo, tzinfoarg) < 0) return NULL; - - replacement = PyBytes_FromStringAndSize(buf, strlen(buf)); - return replacement; + + return PyBytes_FromStringAndSize(buf, strlen(buf)); } static PyObject * From 31a4ae4bb774c4f5f89af57c43f8ccdb0c116167 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 27 Aug 2022 14:32:33 +0200 Subject: [PATCH 13/16] remove versionadded from the table --- Doc/library/datetime.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index a67737dafdf0f7..abdf6dca360dcd 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2390,7 +2390,6 @@ requires, and these work on all platforms with a standard C implementation. | | ``±HH:MM[:SS[.ffffff]]`` | -04:00, +10:30, | | | | (empty string if the object is | +06:34:15, | | | | naive). | -03:07:12.345216 | | -| | .. versionadded:: 3.12 | | | +-----------+--------------------------------+------------------------+-------+ | ``%Z`` | Time zone name (empty string | (empty), UTC, GMT | \(6) | | | if the object is naive). | | | From 682508cb37c6ad7cdfe2bc063ae7748c6276c2dd Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 28 Aug 2022 22:41:21 +0200 Subject: [PATCH 14/16] add versionadded note --- Doc/library/datetime.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index abdf6dca360dcd..d8be279dbe277b 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2463,6 +2463,9 @@ differences between platforms in handling of unsupported format specifiers. .. versionadded:: 3.6 ``%G``, ``%u`` and ``%V`` were added. +.. versionadded:: 3.12 + ``%:z`` was added. + Technical Detail ^^^^^^^^^^^^^^^^ From d8dac927ea8f673292d8c2520f2b5467e0e3f9b7 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 28 Aug 2022 22:43:37 +0200 Subject: [PATCH 15/16] move to table which describes the non-C89 convenience stuff --- Doc/library/datetime.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index d8be279dbe277b..0870f1ddc0d7b8 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2386,11 +2386,6 @@ requires, and these work on all platforms with a standard C implementation. | | string if the object is | +063415, | | | | naive). | -030712.345216 | | +-----------+--------------------------------+------------------------+-------+ -| ``%:z`` | UTC offset in the form | (empty), +00:00, | | -| | ``±HH:MM[:SS[.ffffff]]`` | -04:00, +10:30, | | -| | (empty string if the object is | +06:34:15, | | -| | naive). | -03:07:12.345216 | | -+-----------+--------------------------------+------------------------+-------+ | ``%Z`` | Time zone name (empty string | (empty), UTC, GMT | \(6) | | | if the object is naive). | | | +-----------+--------------------------------+------------------------+-------+ @@ -2448,6 +2443,11 @@ convenience. These parameters all correspond to ISO 8601 date values. | | Week 01 is the week containing | | | | | Jan 4. | | | +-----------+--------------------------------+------------------------+-------+ +| ``%:z`` | UTC offset in the form | (empty), +00:00, | | +| | ``±HH:MM[:SS[.ffffff]]`` | -04:00, +10:30, | | +| | (empty string if the object is | +06:34:15, | | +| | naive). | -03:07:12.345216 | | ++-----------+--------------------------------+------------------------+-------+ These may not be available on all platforms when used with the :meth:`strftime` method. The ISO 8601 year and ISO 8601 week directives are not interchangeable From 5167b49d936439a44c0ce07b619e90236d6fd4a0 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 28 Aug 2022 22:50:02 +0200 Subject: [PATCH 16/16] adapt note 6 for %:z --- Doc/library/datetime.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 0870f1ddc0d7b8..c3a66a4674b10a 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2443,7 +2443,7 @@ convenience. These parameters all correspond to ISO 8601 date values. | | Week 01 is the week containing | | | | | Jan 4. | | | +-----------+--------------------------------+------------------------+-------+ -| ``%:z`` | UTC offset in the form | (empty), +00:00, | | +| ``%:z`` | UTC offset in the form | (empty), +00:00, | \(6) | | | ``±HH:MM[:SS[.ffffff]]`` | -04:00, +10:30, | | | | (empty string if the object is | +06:34:15, | | | | naive). | -03:07:12.345216 | | @@ -2538,8 +2538,8 @@ Notes: available). (6) - For a naive object, the ``%z`` and ``%Z`` format codes are replaced by empty - strings. + For a naive object, the ``%z``, ``%:z`` and ``%Z`` format codes are replaced + by empty strings. For an aware object: @@ -2565,6 +2565,10 @@ Notes: For example, ``'+01:00:00'`` will be parsed as an offset of one hour. In addition, providing ``'Z'`` is identical to ``'+00:00'``. + ``%:z`` + Behaves exactly as ``%z``, but has a colon separator added between + hours, minutes and seconds. + ``%Z`` In :meth:`strftime`, ``%Z`` is replaced by an empty string if :meth:`tzname` returns ``None``; otherwise ``%Z`` is replaced by the