How can I parse and compare ISO 8601 duration in Python?
I am looking for a library in Python (v2) that will allow me to parse and compare ISO 8601 duration, which can be in different units
Ideally this would work with standard operators (a <b), but I would be cool with something like a.compare (b) or however.
so something like:
duration('P23M') < duration('P2Y') //True
duration('P25M') < duration('P2Y') //False
I installed isodate from PyPi, but it has its own class for periods that include month and years, and they don't compare against itself or timedeltas
source to share
The way I ended up "resolving" was by comparing the resulting values if the period was added to a value containing the current UTC time. This, of course, means that in February the P30D is longer than the P1M, in March it is shorter, and in April it is equal. Not perfect, but met my needs enough.
source to share
here's a bit of fiction with a duration (one month is 30 days, one year is average, etc.):
# parse 8601 duration
from re import findall
def iso8601_duration_as_seconds( d ):
if d[0] != 'P':
raise ValueError('Not an ISO 8601 Duration string')
seconds = 0
# split by the 'T'
for i, item in enumerate(d.split('T')):
for number, unit in findall( '(?P<number>\d+)(?P<period>S|M|H|D|W|Y)', item ):
# print '%s -> %s %s' % (d, number, unit )
number = int(number)
this = 0
if unit == 'Y':
this = number * 31557600 # 365.25
elif unit == 'W':
this = number * 604800
elif unit == 'D':
this = number * 86400
elif unit == 'H':
this = number * 3600
elif unit == 'M':
# ambiguity ellivated with index i
if i == 0:
this = number * 2678400 # assume 30 days
# print "MONTH!"
else:
this = number * 60
elif unit == 'S':
this = number
seconds = seconds + this
return seconds
for d in [ 'PT10M', 'PT5H', 'P3D', 'PT45S', 'P8W', 'P7Y', 'PT5H10M', 'P2YT3H10M', 'P3Y6M4DT12H30M5S', 'P23M', 'P2Y' ]:
seconds = iso8601_duration_as_seconds( d )
print "%s \t= %s" % (d,seconds)
print
print '%s' % (iso8601_duration_as_seconds('P23M') < iso8601_duration_as_seconds('P2Y') )
# True
print '%s' % (iso8601_duration_as_seconds('P25M') < iso8601_duration_as_seconds('P2Y') )
# False
source to share
Faced with the same problem, I resorted to regular expressions to analyze the duration and compare the resulting number of seconds:
def duration(duration_str):
match = re.match(
r'P(?P<years>\d+)Y)?(?P<months>\d+)M)?(?P<weeks>\d+)W)?(?P<days>\d+)D)?T((?P<hours>\d+)H)?((?P<minutes>\d+)M)?((?P<seconds>\d+)S)?',
duration_str
).groupdict()
return int(match['years'] or 0)*365*24*3600 + \
int(match['months'] or 0)*30*24*3600 + \
int(match['weeks'] or 0)*7*24*3600 + \
int(match['days'] or 0)*24*3600 + \
int(match['hours'] or 0)*3600 + \
int(match['minutes'] or 0)*60 + \
int(match['seconds'] or 0)
Note that this assumes a year is 365 days, a month is 30 days, and so on.
source to share