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

+3


source to share


3 answers


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.



0


source


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

      

+6


source


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.

0


source







All Articles