2009-05-04 3 views
11

Heylo,Как рассчитать количество дней между двумя датами в Perl?

Я хочу рассчитать (используя только установку по умолчанию Perl) количество дней между двумя датами. Формат обоих дат выглядит так 04-MAY-09. (DD-MMM-YY)

Я не смог найти учебники, в которых обсуждался формат даты. Должен ли я создавать настраиваемый счетчик даты для этого формата? Дальнейшее чтение Date::Calc на CPAN выглядит маловероятным, что этот формат поддерживается.

Спасибо.

+2

Вы не можете просто преобразовать даты в секунды и рассчитать разницу? – TStamper

+1

Пожалуйста, не повторяйте ошибки вычисления разницы в секундах. См. [Мой ответ] (http://stackoverflow.com/a/26732755/664132) для примера. – basic6

ответ

1

Вы можете преобразовать даты в длинный целочисленный формат, который представляет собой количество секунд с эпохи (какая-то дата в 1970 году, я думаю). Затем у вас есть две переменные, которые являются датами в секундах; вычесть меньше из большего. Теперь у вас есть временной интервал в секундах; разделите его на количество секунд в течение 24 часов.

+0

Эпоха UNIX/C - 1 января 1970 г. 00:00:00 UTC. – Powerlord

+0

Это даст вам количество блоков времени в 86400 секунд между двумя датами, что не совпадает с количеством (календарных) дней между двумя датами. (Хорошо, на самом деле это не то же самое, что и 86400-секундные блоки, из-за прыжков секунд ...) –

+0

@John: Флаг DST включен и выключается хуже, чем прыжки секунд. – tchrist

5

Time :: ParseDate будет обрабатывать этот формат просто отлично:

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

Время :: ParseDate не является частью Core Perl –

+2

Очевидно, что упоминание о Date :: Calc в вопросе подразумевает честную игру CPAN. –

+0

Вопрос также говорит о том, что «использование только установки по умолчанию perl» –

4

Date :: Calc имеет Decode_Date_EU (и США и т.д.)

#!/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

Дата :: Calc не является частью Core Perl –

1

Преобразовать две даты в секунды, а затем сделать math:

#!/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

Если вы заботитесь о точности, имейте в виду, что не все дни имеют 86400 секунд. Любое решение, основанное на этом предположении, не будет правильным для некоторых случаев.

Вот фрагмент, который я просматриваю, чтобы рассчитать и отобразить разницу даты и времени несколькими различными способами, используя библиотеку DateTime. Думаю, последний ответ напечатан - это тот, который вы хотите.

#!/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

Это не всегда работает точно, см. [Мой ответ] (http://stackoverflow.com/questions/821423/how-can-i-calculate-the-number- из-дней-между-двумя датами-в-Perl/7111718 # 7111718). –

+0

Это * делает * работу точно (вверх). Вызов subtract_datetime_absolute() может возвращать количество секунд, которое не кратно 86400 (например, 255600! = (3 * 86400 = 259200) в [моем примере] (http://stackoverflow.com/a/26732755/664132)). Вызов delta_md() возвращает фактическую разницу в днях. Вначале может показаться странным, что вы используете переменную $ dur для двух разных вещей (абсолютная разница во времени и разность календарных дат). Оп попросил несколько дней, поэтому разница в дате - интересная часть вашего ответа. – basic6

10

Там, кажется, совсем немного путаницы, потому что, в зависимости от того, что вы пытаетесь достичь, «количество дней между двумя датами» может означать, по меньшей мере, две разные вещи:

  1. calendar расстояние между двумя датами.
  2. абсолютное расстояние между двумя датами.

В качестве примера и отметить разницу, предположим, что у вас есть два объекта DateTime, построенные следующим образом:

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'); 

Обратите внимание, что $dt1 довольно поздно во вторник, в то время как $dt2 является очень рано в следующую пятницу.

Если вы хотите календарь расстояние использование:

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

Действительно, между, во вторник и в пятницу 3 дня.Календарное расстояние 1 означает «завтра», а расстояние -1 означает «вчера». «Временная» часть объектов DateTime в основном не имеет значения (за исключением, возможно, если эти две даты выпадают на разные часовые пояса, тогда вам нужно будет решить, что должно означать «расстояние между календарями» между этими двумя датами).

Если вы хотите абсолютного расстояния затем вместо этого использовать:

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

В самом деле, если вы хотите разделить время между двумя датами в 24-часовых порциях, есть только около 2,07 дней между ними. В зависимости от вашего приложения вы можете обрезать или округлить это число. «Временная» часть объектов DateTime имеет значение очень, и ожидаемый результат хорошо определен даже для дат в разных часовых поясах.

+1

Я не уверен, что вызов 'int' количества дней, чтобы округлить к нулю, прав, но поскольку это зависит от того, что он делает с этим, я не знаю, что правильно. Иногда я вижу «пол()» (который для позитивов дает то же, что и вы), иногда 'ceil()', иногда 'sprintf '% .0f" ', а иногда просто сохраняя float. Но их легко настроить в соответствии с его потребностями. Трудно сказать, что вы не можете использовать CPAN, но можете запросить код. Найти: код требует, чтобы он сосал все «DateTime» в свою программу с помощью вырезания и вставки, наихудшего типа повторного использования кода. Увы! – tchrist

+0

I downvoted - переход от зимы к лету, разница в секундах/86400 может составлять 2,95, что НЕ является 3. Резка фракции (что делает int()), превращается 2,95 в 2, что не является 3 и, следовательно, является неправильным. См. [Мой ответ] (http://stackoverflow.com/a/26732755/664132) для примера, демонстрирующего это. – basic6

+1

Также объясните, почему 'days, delta_days или in_units ('days') не работают' - вы указываете' subtract_datetime_absolute', но это вычисляет абсолютную разницу в секундах и наносекундах, чего мы не хотим. – basic6

4

Этот вопрос уже имеет хороший answer, но я хочу, чтобы предоставить ответ, показывающий почему вычисления разницы в секундах НЕПРАВИЛЬНО (когда мы используем, отформатированные/местные даты, а не плавающие даты).

Я нахожу, что это огорчает, сколько предложений говорят людям, чтобы вычесть секунды. (Этот вопрос стал первым хитом Google для моего поиска, поэтому мне все равно, сколько ему лет.)

Я сам сделал эту ошибку и задался вопросом, почему приложение внезапно (за выходные) покажет неиспользуемые времена , Поэтому я надеюсь, что этот код поможет людям (кто может столкнуться с такой проблемой) понять, почему этот подход неверен и помочь им избежать этой ошибки.

Вот полный пример, который не содержит «...» в какой-то ключевой момент (потому что если вы вставляете две даты в том же часовом поясе, вы можете не увидеть ошибку).

#!/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"; 

Выход:

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) 

Примечания:

  • Опять же, я использую местные даты. Если вы используете плавающие даты, у вас не будет этой проблемы - просто потому, что ваши даты остаются в одном и том же часовом поясе.
  • Оба диапазон времени в моем примере идет от пятницы до понедельника, так что разница в дни 3, а не 3,04 ... и, конечно, не 2,95 ...
  • Включения поплавка в целое число, используя int() (как это было предложено в answer) просто неверно, как показано в примере.
  • Я понимаю, что округление разницы в секундах также вернет правильные результаты в моем примере, но я чувствую, что это все еще неправильно. Вы будете вычислять разницу в день 2 (при большом значении 2) и, поскольку это большое значение 2, превратите его в 3. До тех пор, пока DateTime предоставляет функциональность, используйте DateTime.

Цитирование documentation (delta_days() против subtract_datetime()):

дата против даты и времени по математике

Если вы заботитесь только о дате (календарь) части даты-времени, вы следует использовать либо delta_md(), либо delta_days(), а не subtract_datetime(). Это даст предсказуемые, неудивительные результаты, свободные от осложнения, связанные с ДСТ.

Нижняя строка: не используйте интервалы секунд, если вы используете DateTime. Если вы не знаете, какую фреймворк даты использовать, используйте DateTime, это потрясающе.

Смежные вопросы