2009-05-04 31 views
11

Heylo,Come posso calcolare il numero di giorni tra due date in Perl?

Voglio calcolare (utilizzando solo l'installazione Perl predefinita) il numero di giorni tra due date. Il formato di entrambe le date sono come così 04-MAG-09. (DD-MMM-YY)

Non sono riuscito a trovare alcun tutorial che ha discusso tale formato di data. Dovrei creare un controllo data personalizzato per questo formato? Ulteriori letture di Date::Calc su CPAN sembra improbabile che questo formato sia supportato.

Grazie.

+2

non puoi semplicemente convertire le date in secondi e calcolare la differenza? – TStamper

+1

Si prega di non ripetere l'errore di calcolare la differenza in secondi. Vedi [la mia risposta] (http://stackoverflow.com/a/26732755/664132) per un esempio. – basic6

risposta

1

È possibile convertire le date nel formato intero lungo, ovvero il numero di secondi dall'epoca (una data del 1970 credo). Quindi hai due variabili che sono le date in secondi; sottrarre il più piccolo dal più grande. Ora hai un intervallo di tempo in secondi; dividerlo per il numero di secondi in 24 ore.

+0

L'epoca UNIX/C è il 1 gennaio 1970 00:00:00 UTC. – Powerlord

+0

Questo ti darà il numero di blocchi di tempo di 86400 secondi tra due date, che non è la stessa cosa del numero di giorni (di calendario) tra due date. (Va bene, in realtà non è nemmeno la stessa cosa dei blocchi da 86400 secondi, a causa dei secondi bisestili ...) –

+0

@John: Il flag DST che si accende e si spegne è peggiore dei secondi bisestili. – tchrist

5

Time :: parsedate gestirà quel formato più che bene:

use Time::ParseDate qw(parsedate); 

$d1="04-MAR-09"; 
$d2="06-MAR-09"; 

printf "%d days difference\n", (parsedate($d2) - parsedate($d1))/(60 * 60 * 24); 
+0

Time :: ParseDate non fa parte di Core Perl –

+2

Ovviamente la menzione di Date :: Calc nella domanda implica il fair game di CPAN. –

+0

La domanda dice anche "utilizzando solo l'installazione di Perl predefinita" –

4

Date :: Calc ha Decode_Date_EU (e gli Stati Uniti, ecc)

#!/usr/bin/perl 
use Date::Calc qw(Delta_Days Decode_Date_EU); 

($year1,$month1,$day1) = Decode_Date_EU('02-MAY-09'); 
($year2,$month2,$day2) = Decode_Date_EU('04-MAY-09'); 

print "Diff = " . Delta_Days($year1,$month1,$day1, $year2,$month2,$day2); 
+0

Date :: Calc non fa parte di Core Perl –

1

Convertire le due date per secondi e poi fare il matematica:

#!/usr/bin/perl 

use strict; 
use warnings; 
use POSIX qw/mktime/; 

{ 

    my %mon = (
     JAN => 0, 
     FEB => 1, 
     MAR => 2, 
     APR => 3, 
     MAY => 4, 
     JUN => 5, 
     JUL => 6, 
     AUG => 7, 
     SEP => 8, 
     OCT => 9, 
     NOV => 10, 
     DEC => 11, 
    ); 

    sub date_to_seconds { 
     my $date = shift; 
     my ($day, $month, $year) = split /-/, $date; 

     $month = $mon{$month}; 
     if ($year < 50) { #or whatever your cutoff is 
      $year += 100; #make it 20?? 
     } 

     #return midnight on the day in question in 
     #seconds since the epoch 
     return mktime 0, 0, 0, $day, $month, $year; 
    } 
} 

my $d1 = "04-MAY-99"; 
my $d2 = "04-MAY-00"; 

my $s1 = date_to_seconds $d1; 
my $s2 = date_to_seconds $d2; 

my $days = int(($s2 - $s1)/(24*60*60)); 

print "there are $days days between $d1 and $d2\n"; 
15

Se vi preoccupate per la precisione, tenere a mente che non tutti i giorni hanno 86400 secondi. Qualsiasi soluzione basata su tale ipotesi non sarà corretta per alcuni casi.

Ecco uno snippet che continuo a calcolare e visualizzare le differenze di data/ora in vari modi utilizzando la libreria DateTime. L'ultima risposta stampata è quella che vuoi, credo.

#!/usr/bin/perl -w 

use strict; 

use DateTime; 
use DateTime::Format::Duration; 

# XXX: Create your two dates here 
my $d1 = DateTime->new(...); 
my $d2 = DateTime->new(...); 

my $dur = ($d1 > $d2 ? ($d1->subtract_datetime_absolute($d2)) : 
         ($d2->subtract_datetime_absolute($d1))); 

my $f = DateTime::Format::Duration->new(pattern => 
    '%Y years, %m months, %e days, %H hours, %M minutes, %S seconds'); 

print $f->format_duration($dur), "\n"; 

$dur = $d1->delta_md($d2); 

my $dy = int($dur->delta_months/12); 
my $dm = $dur->delta_months % 12; 
print "$dy years $dm months ", $dur->delta_days, " days\n"; 
print $dur->delta_months, " months ", $dur->delta_days, " days\n"; 
print $d1->delta_days($d2)->delta_days, " days\n"; 
+1

Questo non funziona sempre in modo accurato, vedere [risposta] (http://stackoverflow.com/questions/821423/how-can-i-calculate-the-number- del-giorno-tra-due-date-in-perl/7111718 # 7111718). –

+0

Questo * funziona * con precisione (upvoted). La chiamata subtract_datetime_absolute() può restituire un numero di secondi che non è un multiplo di 86400 (come 255600! = (3 * 86400 = 259200) in [il mio esempio] (http://stackoverflow.com/a/26732755/664132)). La chiamata delta_md() restituisce la differenza effettiva in giorni. Inizialmente potresti confondere che stai usando la variabile $ dur per 2 cose diverse (differenza di fuso orario e differenza di calendario). L'operatore ha chiesto giorni, quindi la differenza di data è la parte interessante della risposta. – basic6

10

Sembra che ci sia un bel po 'di confusione perché, a seconda di ciò che si sta cercando di realizzare, “il numero di giorni tra due date” può significare almeno due cose diverse:

  1. Il distanza dal calendario tra le due date.
  2. distanza assoluta tra le due date.

A titolo di esempio e notare la differenza, si supponga di avere due oggetti DateTime costruiti come segue:

use DateTime; 

sub iso8601_date { 
    die unless $_[0] =~ m/^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/; 
    return DateTime->new(year => $1, month => $2, day => $3, 
    hour => $4, minute => $5, second => $6, time_zone => 'UTC'); 
} 

my $dt1 = iso8601_date('2014-11-04T23:35:42Z'); 
my $dt2 = iso8601_date('2014-11-07T01:15:18Z'); 

Nota che $dt1 è piuttosto in ritardo di Martedì, mentre $dt2 è molto presto il venerdì successivo.

Se si desidera che il calendario di distanza uso:

my $days = $dt2->delta_days($dt1)->delta_days(); 
print "$days\n" # -> 3 

Infatti, tra, Martedì e Venerdì ci sono 3 giorni.Una distanza del calendario di 1 significa "domani" e una distanza di -1 significa "ieri". La parte "tempo" degli oggetti DateTime è per lo più irrilevante (eccetto forse se le due date ricadono su fusi orari diversi, allora dovresti decidere quale "dovrebbe essere la distanza del calendario" tra quelle due date).

Se si desidera che la distanza assoluta poi invece utilizzare:

my $days = $dt2->subtract_datetime_absolute($dt1)->delta_seconds/(24*60*60); 
print "$days\n"; # -> 2.06916666666667 

In effetti, se si desidera dividere il tempo tra le due date in blocchi di 24 ore, ci sono solo circa 2,07 giorni fra loro. A seconda dell'applicazione, è possibile che si desideri troncare o arrotondare questo numero. La parte "tempo" degli oggetti DateTime è molto rilevante e il risultato previsto è ben definito anche per date su fusi orari diversi.

+1

Non sono sicuro che chiamare "int" sul numero di giorni da arrotondare a zero sia corretto, ma dal momento che dipende da ciò che sta facendo con esso, non saprei nemmeno cosa sia giusto. A volte capisco di volere il 'floor()' (che per i positivi dà lo stesso risultato ottenuto), a volte il 'ceil()', a volte 'sprintf"% .0f "', ea volte solo mantenendo il float. Ma quelli sono facili da regolare in base alle sue esigenze. La cosa difficile è che non puoi usare CPAN ma puoi sollecitare il codice. Trova: il codice richiede che succhi tutto il 'DateTime' nel suo programma con taglia e incolla, il peggior tipo di riutilizzo del codice. Ahimè! – tchrist

+0

I downvoted - passando dall'inverno all'estate, la differenza in secondi/86400 può essere 2.95, che NON è 3. Il taglio della frazione (cosa int() fa gira 2.95 in 2, che non è 3 e quindi sbagliato. Vedi [la mia risposta] (http://stackoverflow.com/a/26732755/664132) per un esempio che dimostra questo. – basic6

+1

Inoltre, si prega di spiegare perché 'days, delta_days o in_units ('days') non funzionerà' - si cita' sottrarre_datetime_assoluta', ma questo calcola la differenza assoluta in secondi e nanosecondi, che non è quello che vogliamo. – basic6

4

Questa domanda ha già una bella answer, ma voglio dare una risposta che mostra perché calcolando la differenza in secondi è SBAGLIATO (quando stiamo utilizzando formattati/date locali piuttosto che galleggiano le date).

Trovo doloroso quanti suggerimenti suggeriscono alle persone di sottrarre secondi. (Questa domanda è stata la prima hit di Google per la mia ricerca, quindi non mi interessa quanti anni ha.)

Ho fatto quell'errore io stesso e mi sono chiesto perché l'applicazione avrebbe improvvisamente (durante il fine settimana) mostrare tempi incorrenti . Quindi spero che questo codice possa aiutare le persone (che potrebbero trovarsi di fronte a questo problema) a capire perché questo approccio è sbagliato e aiutarli a evitare questo errore.

Ecco un esempio completo , uno che non contiene "..." in un punto cruciale (perché se si inseriscono due date nello stesso fuso orario, potrebbe non essere visualizzato un errore).

#!/usr/bin/env perl 

use strict; 
use warnings; 

use Data::Dumper; 
use DateTime; 

# Friday, Oct 31 
my $dt1 = DateTime->new(
    time_zone => "America/Chicago", 
    year => 2014, 
    month => 10, 
    day => 31, 
); 
my $date1 = $dt1->strftime("%Y-%m-%d (%Z %z)"); 

# Monday, Nov 01 
my $dt2 = $dt1->clone->set(month => 11, day => 3); 
my $date2 = $dt2->strftime("%Y-%m-%d (%Z %z)"); 

# Friday, Mar 06 
my $dt3 = DateTime->new(
    time_zone => "America/Chicago", 
    year => 2015, 
    month => 3, 
    day => 6, 
); 
my $date3 = $dt3->strftime("%Y-%m-%d (%Z %z)"); 

# Monday, Mar 09 
my $dt4 = $dt3->clone->set(day => 9); 
my $date4 = $dt4->strftime("%Y-%m-%d (%Z %z)"); 

# CDT -> CST 
print "dt1:\t$dt1 ($date1):\t".$dt1->epoch."\n"; 
print "dt2:\t$dt2 ($date2):\t".$dt2->epoch."\n"; 
my $diff1_duration = $dt2->subtract_datetime_absolute($dt1); 
my $diff1_seconds = $diff1_duration->seconds; 
my $diff1_seconds_days = $diff1_seconds/86400; 
print "diff:\t$diff1_seconds seconds = $diff1_seconds_days days (WRONG)\n"; 
my $diff1_seconds_days_int = int($diff1_seconds_days); 
print "int:\t$diff1_seconds_days_int days (RIGHT in this case)\n"; 
print "days\t".$dt2->delta_days($dt1)->days." days (RIGHT)\n"; 
print "\n"; 

# CST -> CDT 
print "dt3:\t$dt3 ($date3):\t".$dt3->epoch."\n"; 
print "dt4:\t$dt4 ($date4):\t".$dt4->epoch."\n"; 
my $diff3_duration = $dt4->subtract_datetime_absolute($dt3); 
my $diff3_seconds = $diff3_duration->seconds; 
my $diff3_seconds_days = $diff3_seconds/86400; 
print "diff:\t$diff3_seconds seconds = $diff3_seconds_days days (WRONG)\n"; 
my $diff3_seconds_days_int = int($diff3_seconds_days); 
print "int:\t$diff3_seconds_days_int days (WRONG!!)\n"; 
print "days\t".$dt4->delta_days($dt3)->days." days (RIGHT)\n"; 
print "\n"; 

uscita:

dt1: 2014-10-31T00:00:00 (2014-10-31 (CDT -0500)): 1414731600 
dt2: 2014-11-03T00:00:00 (2014-11-03 (CST -0600)): 1414994400 
diff: 262800 seconds = 3.04166666666667 days (WRONG) 
int: 3 days (RIGHT in this case) 
days 3 days (RIGHT) 

dt3: 2015-03-06T00:00:00 (2015-03-06 (CST -0600)): 1425621600 
dt4: 2015-03-09T00:00:00 (2015-03-09 (CDT -0500)): 1425877200 
diff: 255600 seconds = 2.95833333333333 days (WRONG) 
int: 2 days (WRONG!!) 
days 3 days (RIGHT) 

Note:

  • Anche in questo caso, sto usando date locali. Se utilizzi le date floating, non avrai questo problema, semplicemente perché le tue date rimangono nello stesso fuso orario.
  • Sia intervalli di tempo nel mio esempio andare da venerdì a lunedi, quindi la differenza in giorni è 3, non 3.04 ... e naturalmente non 2,95 ...
  • Girando il galleggiante in numero intero usando int() (come suggerito in un answer) è semplicemente sbagliato, come mostrato nell'esempio.
  • Mi rendo conto che arrotondare la differenza in secondi potrebbe anche restituire risultati corretti nel mio esempio, ma mi sembra che sia ancora sbagliato. Dovresti calcolare una differenza di giorno di 2 (per un valore elevato di 2) e, poiché è un valore elevato di 2, trasformalo in 3. Quindi, se DateTime fornisce la funzionalità, utilizza DateTime.

Citando le documentation (delta_days() vs subtract_datetime()):

data vs datetime matematica

Se vi interessa soltanto la parte di data (calendario) di un datetime, si dovrebbe usare delta_md() o delta_days(), non subtract_datetime(). Ciò fornirà risultati prevedibili e non prevedibili, esenti da complicazioni relative alla DST .

Bottom line: non diff secondi se si utilizza DateTime. Se non sei sicuro di quale data utilizzare, usa DateTime, è fantastico.

Problemi correlati