2012-01-24 16 views
5

Ecco quello che ho finora:Come calcolare la differenza tra due giorni come una stringa formattata?

/** 
* Parse a duration between 2 date/times in seconds 
* and to convert that duration into a formatted string 
* 
* @param integer $time_start start time in seconds 
* @param integer $time_end end time in seconds 
* @param string $format  like the php strftime formatting uses %y %m %w %d %h or %i. 
* @param boolean $chop  chop off sections that have 0 values 
*/ 
public static function FormatDateDiff($time_start = 0, $time_end = 0, $format = "%s", $chop = false) { 

     if($time_start > $time_end) list($time_start, $time_end) = array($time_end, $time_start); 

     list($year_start,$month_start,$day_start) = explode('-',date('Y-m-d',$time_start)); 
     list($year_end,$month_end,$day_end) = explode('-',date('Y-m-d',$time_end)); 

     $years = $year_end - $year_start; 
     $months = $month_end - $month_start; 
     $days = $day_start - $day_end; 
     $weeks = 0; 
     $hours = 0; 
     $mins = 0; 
     $secs = 0; 

     if(mktime(0,0,0,$month_end,$day_end) < mktime(0,0,0,$month_start,$day_start)) { 
      $years -= 1; 
     } 
     if($days < 0) { 
      $months -= 1; 
      $days += 30; // this is an approximation...not sure how to figure this out 
     } 
     if($months < 0) $months += 12; 
     if(strpos($format, '%y')===false) { 
      $months += $years * 12; 
     } 
     if(strpos($format, '%w')!==false) { 
      $weeks = floor($days/7); 
      $days %= 7; 
     } 
     echo date('Y-m-d',$time_start).' to '.date('Y-m-d',$time_end).": {$years}y {$months}m {$weeks}w {$days}d<br/>"; 
} 

