Skip to content
Open
39 changes: 33 additions & 6 deletions Doc/library/datetime.rst
Original file line number Diff line number Diff line change
Expand Up @@ -772,13 +772,25 @@ Instance methods:
.. versionchanged:: 3.9
Result changed from a tuple to a :term:`named tuple`.

.. method:: date.isoformat()

Return a string representing the date in ISO 8601 format, ``YYYY-MM-DD``::
.. method:: date.isoformat(basic=False)

Return a string representing the date in:

- ISO 8601 extended format ``YYYY-MM-DD`` (the default), or
- ISO 8601 basic format ``YYYYMMDD`` via the *basic* argument.

Examples:

>>> from datetime import date
>>> date(2002, 12, 4).isoformat()
'2002-12-04'
>>> date(2002, 12, 4).isoformat(basic=True)
'20021204'

.. versionchanged:: next
Added the *basic* parameter.


.. method:: date.__str__()

Expand Down Expand Up @@ -1536,9 +1548,9 @@ Instance methods:
and ``weekday``. The same as ``self.date().isocalendar()``.


.. method:: datetime.isoformat(sep='T', timespec='auto')
.. method:: datetime.isoformat(sep='T', timespec='auto', basic=False)

Return a string representing the date and time in ISO 8601 format:
Return a string representing the date and time in ISO 8601 extended format:

- ``YYYY-MM-DDTHH:MM:SS.ffffff``, if :attr:`microsecond` is not 0
- ``YYYY-MM-DDTHH:MM:SS``, if :attr:`microsecond` is 0
Expand All @@ -1550,13 +1562,20 @@ Instance methods:
is not 0
- ``YYYY-MM-DDTHH:MM:SS+HH:MM[:SS[.ffffff]]``, if :attr:`microsecond` is 0

If *basic* is true, this uses the ISO 8601 basic format for the date,
time and offset components.

Examples::

>>> from datetime import datetime, timezone
>>> datetime(2019, 5, 18, 15, 17, 8, 132263).isoformat()
'2019-05-18T15:17:08.132263'
>>> datetime(2019, 5, 18, 15, 17, 8, 132263).isoformat(basic=True)
'20190518T151708.132263'
>>> datetime(2019, 5, 18, 15, 17, tzinfo=timezone.utc).isoformat()
'2019-05-18T15:17:00+00:00'
>>> datetime(2019, 5, 18, 15, 17, tzinfo=timezone.utc).isoformat(basic=True)
'20190518T151700+0000'

The optional argument *sep* (default ``'T'``) is a one-character separator,
placed between the date and time portions of the result. For example::
Expand Down Expand Up @@ -1603,6 +1622,9 @@ Instance methods:
.. versionchanged:: 3.6
Added the *timespec* parameter.

.. versionadded:: next
Added the *basic* parameter.


.. method:: datetime.__str__()

Expand Down Expand Up @@ -1954,15 +1976,17 @@ Instance methods:
Added the *fold* parameter.


.. method:: time.isoformat(timespec='auto')
.. method:: time.isoformat(timespec='auto', basic=False)

Return a string representing the time in ISO 8601 format, one of:
Return a string representing the time in ISO 8601 (extended) format, one of:

- ``HH:MM:SS.ffffff``, if :attr:`microsecond` is not 0
- ``HH:MM:SS``, if :attr:`microsecond` is 0
- ``HH:MM:SS.ffffff+HH:MM[:SS[.ffffff]]``, if :meth:`utcoffset` does not return ``None``
- ``HH:MM:SS+HH:MM[:SS[.ffffff]]``, if :attr:`microsecond` is 0 and :meth:`utcoffset` does not return ``None``

If *basic* is true, this uses the ISO 8601 basic format which omits the colons.

The optional argument *timespec* specifies the number of additional
components of the time to include (the default is ``'auto'``).
It can be one of the following:
Expand Down Expand Up @@ -1997,6 +2021,9 @@ Instance methods:
.. versionchanged:: 3.6
Added the *timespec* parameter.

.. versionchanged:: next
Added the *basic* parameter.


.. method:: time.__str__()

Expand Down
12 changes: 12 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,18 @@ operator
(Contributed by Raymond Hettinger and Nico Mexis in :gh:`115808`.)


datetime
--------

* Add support for the ISO 8601 basic format for the following methods:

- :meth:`date.isoformat <datetime.date.isoformat>`
- :meth:`datetime.isoformat <datetime.datetime.isoformat>`
- :meth:`time.isoformat <datetime.time.isoformat>`

(Contributed by Bénédikt Tran in :gh:`118948`.)


os
--

Expand Down
74 changes: 49 additions & 25 deletions Lib/_pydatetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,23 @@ def _build_struct_time(y, m, d, hh, mm, ss, dstflag):
dnum = _days_before_month(y, m) + d
return _time.struct_time((y, m, d, hh, mm, ss, wday, dnum, dstflag))

