parse_8601_duration


#!/usr/bin/perl 
#===============================================================================
#
#         FILE: parse_8601_duration
#
#        USAGE: ./parse_8601_duration [duration_expression...]
#
#  DESCRIPTION: Parses an ISO_8601 time duration to extract the relevant
#               fields. Note that it is fairly simplistic and does not
#               validate the duration expression very rigorously. For example
#               'P0YT' is invalid yet is accepted by the parser. However,
#               'P0Y2H' is rejected.
#
#      OPTIONS: ---
# REQUIREMENTS: ---
#         BUGS: ---
#        NOTES: This format can represent a number of months but contains no
#               means whereby these months can be resolved to days.
#               There's also some controversy about whether fields other than
#               seconds can be fractional. 'P0.5Y' seems like a good way to
#               represent half a year (though how many days it represents is
#               open to question). This script does not allow this.
#       AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
#      VERSION: 1.0
#      CREATED: 2013-07-04 17:20:09
#     REVISION: 2013-07-06 22:48:47
#
#===============================================================================

use 5.010;
use strict;
use warnings;

my $duration;

my $sign;
my @labels = qw{Years Months Days Hours Minutes Seconds};
my @fields;

my %iso_duration;

#
# Regular expressions for an integer and a decimal fraction
#
my $int  = qr{\d+};
my $frac = qr{\d+(?:\.\d+)?};

#
# Regular expression for the ISO 8601 duration. Uses Perl's extensions where
# (?:) is a non-capturing sub-expression and (?x) is the same as //x in the
# simpler form.  Only the 'P' is mandatory. The other numeric elements all
# need their designators.
#
my $re = qr{(?x)            # Enable embedded whitespace and comments
                        ^([+-]?)        # Assume the string begins with the optional sign
                        P               # The 'P' is mandatory
                        (?:($int)Y)?    # Years
                        (?:($int)M)?    # Months
                        (?:($int)D)?    # Days
                        (?:T            # The 'T' must exist if there's a time element
                        (?:($int)H)?    # Hours
                        (?:($int)M)?    # Minutes
                        (?:($frac)S)?   # Seconds
                        )?              # The time element's optional
                        $};             # Assume there's nothing after the duration string

#
# Loop through the arguments
#
while ( $duration = shift ) {
    print "$duration\n";

    #
    # Perform the comparison
    #
    if ( ( $sign, @fields ) = ( $duration =~ $re ) ) {
        #
        # Optional sign defaults to '+'
        #
        $sign = '+' unless $sign;

        #
        # Force all undefined fields to be zero
        #
        @fields = map { defined($_) ? $_ : 0 } @fields;

        #
        # Build a duration hash (just to show how to do it in a cool Perly way)
        #
        $iso_duration{sign} = $sign;
        @iso_duration{@labels} = @fields;

        #
        # Display the sign and the fields in the right order
        #
        printf "%-7s: %6s\n","Sign",$sign;
        for ( my $i = 0; $i <= $#fields; $i++ ) {
            printf "%-7s: %6.2f\n", $labels[$i], $fields[$i];
        }
    }
    else {
        #
        # The expression didn't match the regex
        #
        print "Invalid duration: $duration\n";
    }
    print "\n";
}

exit;

# vim: syntax=perl:ts=8:sw=4:et:ai:tw=78:fo=tcrqn21:fdm=marker