Как я могу рассчитать количество дней между двумя датами в Perl? - PullRequest
14 голосов
/ 04 мая 2009

HEYLO,

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

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

Спасибо.

Ответы [ 7 ]

16 голосов
/ 05 мая 2009

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

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

  1. календарное расстояние между двумя датами.
  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 дней. В зависимости от вашего приложения вы можете усечь или округлить это число. Часть «time» объектов DateTime очень актуальна, и ожидаемый результат четко определен даже для дат в разных часовых поясах.

5 голосов
/ 04 мая 2009

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);
4 голосов
/ 04 ноября 2014

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

Меня огорчает, как много предложений говорят людям вычитать секунды. (Этот вопрос был первым попаданием 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.

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

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

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

Итог: не различайте секунды, если вы используете DateTime. Если вы не уверены, какую платформу даты использовать, используйте DateTime, это здорово.

4 голосов
/ 04 мая 2009

Дата :: 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);
1 голос
/ 05 мая 2009

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

#!/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";
1 голос
/ 04 мая 2009

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

...