Given a datetime.timedelta, how to convert it into a string of ISO 8601 duration format?
Ex,
>>> iso8601(datetime.timedelta(0, 18, 179651))
'PT18.179651S'
Given a datetime.timedelta, how to convert it into a string of ISO 8601 duration format?
Ex,
>>> iso8601(datetime.timedelta(0, 18, 179651))
'PT18.179651S'
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.
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'
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'