21

我想计算(仅使用默认的 Perl 安装)两个日期之间的天数。两个日期的格式都是 04-MAY-09。(DD-MMM-YY)

我找不到任何讨论该日期格式的教程。我应该为此格式构建自定义日期检查器吗?进一步阅读 CPAN 上的Date::Calc似乎不太可能支持这种格式。

4

7 回答 7

19

如果您关心准确性,请记住并非所有日子都有 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";
于 2009-05-04T20:57:23.683 回答
19

似乎有些混乱,因为根据您要完成的任务,“两个日期之间的天数”可能意味着至少两个不同的东西:

  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 天。根据您的应用程序,您可能希望截断或舍入此数字。DateTime 对象的“时间”部分非常相关,即使对于不同时区的日期,预期结果也得到了很好的定义。

于 2011-08-18T17:40:59.680 回答
5

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);
于 2009-05-04T19:54:50.900 回答
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);
于 2009-05-04T19:25:11.567 回答
5

这个问题已经有一个很好的答案,但我想提供一个答案,说明为什么计算以秒为单位的差异是错误的(当我们使用格式化/本地日期而不是浮动日期时)。

我发现有多少建议告诉人们要减去秒数,这让我很痛苦。(这个问题是我搜索的第一个 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()将浮点数转换为整数(如答案中所建议)是错误的,如示例所示。
  • 我确实意识到在我的示例中以秒为单位的差异也会返回​​正确的结果,但我觉得它仍然是错误的。您将计算 2 的天差(对于 2 的大值),并且因为它是 2 的大值,所以将其转换为 3。所以只要 DateTime 提供功能,就使用 DateTime。

引用文档(delta_days() 与 subtract_datetime()):

日期与日期时间数学

如果您只关心日期时间的日期(日历)部分,则应使用 delta_md() 或 delta_days(),而不是 subtract_datetime()。这将产生可预测的、不出所料的结果,不会出现与 DST 相关的并发症。

底线:如果您使用的是 DateTime,请不要区分秒数。如果您不确定要使用什么日期框架,请使用 DateTime,它很棒。

于 2014-11-04T10:23:03.997 回答
1

将两个日期转换为秒,然后进行数学运算:

#!/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";
于 2009-05-04T20:26:31.263 回答
1

您可以将日期转换为长整数格式,即自纪元以来的秒数(我认为是 1970 年的某个日期)。然后,您有两个变量,它们是以秒为单位的日期;从较大的减去较小的。现在你有一个以秒为单位的时间跨度;除以 24 小时内的秒数。

于 2009-05-04T19:06:19.583 回答