web-dev-qa-db-fra.com

Analyser et créer des intervalles de date et d'heure ISO 8601, comme PT15M dans PHP

Une bibliothèque et un service Web que j'utilise communiquent des intervalles de temps au format ISO 8601: PnYnMnDTnHnMnS . Je veux convertir ces formats en secondes. Et vice versa. Les secondes sont beaucoup plus faciles à calculer.

Exemples de valeurs d'intervalle:

  • PT1M ou PT60S (1 minute)
  • PT1H, PT60M ou PT3600S (1 heure)

J'ai besoin de deux fonctions: analyser de telles valeurs en secondes: iso8601_interval_to_seconds() et des secondes dans de tels intervalles: iso8601_interval_from_seconds().

Ce dernier est assez simple, car il pourrait être fait en tant que "" PT {$ seconds} S ", il suffit de passer les secondes à tout moment. Peut-être que cela peut être mieux fait avec un analyseur qui passe à H(hour) ou M (minute)?

Le premier est plus difficile, mais il y a peut-être une astuce avec l'un des nombreux convertisseurs chaîne à date en PHP? J'aimerais apprendre à utiliser une telle fonction pour analyser les intervalles. Ou apprenez une alternative.

28
berkes

Il ressemble à PHP 5.3's DateInterval supporte cela.

Si vous ne pouvez pas utiliser 5.3, je suppose qu'une telle fonction de conversion connaîtra le nombre de secondes dans une année, un mois, un jour, une heure et une minute. Ensuite, lors de la conversion à partir des secondes, il se divisait dans cet ordre, en prenant à chaque fois le module de l'opération précédente, jusqu'à ce qu'il ne reste que <60 secondes. Lors de la conversion à partir d'une représentation d'intervalle ISO 8601, il devrait être trivial d'analyser la chaîne et de multiplier chaque élément trouvé en conséquence.

18
Fanis

strtotime ne fonctionnera pas directement avec le format ISO 8601 (par exemple P1Y1DT1S), mais le format qu'il comprend (1Year1Day1Second) n'est pas trop éloigné - ce serait une conversion assez simple. (un peu "hacky" ... mais c'est PHP pour vous).

Merci Lee, je n'étais pas au courant que strtotime avait accepté ce format. C'était la partie manquante de mon puzzle. Peut-être que mes fonctions peuvent compléter votre réponse.

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

La fonction prend également en charge les formats négatifs. - P10Y9MT7M5S renverra un tableau comme: [année] => -10 [mois] => -9 [jour] => 0 [heure] => 0 [minute] => -7 [seconde] => -5 Si ce comportement n'est pas souhaité, passez false comme deuxième paramètre. De cette façon, la fonction retournera toujours des valeurs positives. Le symbole min/plus sera toujours disponible dans la clé de résultat ['symbole'].

Et une petite mise à jour: cette fonction utilise la première fonction pour obtenir le nombre total de secondes.

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
8
user1441149

Sachez que la conversion de durées qui contiennent des jours, des mois et/ou des années en une durée comme les secondes ne peut pas être effectuée avec précision sans connaître une date/heure de début réelle.

Par exemple

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

C'est parce que septembre a 30 jours et octobre a 31 jours. Le même problème se produit avec la conversion des intervalles d'année, en raison des années bissextiles et des secondes bissextiles. Les années bissextiles introduisent également une complexité supplémentaire dans la conversion du mois - puisque février est un jour de plus en années bissextiles qu'il ne l'est autrement. Les jours sont problématiques dans les zones où l'heure d'été est pratiquée - une période d'une journée se produisant pendant la transition de l'heure d'été, est en réalité d'une heure de plus qu'elle ne le serait autrement.

Tout cela étant dit - vous pouvez, bien sûr, compresser des valeurs contenant niquement heures, minutes et secondes en valeurs contenant uniquement des secondes. Je vous suggère de créer un analyseur simple pour faire le travail (ou peut-être envisager une expression régulière).

Soyez juste conscient des pièges décrits ci-dessus - il y a des dragons. Si vous avez l'intention de gérer les jours, les mois et/ou les années, vous devez utiliser l'un des mécanismes intégrés pour effectuer les calculs à votre place dans le contexte d'une date/heure connue. Comme d'autres l'ont mentionné: la classe DateInterval, en combinaison avec les fonctions fournies sur la classe DateTime est probablement la façon la plus intuitive de gérer cela. Mais cela n'est disponible qu'en PHP version 5.3.0 ou supérieure.

Si vous devez travailler avec moins de v5.3.0, vous pouvez essayer de construire quelque chose autour de ce petit bijou:

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

strtotime ne fonctionnera pas directement avec le format ISO 8601 (par exemple. P1Y1DT1S), mais le format qu'il comprend comprend (1Year1Day1Second) n'est pas trop loin - ce serait une conversion assez simple. (un peu "hacky" ... mais c'est PHP pour vous).

bonne chance!

7
Lee
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
1
Mantas D

Ce que vous cherchez c'est DateTime :: diff

L'objet DateInterval représentant la différence entre les deux dates ou FALSE en cas d'échec, comme illustré ci-dessous:

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

Utilisez simplement les secondes au lieu des dates.

Cela vient de http://www.php.net/manual/en/datetime.diff.php

Pour une référence à DateInterval, voir http://www.php.net/manual/en/class.dateinterval.php

0
Todd Moses