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

HEYLO,

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

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

Спасибо.

Ответ 1

Если вы заботитесь о точности, имейте в виду, что не все дни имеют 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";

Ответ 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 очень важна, и ожидаемый результат хорошо определен даже для дат в разных часовых поясах.

Ответ 3

Время:: 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);

Ответ 4

Дата:: Calc имеет Decode_Date_EU (и US и т.д.)

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

Ответ 5

Этот вопрос уже имеет приятный ответ, но я хочу предоставить ответ, показывающий , почему вычисление разницы в секундах WRONG (когда мы используем отформатированные/локальные даты, а не плавающие даты).

Мне кажется, что это огорчает, сколько предложений подсказывают людям вычитать секунды. (Этот вопрос был первым хитом 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...
  • Превращение float в целое число с помощью int() (как предложено в answer) просто неверно, как показано в примере.
  • Я понимаю, что округление разницы в секундах также вернет правильные результаты в моем примере, но я чувствую, что все еще неправильно. Вы будете вычислять разницу в день 2 (при большом значении 2) и, поскольку это большое значение 2, превратите его в 3. Так что, пока DateTime предоставляет эту функцию, используйте DateTime.

Цитирование документации (delta_days() vs subtract_datetime()):

date vs datetime math

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

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

Ответ 6

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

Ответ 7

Преобразуйте две даты в секундах, а затем выполните математику:

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