| | Week 01 is the week containing | | |
| | Jan 4. | | |
+-----------+--------------------------------+------------------------+-------+
+| ``%: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 | |
++-----------+--------------------------------+------------------------+-------+
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
.. versionadded:: 3.6
``%G``, ``%u`` and ``%V`` were added.
+.. versionadded:: 3.12
+ ``%:z`` was added.
+
Technical Detail
^^^^^^^^^^^^^^^^
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:
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
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:
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
# 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)
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 == ':':
+ 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 = ""
# 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")
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))
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))
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:
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:
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)
--- /dev/null
+Add ``%:z`` strftime format code (generates tzoffset with colons as separator), see :ref:`strftime-strptime-behavior`.
return 0;
}
+static PyObject *
+make_somezreplacement(PyObject *object, char *sep, PyObject *tzinfoarg)
+{
+ char buf[100];
+ PyObject *tzinfo = get_tzinfo_member(object);
+
+ if (tzinfo == Py_None || tzinfo == NULL) {
+ return PyBytes_FromStringAndSize(NULL, 0);
+ }
+
+ assert(tzinfoarg != NULL);
+ if (format_utcoffset(buf,
+ sizeof(buf),
+ sep,
+ tzinfo,
+ tzinfoarg) < 0)
+ return NULL;
+
+ return PyBytes_FromStringAndSize(buf, strlen(buf));
+}
+
static PyObject *
make_Zreplacement(PyObject *object, PyObject *tzinfoarg)
{
/* 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.
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 */
}
/* A % has been seen and ch is the character after it. */
else if (ch == 'z') {
+ /* %z -> +HHMM */
if (zreplacement == NULL) {
- /* format utcoffset */
- char buf[100];
- PyObject *tzinfo = get_tzinfo_member(object);
- zreplacement = PyBytes_FromStringAndSize("", 0);
- if (zreplacement == NULL) goto Done;
- if (tzinfo != Py_None && tzinfo != NULL) {
- assert(tzinfoarg != NULL);
- if (format_utcoffset(buf,
- sizeof(buf),
- "",
- tzinfo,
- tzinfoarg) < 0)
- goto Done;
- Py_DECREF(zreplacement);
- zreplacement =
- PyBytes_FromStringAndSize(buf,
- strlen(buf));
- if (zreplacement == NULL)
- goto Done;
- }
+ zreplacement = make_somezreplacement(object, "", tzinfoarg);
+ if (zreplacement == NULL)
+ goto Done;
}
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(colonzreplacement != NULL);
+ assert(PyBytes_Check(colonzreplacement));
+ ptoappend = PyBytes_AS_STRING(colonzreplacement);
+ ntoappend = PyBytes_GET_SIZE(colonzreplacement);
+ }
else if (ch == 'Z') {
/* format tzname */
if (Zreplacement == NULL) {
ntoappend = PyBytes_GET_SIZE(freplacement);
}
else {
- /* percent followed by neither z nor Z */
+ /* percent followed by something else */
ptoappend = pin - 2;
ntoappend = 2;
}
Done:
Py_XDECREF(freplacement);
Py_XDECREF(zreplacement);
+ Py_XDECREF(colonzreplacement);
Py_XDECREF(Zreplacement);
Py_XDECREF(newfmt);
return result;