def _format_time(hh, mm, ss, us, timespec='auto'):
specs = {
'hours': '{:02d}',
'minutes': '{:02d}:{:02d}',
'seconds': '{:02d}:{:02d}:{:02d}',
'milliseconds': '{:02d}:{:02d}:{:02d}.{:03d}',
'microseconds': '{:02d}:{:02d}:{:02d}.{:06d}'
}
def _format_time(hh, mm, ss, us, timespec='auto', basic=False):
if basic:
specs = {
'hours': '{:02d}',
'minutes': '{:02d}{:02d}',
'seconds': '{:02d}{:02d}{:02d}',
'milliseconds': '{:02d}{:02d}{:02d}.{:03d}',
'microseconds': '{:02d}{:02d}{:02d}.{:06d}'
}
else:
specs = {
'hours': '{:02d}',
'minutes': '{:02d}:{:02d}',
'seconds': '{:02d}:{:02d}:{:02d}',
'milliseconds': '{:02d}:{:02d}:{:02d}.{:03d}',
'microseconds': '{:02d}:{:02d}:{:02d}.{:06d}'
}

if timespec == 'auto':
# Skip trailing microseconds when us==0.
Expand Down Expand Up @@ -1101,16 +1110,18 @@ def __format__(self, fmt):
return self.strftime(fmt)
return str(self)

def isoformat(self):
"""Return the date formatted according to ISO.
def isoformat(self, basic=False):
"""Return the date formatted according to ISO 8601.

This is 'YYYY-MM-DD'.
This is 'YYYY-MM-DD' or 'YYYYMMDD' if *basic* is true.

References:
- http://www.w3.org/TR/NOTE-datetime
- http://www.cl.cam.ac.uk/~mgk25/iso-time.html
- https://www.w3.org/TR/NOTE-datetime
- https://www.cl.cam.ac.uk/~mgk25/iso-time.html
"""
return "%04d-%02d-%02d" % (self._year, self._month, self._day)
if basic:
return f"{self._year:04d}{self._month:02d}{self._day:02d}"
return f"{self._year:04d}-{self._month:02d}-{self._day:02d}"

__str__ = isoformat

Expand Down Expand Up @@ -1558,10 +1569,13 @@ def __hash__(self):

# Conversion to string

def _tzstr(self):
"""Return formatted timezone offset (+xx:xx) or an empty string."""
def _tzstr(self, basic):
"""Return formatted timezone offset (+xx:xx) or an empty string.
The colon separator is omitted if *basic* is true.
"""
off = self.utcoffset()
return _format_offset(off)
sep = '' if basic else ':'
return _format_offset(off, sep)

def __repr__(self):
"""Convert to formal string, for repr()."""
Expand All @@ -1582,19 +1596,21 @@ def __repr__(self):
s = s[:-1] + ", fold=1)"
return s

def isoformat(self, timespec='auto'):
def isoformat(self, timespec='auto', basic=False):
"""Return the time formatted according to ISO.

The full format is 'HH:MM:SS.mmmmmm+zz:zz'. By default, the fractional
part is omitted if self.microsecond == 0.

If *basic* is true, separators ':' are omitted.

The optional argument timespec specifies the number of additional
terms of the time to include. Valid options are 'auto', 'hours',
'minutes', 'seconds', 'milliseconds' and 'microseconds'.
"""
s = _format_time(self._hour, self._minute, self._second,
self._microsecond, timespec)
tz = self._tzstr()
self._microsecond, timespec, basic)
tz = self._tzstr(basic)
if tz:
s += tz
return s
Expand Down Expand Up @@ -2102,6 +2118,12 @@ def astimezone(self, tz=None):

# Ways to produce a string.

def _tzstr(self, basic):
"""Return formatted timezone offset (+xx:xx) or an empty string."""
off = self.utcoffset()
sep = '' if basic else ':'
return _format_offset(off, sep)

def ctime(self):
"Return ctime() style string."
weekday = self.toordinal() % 7 or 7
Expand All @@ -2112,12 +2134,14 @@ def ctime(self):
self._hour, self._minute, self._second,
self._year)

def isoformat(self, sep='T', timespec='auto'):
def isoformat(self, sep='T', timespec='auto', basic=False):
"""Return the time formatted according to ISO.

The full format looks like 'YYYY-MM-DD HH:MM:SS.mmmmmm'.
By default, the fractional part is omitted if self.microsecond == 0.

If *basic* is true, separators ':' and '-' are omitted.

If self.tzinfo is not None, the UTC offset is also attached, giving
giving a full format of 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM'.

Expand All @@ -2128,12 +2152,12 @@ def isoformat(self, sep='T', timespec='auto'):
terms of the time to include. Valid options are 'auto', 'hours',
'minutes', 'seconds', 'milliseconds' and 'microseconds'.
"""
s = ("%04d-%02d-%02d%c" % (self._year, self._month, self._day, sep) +
fmt = "%04d%02d%02d%c" if basic else "%04d-%02d-%02d%c"
s = (fmt % (self._year, self._month, self._day, sep) +
_format_time(self._hour, self._minute, self._second,
self._microsecond, timespec))
self._microsecond, timespec, basic))

off = self.utcoffset()
tz = _format_offset(off)
tz = self._tzstr(basic)
if tz:
s += tz

Expand Down
Loading