17

Given a datetime.timedelta, how to convert it into an ISO 8601 duration string?

For example, given datetime.timedelta(0, 18, 179651), how do I get 'PT18.179651S'?


For the reverse, see Is there an easy way to convert ISO 8601 duration to timedelta?.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Drake Guan
  • 14,514
  • 15
  • 67
  • 94
  • 5
    [The `isodate` module on PyPI](https://pypi.python.org/pypi/isodate) can do that. You can [look into its source code](https://github.com/gweis/isodate/blob/master/src/isodate/isostrf.py#L122) to see how it's implemented. – Carsten Nov 27 '14 at 10:46
  • 1
    @Carsten It works quite well through isodate: `isodate.duration_isoformat(timedelta)`. Thanks. – Drake Guan Nov 27 '14 at 14:41

3 Answers3

12

Although the datetime module contains an implementation for a ISO 8601 notation for datetime or date objects, it does not currently (Python 3.7) support the same for timedelta objects. However, the isodate module (pypi link) has functionality to generate a duration string in ISO 8601 notation:

In [15]: import isodate, datetime

In [16]: print(isodate.duration_isoformat(datetime.datetime.now() - datetime.datetime(1985, 8, 13, 15)))
P12148DT4H20M39.47017S

which means 12148 days, 4 hours, 20 minutes, 39.47017 seconds.

gerrit
  • 24,025
  • 17
  • 97
  • 170
5

This is a function from Tin Can Python project (Apache License 2.0) that can do the conversion:

def iso8601(value):
    # split seconds to larger units
    seconds = value.total_seconds()
    minutes, seconds = divmod(seconds, 60)
    hours, minutes = divmod(minutes, 60)
    days, hours = divmod(hours, 24)
    days, hours, minutes = map(int, (days, hours, minutes))
    seconds = round(seconds, 6)

    ## build date
    date = ''
    if days:
        date = '%sD' % days

    ## build time
    time = u'T'
    # hours
    bigger_exists = date or hours
    if bigger_exists:
        time += '{:02}H'.format(hours)
    # minutes
    bigger_exists = bigger_exists or minutes
    if bigger_exists:
      time += '{:02}M'.format(minutes)
    # seconds
    if seconds.is_integer():
        seconds = '{:02}'.format(int(seconds))
    else:
        # 9 chars long w/leading 0, 6 digits after decimal
        seconds = '%09.6f' % seconds
    # remove trailing zeros
    seconds = seconds.rstrip('0')
    time += '{}S'.format(seconds)
    return u'P' + date + time

E.g.

>>> iso8601(datetime.timedelta(0, 18, 179651))
'PT18.179651S'
manlio
  • 18,345
  • 14
  • 76
  • 126
1

I tried to write a shorter function:

def iso8601(tdelta):
    ts = tdelta.total_seconds()
    d = int(ts // 86400)
    s = round(ts % 60, 6)
    hms = int(ts // 3600 % 24), int(ts // 60 % 60), s if s % 1 != 0 else int(s)
    t = ''.join([str(p[0]) + p[1] for p in zip(hms, ['H', 'M', 'S']) if p[0]])
    sep = 'T' if any(hms) else ''
    return 'P' + (str(d) + 'D' if d else '') + sep + (t if ts else 'T0S')

E.g.

>>> iso8601(datetime.timedelta(0, 18, 179651))
'PT18.179651S'

This may be less readable for some, but I think it's easier to take it apart and inline it in certain use cases. For example, if you're not using timedelta but already have the duration in integer seconds ts and, say, your durations are always shorter than a day, this becomes:

hms = ts // 3600, ts // 60 % 60, ts % 60
t = ''.join([str(p[0]) + p[1] for p in zip(hms, ['H', 'M', 'S']) if p[0]])
ts_iso8601 = 'PT' + t if ts else 'PT0S'
naktinis
  • 3,957
  • 3
  • 36
  • 52
  • 1
    I personally find your code very hard to read. longer variable names would be a big improvement – Neuron Mar 16 '21 at 09:08
  • I totally agree, and even mention that in the answer. The function (first block) is not something I'd use in production verbatim, it's more of an exercise in brevity that leads to the subset three-line solution (third block), which could be tolerable (or easily clarified) in certain cases. – naktinis Mar 16 '21 at 10:18
  • 1
    yea, but don't forget that a lot of people just blindly copy & paste SO answers and many beginners will adapt this coding style. That's why I personally try to stick to production quality code on SO as much as possible. – Neuron Mar 16 '21 at 11:55