(E 'incompleta e imprecisa)

io non riesco a ottenere la matematica destra. Dividerlo in modo creativo non funzionerà a causa degli anni bisestili e delle lunghezze di mesi diverse.

La logica deve anche cambiare in base alla stringa di formato. Ad esempio, passando da 04-Feb-2010 a 28-giu-2011 (come timestamp unix) con la stringa di formato %y year %m month %d day dovrebbe essere emesso 1 year 4 month 24 day ma se %y year viene omesso, è necessario aggiungere 12 mesi al mese, ad esempio, l'output deve essere 16 month 24 day.

Dovrebbe gestire anche i tempi ... ma non ce l'ho ancora.


Nessuna di queste soluzioni date_diff gestire settimane. E non so come potrei modificarlo in date_diff, quindi non è davvero una soluzione per me.

Inoltre, $diff->format non fa ciò che ho chiesto ... per indicare i mesi ei giorni totali se "unità più grandi" sono omesse. Esempio:

>>> $start = new DateTime('04-Feb-2010') 
>>> $end = new DateTime('28-Jun-2011') 
>>> $diff = $start->diff($end) 
>>> $diff->format('%m months, %d days') 
'4 months, 24 days' 

Dovrebbe essere 16 months, 24 days, come ho detto in precedenza. Per favore, smettila di essere così veloce da chiudere la mia domanda come un inganno prima di comprenderlo completamente. Se le soluzioni ad altre domande possono essere ottimizzate per risolvere questo, bene, ma per favore spiega come, perché non capisco.

Per essere chiari,

  • se %y è omesso, anni dovrebbero essere rotolati nei mesi
  • se %m viene omesso, mesi dovrebbero essere rotolato in giorni
  • se %w è omesso, settimane deve essere inserito nei giorni
  • se si omette %h, le ore devono essere riportate in minuti
  • se %m è omesso, i minuti devono essere inseriti in secondi

Se "unità più piccole" vengono omesse, l'unità successiva più grande può essere arrotondata o pavimentata dove ha senso.

+1

Se si dispone di PHP 5.3.0 o superiore, questo potrebbe aiutare a: [DateInterval :: format] (http://www.php.net/manual/en/dateinterval.format.php) – drew010

+2

duplicato di [questo post] (http: // StackOverflow.it/questions/676824/how-to-calculate-the-difference-between-two-dates-using-php) – bowlerae

+0

@ drew010: No ... non gestisce le settimane. – mpen

risposta

1

Non mi aspetto una risposta accettata, ma ecco come ottenere date_diff per fare settimane.

<?php 

$january = new DateTime('2010-01-01'); 
$february = new DateTime('2011-02-20 3:35:28'); 
$interval = $february->diff($january); 

$parts = $interval->format('%y %m %d %h %i %s %a'); 

$weeks = 0; 
list($years, $months, $days, $hours, $minutes, $seconds, $total_days) = explode(' ', $parts); 

if ($days >= 7) { 
    $weeks = (int)($days/7); 
    $days %= 7; 
} 

echo "$years years, $months months, $weeks weeks, $days days, $hours hours, $minutes minutes $seconds seconds"; 
// 1 years, 1 months, 2 weeks, 5 days, 3 hours, 35 minutes 28 seconds 

Magari con che è possibile integrare nella vostra funzione per fare il ribaltamento e la gestione del formato user data.

Se non vengono fornite le unità più grandi, è possibile iniziare dall'unità più grande e applicarle all'unità più piccola successiva. (Ad esempio 1 anno 1 mese senza anni dovrebbe aggiungere 12 di nuovo a mesi). Se "mese" non è incluso nel formato, puoi utilizzare i giorni totali per gestire il fatto che i mesi hanno un numero diverso di giorni.

+0

Bello! Proverò a giocare con questo e vedere se riesco a farlo fare quello che voglio. Sembra promettente. – mpen

+0

Speriamo che tu possa arrivare da qualche parte con esso. Cerco sempre di affidarmi a metodi di data "incorporati" come quello ora a causa di così tanti problemi quando si tratta di osservare i confini dell'ora legale, gli anni bisestili/secondi e diversi giorni in ogni mese e così via. – drew010

+0

Sì .... Stavo armeggiando con 'DateTime' prima, ma c'erano ancora alcuni problemi con esso, quindi ho detto" Vaffanculo! Lo scriverò io stesso! ". Evidentemente questo era l'approccio sbagliato. Il tuo è bello e pulito però. Lo accetterò :) – mpen

1
$absolute = false; //default, returns years, months, days, etc. True would return seconds 
$difference = date_diff($dateTimeStart, $dateTimeEnd, $absolute); 
echo $difference->format("%y Year(s) %m Month(s) ..."); 

vorrei provare questo primo, se non funziona per i vostri scopi, allora si potrebbe tentare di utilizzare le funzioni di calendario per andare a prendere il numero corretto di giorni di ciascun mese.

Penso che avresti bisogno di decidere la tua logica per calcolare le settimane. Personalmente, direi $weeks = $difference->d/7; ma non esiste un modo strettamente corretto per contare le settimane trascorse in una porzione di un mese. Pensa a un calendario, spesso parliamo di prima, seconda, terza settimana, ma a meno che il mese sia iniziato di domenica (o lunedì o sabato, a seconda della compagnia, della religione, ecc.), In realtà non siamo assoluti in queste descrizioni .Puoi dire assolutamente che ci sono state 3 domeniche (ad esempio) dall'inizio del mese, ma non tre settimane. Il 28, sono trascorse quattro settimane? E se il mese fosse iniziato mercoledì, allora è il cinque?

Inoltre, se si desidera un formato personalizzato (35 mesi) è sempre possibile accedere direttamente ai membri e concatenare una stringa di formato.

+0

'date_diff' non gestisce le settimane. Intendi calcolare il numero di giorni in ogni mese tra le 2 date e sommarle o qualcosa del genere? O solo il mese di inizio e fine ..? – mpen

+1

Hai ragione, non è così. Mi dispiace di non aver notato quel requisito. – Tim

+0

Va bene. Quel requisito era sepolto nel codice; Avrei dovuto renderlo più chiaro. Tuttavia, non gestirà il requisito delle "unità più grandi". – mpen

1

userei DateTime::diff() per questo:

$start = new DateTime('2012-01-01 12:00:00'); 
$end = new DateTime('2012-01-20 06:59:59'); 
$diff = $start->diff($end); 

echo $diff->format('%d days, %h hours, %m minutes, %s seconds'); 

Vedi DateInterval::format() per ulteriori informazioni sui formati.

+0

Che non gestisce settimane. – mpen

1

Io penso li ho:

if($time_start > $time_end) list($time_start, $time_end) = array($time_end, $time_start); 

$start_dt = new DateTime(); 
$end_dt = new DateTime(); 
$start_dt->setTimestamp($time_start); 
$end_dt->setTimestamp($time_end); 
$has_time = preg_match('`%[his]`',$format) > 0; 
if(!$has_time) { 
    $start_dt->setTime(0,0,0); 
    $end_dt->setTime(0,0,0); 
} 
$interval = $end_dt->diff($start_dt); 
$parts = $interval->format('%y %m %d %h %i %s %a'); 
$weeks = 0; 
list($years, $months, $days, $hours, $mins, $secs, $total_days) = explode(' ',$parts); 
if(strpos($format,'%y')===false) { 
    $months += $years * 12; 
} 
if(strpos($format,'%m')===false) { 
    $days = $total_days; 
    if(strpos($format,'%y')!==false) { 
     $start_dt->add(new DateInterval('P'.$years.'Y')); 
     $interval = $end_dt->diff($start_dt); 
     $days = $interval->days; 
    } 
} 
if(strpos($format,'%w')!==false) { 
    $weeks = (int)($days/7); 
    $days %= 7; 
} 
if(strpos($format,'%d')===false) { 
    $hours += $days * 24; 
} 
if(strpos($format,'%h')===false) { 
    $mins += $hours * 60; 
} 
if(strpos($format,'%i')===false) { 
    $secs += $mins * 60; 
} 

(FYI, ho solo fare un po 'str_replacing base alla fine di gettare nella mia stringa di formato personalizzato)

Edit: C'è ancora un altro scenario che non so come gestire ... quando sono richiesti anni e giorni ma non mesi, l'output sarà sbagliato. Mi chiedo se dovrei mod mod i giorni totali con 365 .... è una specie di scenario raro.

Edit2: Risolto! Aggiungeremo solo molti anni alla data di inizio e quindi ricalcolare i giorni totali.

+1

Bella soluzione Mark, pulito e facile da leggere fino in fondo. Sembra che tu sia quasi arrivato. – drew010

1

Ecco il mio tentativo di un problema interessante. Non funziona del tutto al 100%, ma è molto vicino.

La funzione inizia calcolando il numero totale di secondi e mesi richiesti dalla differenza di data. Quindi, usando l'array $tokens che è ordinato in ordine ascendente, scorre attraverso quei token e cerca di farlo corrispondere all'input $format. Se viene trovato lo $format, il valore appropriato viene sottratto dal numero totale di mesi o secondi.

Tuttavia, esiste la possibilità di avere un valore maggiore di zero per mesi o anni, ma la stringa $format non ha specificato nessuno di questi parametri. In tal caso, la funzione esegue il rollover sul numero di giorni appropriato per ottenere i calcoli corretti.

L'ho provato brevemente e funziona con tutti gli esempi in questa discussione. Puoi provarlo su codepad per vedere se riesci a romperlo! :) Sarei interessato a miglioramenti su questo.

<?php 

function calc($start, $end, $format = '%s', $chop = false) 
{ 
    $tokens = array('%y', '%m', '%w', '%d', '%h', '%i', '%s'); 

    if(!is_a($start, 'DateTime') || !is_a($end, 'DateTime')) 
    { 
     return; 
    } 
    $diff = $start->diff($end); 

    $months = ($diff->y * 12) + $diff->m; 
    $secs = ($diff->d * 24 * 3600) + 
      ($diff->h * 3600) + 
      ($diff->i * 60) + 
      ($diff->s); 

    $output = array(); 
    while($token = array_shift($tokens)) 
    { 
     $token_present = !(strpos($format, $token) === false); 

     switch($token) 
     { 
      case '%y': 
       if(($months/12) > 0 && $token_present) 
       { 
        $output[$token] = floor($months/12); 
        $months -= $output[$token] * 12; 
       } 
      break; 
      case '%m': 
       if($months > 0 && $token_present) 
       { 
        $output[$token] = $months; 
        $months = 0; 
       } 
      break; 
      case '%w': 
       // Rollover between (months or years) and seconds 
       if((!isset($output['%y']) || !isset($output['%m'])) && $months > 0) 
       { 
        $days = $diff->format('%a'); 
       // Need a fix for leap year probably. 
       $days -= (isset($output['%y'])) ? ($output['%y'] * 365) : 0; 
       $days -= $diff->d; 
       $secs += ($days * 24 * 60 * 60); 
       } 

       $val = (7 * 24 * 60 * 60); 
       if(($secs/$val) > 0 && $token_present) 
       { 
        $output[$token] = floor($secs/$val); 
        $secs -= $output[$token] * $val; 
       } 
      break; 
      case '%d': 
       $val = (24 * 60 * 60); 
       if(($secs/$val) > 0 && $token_present) 
       { 
        $output[$token] = floor($secs/$val); 
        $secs -= $output[$token] * $val; 
       } 
      break; 
      case '%h': 
       $val = (60 * 60); 
       if(($secs/$val) > 0 && $token_present) 
       { 
        $output[$token] = floor($secs/$val); 
        $secs -= $output[$token] * $val; 
       } 
      break; 
      case '%i': 
       $val = (60); 
       if(($secs/$val) > 0 && $token_present) 
       { 
        $output[$token] = floor($secs/$val); 
        $secs -= $output[$token] * $val; 
       } 
      break; 
      case '%s': 
       if($secs > 0 && $token_present) 
       { 
        $output[$token] = $secs; 
       } 
      break; 
     } 
    } 

    // Filter out blank keys and replace their tokens in the $format string 
    $filtered = $chop ? array_filter($output) : $output; 
    $format = str_replace(array_diff(array_keys($output), array_keys($filtered)), '', $format); 

    return str_replace(array_keys($filtered), array_values($filtered), $format); 
} 

$start = new DateTime('04-Feb-2010'); 
$end = new DateTime('28-Jun-2011'); 
echo calc($start, $end, "%m months %d days\n"); // 16 months 24 days 

$january = new DateTime('2010-01-01'); 
$february = new DateTime('2011-02-20 3:35:28'); 
echo calc($january, $february, '%y years %m months %w weeks %d days %h hours %i minutes %s seconds'); // 1 years 1 months 2 weeks 5 days 3 hours 35 minutes 28 seconds 
+0

L'ho rotto: http://codepad.viper-7.com/lc16ez – mpen

+0

@Mark Ho notato che l'errore come ho postato la risposta ... Penso che questo corregge: http://codepad.viper-7.com/T7qehe – nickb

+0

Sì..questo è lo stesso risultato che ottengo ora, tuttavia penso che tu abbia ancora degli errori di arrotondamento, in particolare per gli anni bisestili. Non mi piace quel "365" in là;) La mia soluzione è attualmente in fondo a questa pagina BTW. – mpen

Problemi correlati