8000 gh-118948: add support for ISO 8601 basic format to ``datetime`` by picnixz · Pull Request #120553 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-118948: add support for ISO 8601 basic format to datetime #120553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
44 changes: 38 additions & 6 deletions Doc/library/datetime.rst
10000
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:: 3.14
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:: 3.14
Added the *basic* parameter.


.. method:: datetime.__str__()

Expand Down Expand Up @@ -1954,15 +1976,22 @@ 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, one of:

- ``HHMMSS``
- ``HHMMSS.ffffff``
- ``HHMMSS+HHMM[SS[.ffffff]]``
- ``HHMMSS.ffffff+HHMM[SS[.ffffff]]``

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 +2026,9 @@ Instance methods:
.. versionchanged:: 3.6
Added the *timespec* parameter.

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


.. method:: time.__str__()

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


datetime
--------

* Add support for 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
64 changes: 43 additions & 21 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):
def isoformat(self, basic=False):
"""Return the date formatted according to ISO.

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
"""
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,11 @@ def __hash__(self):

# Conversion to string

def _tzstr(self):
def _tzstr(self, basic):
"""Return formatted timezone offset (+xx:xx) or an empty string."""
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 +1594,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 removed from the output.

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 +2116,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 +2132,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 removed from the output.

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 +2150,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
Loading
0