Parse and create ISO 8601 Date and time intervals, like PT15M in PHP

前端 未结 5 407
無奈伤痛
無奈伤痛 2020-12-29 21:23

A library and webservice I am using communicates time-intervals in ISO 8601 format: PnYnMnDTnHnMnS. I want to convert such formats to seconds. And vice versa. Seconds are a

相关标签:
5条回答
  • 2020-12-29 21:36

    What you are looking for is DateTime::diff

    The DateInterval object representing the difference between the two dates or FALSE on failure as illustrated below:

    $datetime1 = new DateTime('2009-10-11');
    $datetime2 = new DateTime('2009-10-13');
    $interval = $datetime1->diff($datetime2);
    echo $interval->format('%R%d days');
    

    Just use seconds instead of dates.

    This is from http://www.php.net/manual/en/datetime.diff.php

    For a reference to DateInterval see http://www.php.net/manual/en/class.dateinterval.php

    0 讨论(0)
  • 2020-12-29 21:38

    strtotime won't work with the ISO 8601 format directly (eg. P1Y1DT1S), but the format that it does understand (1Year1Day1Second) is not too far off -- it would a pretty straight-forward conversion. (a little "hacky"... but that's PHP for you).

    Thanks Lee, I was not aware strtotime accepted this format. This was the missing part of my puzzle. Perhaps my functions can complete your answer.

    function parse_duration($iso_duration, $allow_negative = true){
        // Parse duration parts
        $matches = array();
        preg_match('/^(-|)?P([0-9]+Y|)?([0-9]+M|)?([0-9]+D|)?T?([0-9]+H|)?([0-9]+M|)?([0-9]+S|)?$/', $iso_duration, $matches);
        if(!empty($matches)){       
            // Strip all but digits and -
            foreach($matches as &$match){
                $match = preg_replace('/((?!([0-9]|-)).)*/', '', $match);
            }   
            // Fetch min/plus symbol
            $result['symbol'] = ($matches[1] == '-') ? $matches[1] : '+'; // May be needed for actions outside this function.
            // Fetch duration parts
            $m = ($allow_negative) ? $matches[1] : '';
            $result['year']   = intval($m.$matches[2]);
            $result['month']  = intval($m.$matches[3]);
            $result['day']    = intval($m.$matches[4]);
            $result['hour']   = intval($m.$matches[5]);
            $result['minute'] = intval($m.$matches[6]);
            $result['second'] = intval($m.$matches[7]);     
            return $result; 
        }
        else{
            return false;
        }
    }
    

    The function also supports negative formats. -P10Y9MT7M5S will return an array like: [year] => -10 [month] => -9 [day] => 0 [hour] => 0 [minute] => -7 [second] => -5 If this behaviour is not desired pass false as second parameter. This way the function will always return positive values. The min/plus symbol will still be available in result key ['symbol'].

    And a little update: This function uses the first function to get the total amount of seconds.

    function get_duration_seconds($iso_duration){
        // Get duration parts
        $duration = parse_duration($iso_duration, false);
        if($duration){
            extract($duration);
            $dparam  = $symbol; // plus/min symbol
            $dparam .= (!empty($year)) ? $year . 'Year' : '';
            $dparam .= (!empty($month)) ? $month . 'Month' : '';
            $dparam .= (!empty($day)) ? $day . 'Day' : '';
            $dparam .= (!empty($hour)) ? $hour . 'Hour' : '';
            $dparam .= (!empty($minute)) ? $minute . 'Minute' : '';
            $dparam .= (!empty($second)) ? $second . 'Second' : '';
            $date = '19700101UTC';
            return strtotime($date.$dparam) - strtotime($date);
        }
        else{
            // Not a valid iso duration
            return false;
        }
    }
    
    $foo = '-P1DT1S';
    echo get_duration_seconds($foo); // Output -86399
    $bar = 'P1DT1S';
    echo get_duration_seconds($bar); // Output 86401
    
    0 讨论(0)
  • 2020-12-29 21:41

    It looks like PHP 5.3's DateInterval supports this.

    If you can't use 5.3, I suppose any such conversion function would know how many seconds are in a year, a month, a day, an hour, and a minute. Then, when converting from seconds, it would divide in that order, each time taking the modulo of the previous operation, until only <60 seconds are left. When converting from an ISO 8601 interval representation it should be trivial to parse the string and multiply each found element accordingly.

    0 讨论(0)
  • 2020-12-29 21:45
    function parsePTTime($pt_time)
    {
        $string = str_replace('PT', '', $pt_time);
        $string = str_replace('H', 'Hour', $string);
        $string = str_replace('M', 'Minute', $string);
        $string = str_replace('S', 'Second', $string);
    
        $startDateTime = '19700101UTC';
        $seconds = strtotime($startDateTime . '+' . $string) - strtotime($startDateTime);
    
        return $seconds;
    }
    

    Tests:

    PT1H         - OK
    PT23M        - OK
    PT45S        - OK
    PT1H23M      - OK
    PT1H45S      - OK
    PT23M45S     - OK
    PT1H23M45S   - OK
    
    0 讨论(0)
  • 2020-12-29 21:49

    Be aware that converting durations that contain Days, Months, and/or Years into a duration like seconds can not be done accurately without knowing an actual starting date/time.

    For Example

    1 SEP 2010:  +P1M2D means +2764800 seconds
    1 OCT 2010:  +P1M2D means +2851200 seconds
    

    That's because September has 30-days, and October has 31-days. The same problem occurs with converting Year intervals, because of leap-years and leap-seconds. Leap-years introduce further complexity to the Month conversion as well - since February is one day longer in leap-years than it is otherwise. Days are problematic in areas where daylight saving time is practiced - a one-day period occurring during the DST transition, is actually 1-hour longer than it would be otherwise.

    All that being said -- you can, of course, compress values containing only Hours, Minutes, and Seconds into values containing just Seconds. I'd suggest that you build a simple parser to do the job (or maybe consider a regular expression).

    Just be aware of the pitfalls outlined above -- there there be dragons. If you intend to deal with Days, Months, and/or Years, you need to use one of the built-in mechanisms to do the math for you in the context of a known date/time. As others have mentioned: the DateInterval class, in combination withe the functions provided on the DateTime class is probably the most intuitive way to deal with this. But that's only available in PHP version 5.3.0 or greater.

    If you have to work with less than v5.3.0, you can try to build something around this little gem:

    $startDateTime = '19700101UTC';
    $duration = strtotime( $startDateTime.'+1Year1Day1Second' ) - strtotime($startDateTime);
    print("Duration in Seconds:  $duration");
    

    strtotime won't work with the ISO 8601 format directly (eg. P1Y1DT1S), but the format that it does understand (1Year1Day1Second) is not too far off -- it would a pretty straight-forward conversion. (a little "hacky"... but that's PHP for you).

    good luck!

    0 讨论(0)
提交回复
热议问题