| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent e860e52 commit 023c51d
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2443,6 +2443,11 @@ convenience. These parameters all correspond to ISO 8601 date values. | |||
| 2443 | 2443 | | | Week 01 is the week containing | | | | |
| 2444 | 2444 | | | Jan 4. | | | | |
| 2445 | 2445 | +-----------+--------------------------------+------------------------+-------+ | |
| 2446 | + | ``%:z`` | UTC offset in the form | (empty), +00:00, | \(6) | | ||
| 2447 | + | | ``±HH:MM[:SS[.ffffff]]`` | -04:00, +10:30, | | | ||
| 2448 | + | | (empty string if the object is | +06:34:15, | | | ||
| 2449 | + | | naive). | -03:07:12.345216 | | | ||
| 2450 | + +-----------+--------------------------------+------------------------+-------+ | ||
| 2446 | 2451 | ||
| 2447 | 2452 | These may not be available on all platforms when used with the :meth:`strftime` | |
| 2448 | 2453 | method. The ISO 8601 year and ISO 8601 week directives are not interchangeable | |
@@ -2458,6 +2463,9 @@ differences between platforms in handling of unsupported format specifiers. | |||
| 2458 | 2463 | .. versionadded:: 3.6 | |
| 2459 | 2464 | ``%G``, ``%u`` and ``%V`` were added. | |
| 2460 | 2465 | ||
| 2466 | + .. versionadded:: 3.12 | ||
| 2467 | + ``%:z`` was added. | ||
| 2468 | + | ||
| 2461 | 2469 | Technical Detail | |
| 2462 | 2470 | ^^^^^^^^^^^^^^^^ | |
| 2463 | 2471 | ||
@@ -2530,8 +2538,8 @@ Notes: | |||
| 2530 | 2538 | available). | |
| 2531 | 2539 | ||
| 2532 | 2540 | (6) | |
| 2533 | - For a naive object, the ``%z`` and ``%Z`` format codes are replaced by empty | ||
| 2534 | - strings. | ||
| 2541 | + For a naive object, the ``%z``, ``%:z`` and ``%Z`` format codes are replaced | ||
| 2542 | + by empty strings. | ||
| 2535 | 2543 | ||
| 2536 | 2544 | For an aware object: | |
| 2537 | 2545 | ||
@@ -2557,6 +2565,10 @@ Notes: | |||
| 2557 | 2565 | For example, ``'+01:00:00'`` will be parsed as an offset of one hour. | |
| 2558 | 2566 | In addition, providing ``'Z'`` is identical to ``'+00:00'``. | |
| 2559 | 2567 | ||
| 2568 | + ``%:z`` | ||
| 2569 | + Behaves exactly as ``%z``, but has a colon separator added between | ||
| 2570 | + hours, minutes and seconds. | ||
| 2571 | + | ||
| 2560 | 2572 | ``%Z`` | |
| 2561 | 2573 | In :meth:`strftime`, ``%Z`` is replaced by an empty string if | |
| 2562 | 2574 | :meth:`tzname` returns ``None``; otherwise ``%Z`` is replaced by the | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -179,7 +179,7 @@ def _format_time(hh, mm, ss, us, timespec='auto'): | |||
| 179 | 179 | else: | |
| 180 | 180 | return fmt.format(hh, mm, ss, us) | |
| 181 | 181 | ||
| 182 | - def _format_offset(off): | ||
| 182 | + def _format_offset(off, sep=':'): | ||
| 183 | 183 | s = '' | |
| 184 | 184 | if off is not None: | |
| 185 | 185 | if off.days < 0: | |
@@ -189,9 +189,9 @@ def _format_offset(off): | |||
| 189 | 189 | sign = "+" | |
| 190 | 190 | hh, mm = divmod(off, timedelta(hours=1)) | |
| 191 | 191 | mm, ss = divmod(mm, timedelta(minutes=1)) | |
| 192 | - s += "%s%02d:%02d" % (sign, hh, mm) | ||
| 192 | + s += "%s%02d%s%02d" % (sign, hh, sep, mm) | ||
| 193 | 193 | if ss or ss.microseconds: | |
| 194 | - s += ":%02d" % ss.seconds | ||
| 194 | + s += "%s%02d" % (sep, ss.seconds) | ||
| 195 | 195 | ||
| 196 | 196 | if ss.microseconds: | |
| 197 | 197 | s += '.%06d' % ss.microseconds | |
@@ -202,9 +202,10 @@ def _wrap_strftime(object, format, timetuple): | |||
| 202 | 202 | # Don't call utcoffset() or tzname() unless actually needed. | |
| 203 | 203 | freplace = None # the string to use for %f | |
| 204 | 204 | zreplace = None # the string to use for %z | |
| 205 | + colonzreplace = None # the string to use for %:z | ||
| 205 | 206 | Zreplace = None # the string to use for %Z | |
| 206 | 207 | ||
| 207 | - # Scan format for %z and %Z escapes, replacing as needed. | ||
| 208 | + # Scan format for %z, %:z and %Z escapes, replacing as needed. | ||
| 208 | 209 | newformat = [] | |
| 209 | 210 | push = newformat.append | |
| 210 | 211 | i, n = 0, len(format) | |
@@ -222,26 +223,28 @@ def _wrap_strftime(object, format, timetuple): | |||
| 222 | 223 | newformat.append(freplace) | |
| 223 | 224 | elif ch == 'z': | |
| 224 | 225 | if zreplace is None: | |
| 225 | - zreplace = "" | ||
| 226 | 226 | if hasattr(object, "utcoffset"): | |
| 227 | - offset = object.utcoffset() | ||
| 228 | - if offset is not None: | ||
| 229 | - sign = '+' | ||
| 230 | - if offset.days < 0: | ||
| 231 | - offset = -offset | ||
| 232 | - sign = '-' | ||
| 233 | - h, rest = divmod(offset, timedelta(hours=1)) | ||
| 234 | - m, rest = divmod(rest, timedelta(minutes=1)) | ||
| 235 | - s = rest.seconds | ||
| 236 | - u = offset.microseconds | ||
| 237 | - if u: | ||
| 238 | - zreplace = '%c%02d%02d%02d.%06d' % (sign, h, m, s, u) | ||
| 239 | - elif s: | ||
| 240 | - zreplace = '%c%02d%02d%02d' % (sign, h, m, s) | ||
| 241 | - else: | ||
| 242 | - zreplace = '%c%02d%02d' % (sign, h, m) | ||
| 227 | + zreplace = _format_offset(object.utcoffset(), sep="") | ||
| 228 | + else: | ||
| 229 | + zreplace = "" | ||
| 243 | 230 | assert '%' not in zreplace | |
| 244 | 231 | newformat.append(zreplace) | |
| 232 | + elif ch == ':': | ||
| 233 | + if i < n: | ||
| 234 | + ch2 = format[i] | ||
| 235 | + i += 1 | ||
| 236 | + if ch2 == 'z': | ||
| 237 | + if colonzreplace is None: | ||
| 238 | + if hasattr(object, "utcoffset"): | ||
| 239 | + colonzreplace = _format_offset(object.utcoffset(), sep=":") | ||
| 240 | + else: | ||
| 241 | + colonzreplace = "" | ||
| 242 | + assert '%' not in colonzreplace | ||
| 243 | + newformat.append(colonzreplace) | ||
| 244 | + else: | ||
| 245 | + push('%') | ||
| 246 | + push(ch) | ||
| 247 | + push(ch2) | ||
| 245 | 248 | elif ch == 'Z': | |
| 246 | 249 | if Zreplace is None: | |
| 247 | 250 | Zreplace = "" | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1463,8 +1463,8 @@ def test_strftime(self): | |||
| 1463 | 1463 | # test that unicode input is allowed (issue 2782) | |
| 1464 | 1464 | self.assertEqual(t.strftime("%m"), "03") | |
| 1465 | 1465 | ||
| 1466 | - # A naive object replaces %z and %Z w/ empty strings. | ||
| 1467 | - self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") | ||
| 1466 | + # A naive object replaces %z, %:z and %Z w/ empty strings. | ||
| 1467 | + self.assertEqual(t.strftime("'%z' '%:z' '%Z'"), "'' '' ''") | ||
| 1468 | 1468 | ||
| 1469 | 1469 | #make sure that invalid format specifiers are handled correctly | |
| 1470 | 1470 | #self.assertRaises(ValueError, t.strftime, "%e") | |
@@ -1528,7 +1528,7 @@ def strftime(self, format_spec): | |||
| 1528 | 1528 | ||
| 1529 | 1529 | for fmt in ["m:%m d:%d y:%y", | |
| 1530 | 1530 | "m:%m d:%d y:%y H:%H M:%M S:%S", | |
| 1531 | - "%z %Z", | ||
| 1531 | + "%z %:z %Z", | ||
| 1532 | 1532 | ]: | |
| 1533 | 1533 | self.assertEqual(dt.__format__(fmt), dt.strftime(fmt)) | |
| 1534 | 1534 | self.assertEqual(a.__format__(fmt), dt.strftime(fmt)) | |
@@ -2134,7 +2134,7 @@ def strftime(self, format_spec): | |||
| 2134 | 2134 | ||
| 2135 | 2135 | for fmt in ["m:%m d:%d y:%y", | |
| 2136 | 2136 | "m:%m d:%d y:%y H:%H M:%M S:%S", | |
| 2137 | - "%z %Z", | ||
| 2137 | + "%z %:z %Z", | ||
| 2138 | 2138 | ]: | |
| 2139 | 2139 | self.assertEqual(dt.__format__(fmt), dt.strftime(fmt)) | |
| 2140 | 2140 | self.assertEqual(a.__format__(fmt), dt.strftime(fmt)) | |
@@ -2777,6 +2777,7 @@ def test_more_strftime(self): | |||
| 2777 | 2777 | tz = timezone(-timedelta(hours=2, seconds=s, microseconds=us)) | |
| 2778 | 2778 | t = t.replace(tzinfo=tz) | |
| 2779 | 2779 | self.assertEqual(t.strftime("%z"), "-0200" + z) | |
| 2780 | + self.assertEqual(t.strftime("%:z"), "-02:00:" + z) | ||
| 2780 | 2781 | ||
| 2781 | 2782 | # bpo-34482: Check that surrogates don't cause a crash. | |
| 2782 | 2783 | try: | |
@@ -3515,8 +3516,8 @@ def test_1653736(self): | |||
| 3515 | 3516 | def test_strftime(self): | |
| 3516 | 3517 | t = self.theclass(1, 2, 3, 4) | |
| 3517 | 3518 | self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004") | |
| 3518 | - # A naive object replaces %z and %Z with empty strings. | ||
| 3519 | - self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") | ||
| 3519 | + # A naive object replaces %z, %:z and %Z with empty strings. | ||
| 3520 | + self.assertEqual(t.strftime("'%z' '%:z' '%Z'"), "'' '' ''") | ||
| 3520 | 3521 | ||
| 3521 | 3522 | # bpo-34482: Check that surrogates don't cause a crash. | |
| 3522 | 3523 | try: | |
@@ -3934,10 +3935,10 @@ def test_zones(self): | |||
| 3934 | 3935 | self.assertEqual(repr(t4), d + "(0, 0, 0, 40)") | |
| 3935 | 3936 | self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)") | |
| 3936 | 3937 | ||
| 3937 | - self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"), | ||
| 3938 | - "07:47:00 %Z=EST %z=-0500") | ||
| 3939 | - self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000") | ||
| 3940 | - self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100") | ||
| 3938 | + self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z %%:z=%:z"), | ||
| 3939 | + "07:47:00 %Z=EST %z=-0500 %:z=-05:00") | ||
| 3940 | + self.assertEqual(t2.strftime("%H:%M:%S %Z %z %:z"), "12:47:00 UTC +0000 +00:00") | ||
| 3941 | + self.assertEqual(t3.strftime("%H:%M:%S %Z %z %:z"), "13:47:00 MET +0100 +01:00") | ||
| 3941 | 3942 | ||
| 3942 | 3943 | yuck = FixedOffset(-1439, "%z %Z %%z%%Z") | |
| 3943 | 3944 | t1 = time(23, 59, tzinfo=yuck) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1 @@ | |||
| 1 | + Add ``%:z`` strftime format code (generates tzoffset with colons as separator), see :ref:`strftime-strptime-behavior`. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1506,6 +1506,27 @@ format_utcoffset(char *buf, size_t buflen, const char *sep, | |||
| 1506 | 1506 | return 0; | |
| 1507 | 1507 | } | |
| 1508 | 1508 | ||
| 1509 | + static PyObject * | ||
| 1510 | + make_somezreplacement(PyObject *object, char *sep, PyObject *tzinfoarg) | ||
| 1511 | + { | ||
| 1512 | + char buf[100]; | ||
| 1513 | + PyObject *tzinfo = get_tzinfo_member(object); | ||
| 1514 | + | ||
| 1515 | + if (tzinfo == Py_None || tzinfo == NULL) { | ||
| 1516 | + return PyBytes_FromStringAndSize(NULL, 0); | ||
| 1517 | + } | ||
| 1518 | + | ||
| 1519 | + assert(tzinfoarg != NULL); | ||
| 1520 | + if (format_utcoffset(buf, | ||
| 1521 | + sizeof(buf), | ||
| 1522 | + sep, | ||
| 1523 | + tzinfo, | ||
| 1524 | + tzinfoarg) < 0) | ||
| 1525 | + return NULL; | ||
| 1526 | + | ||
| 1527 | + return PyBytes_FromStringAndSize(buf, strlen(buf)); | ||
| 1528 | + } | ||
| 1529 | + | ||
| 1509 | 1530 | static PyObject * | |
| 1510 | 1531 | make_Zreplacement(PyObject *object, PyObject *tzinfoarg) | |
| 1511 | 1532 | { | |
@@ -1566,7 +1587,7 @@ make_freplacement(PyObject *object) | |||
| 1566 | 1587 | ||
| 1567 | 1588 | /* I sure don't want to reproduce the strftime code from the time module, | |
| 1568 | 1589 | * so this imports the module and calls it. All the hair is due to | |
| 1569 | - * giving special meanings to the %z, %Z and %f format codes via a | ||
| 1590 | + * giving special meanings to the %z, %:z, %Z and %f format codes via a | ||
| 1570 | 1591 | * preprocessing step on the format string. | |
| 1571 | 1592 | * tzinfoarg is the argument to pass to the object's tzinfo method, if | |
| 1572 | 1593 | * needed. | |
@@ -1578,6 +1599,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, | |||
| 1578 | 1599 | PyObject *result = NULL; /* guilty until proved innocent */ | |
| 1579 | 1600 | ||
| 1580 | 1601 | PyObject *zreplacement = NULL; /* py string, replacement for %z */ | |
| 1602 | + PyObject *colonzreplacement = NULL; /* py string, replacement for %:z */ | ||
| 1581 | 1603 | PyObject *Zreplacement = NULL; /* py string, replacement for %Z */ | |
| 1582 | 1604 | PyObject *freplacement = NULL; /* py string, replacement for %f */ | |
| 1583 | 1605 | ||
@@ -1632,32 +1654,29 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, | |||
| 1632 | 1654 | } | |
| 1633 | 1655 | /* A % has been seen and ch is the character after it. */ | |
| 1634 | 1656 | else if (ch == 'z') { | |
| 1657 | + /* %z -> +HHMM */ | ||
| 1635 | 1658 | if (zreplacement == NULL) { | |
| 1636 | - /* format utcoffset */ | ||
| 1637 | - char buf[100]; | ||
| 1638 | - PyObject *tzinfo = get_tzinfo_member(object); | ||
| 1639 | - zreplacement = PyBytes_FromStringAndSize("", 0); | ||
| 1640 | - if (zreplacement == NULL) goto Done; | ||
| 1641 | - if (tzinfo != Py_None && tzinfo != NULL) { | ||
| 1642 | - assert(tzinfoarg != NULL); | ||
| 1643 | - if (format_utcoffset(buf, | ||
| 1644 | - sizeof(buf), | ||
| 1645 | - "", | ||
| 1646 | - tzinfo, | ||
| 1647 | - tzinfoarg) < 0) | ||
| 1648 | - goto Done; | ||
| 1649 | - Py_DECREF(zreplacement); | ||
| 1650 | - zreplacement = | ||
| 1651 | - PyBytes_FromStringAndSize(buf, | ||
| 1652 | - strlen(buf)); | ||
| 1653 | - if (zreplacement == NULL) | ||
| 1654 | - goto Done; | ||
| 1655 | - } | ||
| 1659 | + zreplacement = make_somezreplacement(object, "", tzinfoarg); | ||
| 1660 | + if (zreplacement == NULL) | ||
| 1661 | + goto Done; | ||
| 1656 | 1662 | } | |
| 1657 | 1663 | assert(zreplacement != NULL); | |
| 1664 | + assert(PyBytes_Check(zreplacement)); | ||
| 1658 | 1665 | ptoappend = PyBytes_AS_STRING(zreplacement); | |
| 1659 | 1666 | ntoappend = PyBytes_GET_SIZE(zreplacement); | |
| 1660 | 1667 | } | |
| 1668 | + else if (ch == ':' && *pin == 'z' && pin++) { | ||
| 1669 | + /* %:z -> +HH:MM */ | ||
| 1670 | + if (colonzreplacement == NULL) { | ||
| 1671 | + colonzreplacement = make_somezreplacement(object, ":", tzinfoarg); | ||
| 1672 | + if (colonzreplacement == NULL) | ||
| 1673 | + goto Done; | ||
| 1674 | + } | ||
| 1675 | + assert(colonzreplacement != NULL); | ||
| 1676 | + assert(PyBytes_Check(colonzreplacement)); | ||
| 1677 | + ptoappend = PyBytes_AS_STRING(colonzreplacement); | ||
| 1678 | + ntoappend = PyBytes_GET_SIZE(colonzreplacement); | ||
| 1679 | + } | ||
| 1661 | 1680 | else if (ch == 'Z') { | |
| 1662 | 1681 | /* format tzname */ | |
| 1663 | 1682 | if (Zreplacement == NULL) { | |
@@ -1686,7 +1705,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, | |||
| 1686 | 1705 | ntoappend = PyBytes_GET_SIZE(freplacement); | |
| 1687 | 1706 | } | |
| 1688 | 1707 | else { | |
| 1689 | - /* percent followed by neither z nor Z */ | ||
| 1708 | + /* percent followed by something else */ | ||
| 1690 | 1709 | ptoappend = pin - 2; | |
| 1691 | 1710 | ntoappend = 2; | |
| 1692 | 1711 | } | |
@@ -1733,6 +1752,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, | |||
| 1733 | 1752 | Done: | |
| 1734 | 1753 | Py_XDECREF(freplacement); | |
| 1735 | 1754 | Py_XDECREF(zreplacement); | |
| 1755 | + Py_XDECREF(colonzreplacement); | ||
| 1736 | 1756 | Py_XDECREF(Zreplacement); | |
| 1737 | 1757 | Py_XDECREF(newfmt); | |
| 1738 | 1758 | return result; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments