Разбор и создание ISO 8601 Дата и интервалы времени, как PT15M в PHP - PullRequest
26 голосов
/ 15 сентября 2010

Используемая мной библиотека и веб-служба сообщают временные интервалы в формате ISO 8601: PnYnMnDTnHnMnS.Я хочу конвертировать такие форматы в секунды.И наоборот.Секунды намного легче вычислить с помощью.

Примерные значения интервалов:

  • PT1M или PT60S (1 минута)
  • PT1H, PT60M или PT3600S (1 час)

Мне нужны две функции: разбор таких значений в секунды: iso8601_interval_to_seconds() и из секунд в такие интервалы: iso8601_interval_from_seconds().

Последнее довольно просто, потому что ономожно сделать как `" PT {$ seconds} S ", просто передавая секунды, все время.Может быть, это можно сделать лучше с парсером, который переключается на H (час) или M (минута)?

Первый сложнее, но, может быть, есть хитрость с одним из многих преобразователей строк в дату в PHP?Я хотел бы узнать, как использовать такую ​​функцию для разбора интервалов.Или выучить альтернативу.

Ответы [ 5 ]

17 голосов
/ 15 сентября 2010

Похоже, что PHP 5.3 DateInterval поддерживает это.

Если вы не можете использовать 5.3, я полагаю, что любая такая функция преобразования будет знать, сколько секунд в году, месяце, дне, часе и минуте. Затем при преобразовании из секунд он делится в этом порядке, каждый раз принимая модуль предыдущей операции, до тех пор, пока не останется только <60 секунд. При преобразовании из представления интервала ISO 8601 должно быть тривиально проанализировать строку и соответственно умножить каждый найденный элемент. </p>

8 голосов
/ 15 декабря 2012

strtotime не будет работать напрямую с форматом ISO 8601 (например, P1Y1DT1S), но формат, который он понимает (1Year1Day1Second), не слишком далеко - это было бы довольно прямым преобразованием. (немного "хакер" ... но для вас это PHP).

Спасибо, Ли, я не знал, что strtotime принял этот формат. Это была недостающая часть моей загадки. Возможно, мои функции могут завершить ваш ответ.

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;
    }
}

Функция также поддерживает негативные форматы. -P10Y9MT7M5S вернет массив наподобие: [год] => -10 [месяц] => -9 [день] => 0 [час] => 0 [минута] => -7 [секунда] => -5 Если это поведение не желаемый проход false в качестве второго параметра. Таким образом, функция всегда будет возвращать положительные значения. Символ мин / плюс все еще будет доступен в ключе результата ['symbol'].

И небольшое обновление: Эта функция использует первую функцию для получения общего количества секунд.

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
7 голосов
/ 16 сентября 2010

Имейте в виду, что преобразование длительностей, содержащих дни, месяцы и / или годы, в длительность, например секунды, не может быть выполнено точно без знания фактической даты / времени начала.

Например

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

Это потому, что в сентябре 30 дней, а в октябре 31 день. Та же проблема возникает с преобразованием интервалов года из-за високосных лет и високосных секунд. Високосные годы также усложняют конвертацию Месяца - поскольку февраль в високосные годы на один день длиннее, чем в противном случае. Дни являются проблематичными в районах, где практикуется летнее время - однодневный период, происходящий во время перехода на летнее время, фактически на 1 час длиннее, чем он был бы в противном случае.

Все, что говорится - вы, конечно, можете сжать значения, содержащие только Часы, минуты и секунды, в значения, содержащие только секунды. Я бы посоветовал вам создать простой парсер для выполнения работы (или, возможно, рассмотреть регулярное выражение).

Просто знайте о ловушках, описанных выше - там будут драконы. Если вы намереваетесь иметь дело с днями, месяцами и / или годами, вам нужно использовать один из встроенных механизмов, чтобы выполнить математику для вас в контексте известной даты / времени. Как уже упоминали другие: класс DateInterval в сочетании с функциями, предоставляемыми в классе DateTime, является, вероятно, наиболее интуитивным способом решения этой проблемы. Но это доступно только в PHP версии 5.3.0 или выше.

Если вам нужно работать с версией ниже v5.3.0, вы можете попытаться построить что-нибудь вокруг этой маленькой жемчужины:

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

strtotime не будет работать с форматом ISO 8601 напрямую (например, P1Y1DT1S), но формат, который он понимает понимает (1Year1Day1Second), не слишком далек - он было бы довольно простым преобразованием. (немного "хакерский" ... но для вас это PHP).

удачи!

1 голос
/ 12 мая 2017
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;
}

Тесты:

PT1H         - OK
PT23M        - OK
PT45S        - OK
PT1H23M      - OK
PT1H45S      - OK
PT23M45S     - OK
PT1H23M45S   - OK
0 голосов
/ 15 сентября 2010

Что вам нужно, так это DateTime :: diff

Объект DateInterval, представляющий разницу между двумя датами или FALSE в случае неудачи, как показано ниже:

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

Просто используйте секунды вместодат.

Это от http://www.php.net/manual/en/datetime.diff.php

Для ссылки на DateInterval см. http://www.php.net/manual/en/class.dateinterval.php

...