2010-09-15 14 views
22

Una biblioteca y un servicio web que estoy utilizando, comunica intervalos de tiempo en ISO 8601: PnYnMnDTnHnMnS. Quiero convertir esos formatos a segundos. Y viceversa. Los segundos son mucho más fáciles de calcular con.Analizar y crear ISO 8601 Intervalos de fecha y hora, como PT15M en PHP

Ejemplo valores de intervalo son:

  • PT1M o PT60S (1 minuto)
  • PT1H, PT60M o PT3600S (1 hora)

Necesito dos funciones: analizar a partir de tales valores a segundos: iso8601_interval_to_seconds() y a partir de segundos en tales intervalos: iso8601_interval_from_seconds().

Esto último es bastante simple, porque podría hacerse como `" PT {$ segundos} S ", simplemente pase segundos, en todo momento. Tal vez esto se puede hacer mejor con un analizador que cambia a H (hora) o M (minuto)?

La primera es más difícil, pero tal vez hay un truco con uno de los muchos convertidores de cadena a la fecha en PHP? Me encantaría aprender a usar esa función para los intervalos de análisis. O aprende una alternativa.

+0

Una precaución muy tarde ... P1MT convertir a segundos sería imposible a menos que sepa si va a convertir un mes con 28, 29, 30, o 31 días. De hecho, ni siquiera puedes convertir P1YT en 365 días sin equivocarte un año de cada cuatro. (Hay una razón por la cual los intervalos no se especifican en días o segundos en el estándar.) – Bevan

+0

@Bevan: The Answer by Lee entra en detalles con esto. TL; DR: necesitas una fecha de inicio. – berkes

+0

Ahh - gracias @berkes, me perdí que cuando hojeé la página (lo encontré mientras buscaba algo no relacionado). – Bevan

Respuesta

17

Parece que PHP 5.3's DateInterval es compatible con esto.

Si no puede usar 5.3, supongo que cualquier función de conversión de este tipo sabría cuántos segundos hay en un año, un mes, un día, una hora y un minuto. Luego, al convertir desde segundos, se dividiría en ese orden, cada vez tomando el módulo de la operación anterior, hasta que solo quedan < 60 segundos. Al convertir desde una representación de intervalo ISO 8601, debería ser trivial analizar la cadena y multiplicar cada elemento encontrado en consecuencia.

+1

DateInterval ('PT1H') funciona como un amuleto!Ahora, me gustaría buscar una forma más agradable de reconstruir estas cadenas, aparte de PTXXXS, todo en segundos :) – berkes

+0

⚠️ PHP 'DateInterval()' tiene un error que no permite fracciones en las representaciones ISO 8601 que analiza : https://bugs.php.net/bug.php?id=53831 –

0

Lo que se busca es DateTime :: diff

El objeto DateInterval que representa la diferencia entre las dos fechas o en caso de fallo como se ilustra a continuación:

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

sólo tiene que utilizar segundo lugar de las fechas.

Esto es de http://www.php.net/manual/en/datetime.diff.php

Para una referencia a DateInterval ver http://www.php.net/manual/en/class.dateinterval.php

+0

No veo cómo esto ayuda a convertir el PT10M, al igual que los formatos ISO 8601. – berkes

7

Tenga en cuenta que la conversión de duraciones que contienen días, meses y/o años de una duración igual segundos no se puede hacer con precisión sin saber una fecha/hora de inicio real.

Por ejemplo

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

Esto se debe a Septiembre ha 30 días, y octubre tiene 31 días. El mismo problema ocurre con la conversión de intervalos anuales, debido a años bisiestos y segundos intercalares. Los años bisiestos también introducen una mayor complejidad en la conversión del mes, ya que febrero es un día más largo en años bisiestos que en el resto. Los días son problemáticos en áreas donde se practica el horario de verano; un período de un día que se produce durante la transición de DST, en realidad es 1 hora más de lo que sería de otra manera.

dicho todo esto - se puede, por supuesto, comprimir los valores que contienen sólo Horas, Minutos y Segundos en valores que contienen sólo unos segundos. Sugiero que construyas un analizador simple para hacer el trabajo (o tal vez consideres una expresión regular).

Simplemente tenga en cuenta las trampas descritas anteriormente: hay dragones. Si tiene la intención de lidiar con Días, Meses y/o Años, necesita utilizar uno de los mecanismos integrados para hacer los cálculos en su caso en el contexto de una fecha/hora conocida. Como otros han mencionado: la clase DateInterval, en combinación con las funciones proporcionadas en la clase DateTime, es probablemente la forma más intuitiva de lidiar con esto. Pero eso solo está disponible en PHP versión 5.3.0 o superior.

Si usted tiene que trabajar con menos de V5.3.0, se puede tratar de construir algo en torno a esta pequeña joya:

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

strtotime no funcionará con el formato ISO 8601 directamente (por ejemplo P1Y1DT1S). , pero el formato que hace entender (1Year1Day1Second) no está muy lejos, sería una conversión bastante directa. (un poco "hacky" ... pero eso es PHP para ti).

buena suerte!

+0

Pensando en esto intuitivamente, esperaría que reescribir un número absoluto de segundos como años + meses + días + horas + minutos + segundos para hacerlo más legible no cambiará su valor real, lo que significa que siempre que haya un intervalo variable, el estándar se elegirá valor Entonces, durante meses, eso sería 30 días = 1 mes calendario. Estoy tratando de encontrar exactamente dónde especifican esto en el estándar en sí, pero se está haciendo tarde, y solo puedo ver el borrador final sin pagar: http://xml.coverpages.org/ISO-FDIS-8601.pdf – Fanis

+5

En primero parece que esa sería la respuesta intuitiva, pero si realmente piensas en ello, se vuelve evidente que las cosas realmente no pueden funcionar así. Resulta que el "tiempo calendario" (Días, Meses, Años) es tan importante como "tiempo absoluto" (Segundos, Minutos, Horas) - cuando usamos el tiempo calendario, nos referimos a algo fundamentalmente diferente que cuando usamos el tiempo absoluto. Por ejemplo: ¿Cuán separados están tus cumpleaños? Siempre 1 año, no siempre 8760 horas. Si la factura de la tarjeta de crédito del mes pasado fue fechada el 21 de agosto, ¿cuál será la fecha de este mes? 21 de septiembre. Siempre 1 mes, no 30 días. – Lee

8

strtotime no va a trabajar con el formato ISO 8601 directamente (por ejemplo P1Y1DT1S.), pero el formato que lo hace entender (1Year1Day1Second) no es demasiado lejos - sería una conversión bastante recta hacia adelante . (un poco "hacky" ... pero eso es PHP para ti).

Gracias, Lee, no sabía que Stretten aceptó este formato. Esta era la parte que faltaba de mi rompecabezas. Quizás mis funciones pueden completar su respuesta.

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 función también admite formatos negativos. -P10Y9MT7M5S devolverá una matriz como: [año] => -10 [mes] => -9 [día] => 0 [hora] => 0 [minuto] => -7 [segundo] = > -5 Si no se desea este comportamiento, pase falso como segundo parámetro. De esta forma, la función siempre devolverá valores positivos. El símbolo de mínimo/más seguirá estando disponible en la clave de resultado ['symbol'].

Y una pequeña actualización: Esta función usa la primera función para obtener la cantidad total de segundos.

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

Hola, ¿hay un método más sencillo ahora con PHP 5.4? No es que no quiera agradecerte mucho por esta función, pero tal vez haya un método más limpio. – andreszs

+0

Escribí esto hace siglos, jejeje. Creo que podría reescribir la función. Muy poco tiempo ahora, pero lo veré más tarde. –

+0

¿Puedes explicar los caracteres de tubería '|'? No son seguidos por una rama alternativa, pero tampoco van precedidos de un signo de interrogación '? |' Para unir las referencias entre sí. http://php.net/manual/de/regexp.reference.subpatterns.php No se necesita una alternativa vacía, porque se usa el cuantificador '?'. – CoDEmanX

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

Pruebas:

PT1H   - OK 
PT23M  - OK 
PT45S  - OK 
PT1H23M  - OK 
PT1H45S  - OK 
PT23M45S  - OK 
PT1H23M45S - OK 
Cuestiones relacionadas