如何计算Perl中两个日期之间的天数?

时间:2009-05-04 19:00:28

标签: perl datetime date time days

Heylo,

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

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

感谢。

7 个答案:

答案 0 :(得分:16)

如果您关心准确性,请记住并非所有日子都有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 :(得分:12)

似乎有很多混乱,因为根据你想要完成的事情,“两个日期之间的天数”至少可能意味着两件事:

  1. 两个日期之间的 日历距离
  2. 两个日期之间的 绝对距离
  3. 作为示例并注意区别,假设您有两个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对象的“时间”部分与非常相关,即使对于不同时区的日期,也可以很好地定义预期结果。

答案 2 :(得分: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);

答案 3 :(得分:4)

Date :: 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);

答案 4 :(得分: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。 / LI>

引用documentation(delta_days()vs subtract_datetime()):

  

日期与日期时间数学

     

如果您只关心日期时间的日期(日历)部分,那么   应该使用delta_md()或delta_days(),而不是subtract_datetime()。   这将带来可预测的,不令人惊讶的结果,免于   与DST相关的并发症。

底线:如果您正在使用DateTime,请不要seconds秒。如果您不确定要使用的日期框架,请使用DateTime,它很棒。

答案 5 :(得分:1)

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

答案 6 :(得分: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";