How can I calculate the number of days between two dates in Perl?

前端 未结 7 2305
长发绾君心
长发绾君心 2020-12-05 19:05

I want to calculate (using the default Perl installation only) the number of days between two dates. The format of both the dates are like so 04-MAY-09. (DD-MMM-YY)

I

相关标签:
7条回答
  • 2020-12-05 19:27

    Date::Calc has Decode_Date_EU (and US etc)

    #!/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 讨论(0)
  • 2020-12-05 19:30

    There seems to be quite a bit of confusion because, depending on what you are trying to accomplish, “the number of days between two dates” can mean at least two different things:

    1. The calendar distance between the two dates.
    2. The absolute distance between the two dates.

    As an example and to note the difference, assume that you have two DateTime objects constructed as follows:

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

    Note that $dt1 is quite late on a Tuesday, while $dt2 is very early on the following Friday.

    If you want the calendar distance use:

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

    Indeed, between, Tuesday and Friday there are 3 days. A calendar distance of 1 means “tomorrow” and a distance of -1 means “yesterday”. The “time” part of the DateTime objects is mostly irrelevant (except perhaps if the two dates fall on different time zones, then you would have to decide what “the calendar distance” between those two dates should mean).

    If you want the absolute distance then instead use:

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

    Indeed, if you want to split the time between the two dates in 24-hour chunks, there are only about 2.07 days between them. Depending on your application, you might want to truncate or round this number. The “time” part of the DateTime objects is very relevant, and the expected result is well defined even for dates on different time zones.

    0 讨论(0)
  • 2020-12-05 19:37

    You could convert the dates into the long integer format, which is the number of seconds since the epoch (some date in 1970 I think). You then have two variables that are the dates in seconds; subtract the smaller from the larger. Now you have a time span in seconds; divide it by the number of seconds in 24 hours.

    0 讨论(0)
  • 2020-12-05 19:38

    This question already has a nice answer, but I want to provide a answer showing why calculating the difference in seconds is WRONG (when we're using formatted/local dates rather than floating dates).

    I find it distressing how many suggestions tell people to subtract seconds. (This question was the first Google hit for my search, so I don't care how old it is.)

    I've made that mistake myself and wondered why the application would suddenly (over the weekend) show incorrent times. So I'm hoping this code will help people (who may be facing such an issue) understand why this approach is wrong and help them avoid that mistake.

    Here is a complete example, one that doesn't contain "..." at some crucial point (because if you insert two dates in the same time zone, you may not see an error).

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

    Output:

    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)
    

    Notes:

    • Again, I'm using local dates. If you use floating dates, you won't have that problem - simply because your dates stay in the same time zone.
    • Both time ranges in my example go from friday to monday, so the difference in days is 3, not 3.04... and of course not 2.95...
    • Turning the float into an integer using int() (as suggested in an answer) is just wrong, as shown in the example.
    • I do realize that rounding the difference in seconds would also return correct results in my example, but I feel like it's still wrong. You'd be calculating a day difference of 2 (for a large value of 2) and, because it is a large value of 2, turn it into a 3. So as long as DateTime provides the functionality, use DateTime.

    Quoting the documentation (delta_days() vs subtract_datetime()):

    date vs datetime math

    If you only care about the date (calendar) portion of a datetime, you should use either delta_md() or delta_days(), not subtract_datetime(). This will give predictable, unsurprising results, free from DST-related complications.

    Bottom line: Don't diff seconds if you're using DateTime. If you're not sure what date framework to use, use DateTime, it's awesome.

    0 讨论(0)
  • 2020-12-05 19:40

    Convert the two dates to seconds and then do the 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";
    
    0 讨论(0)
  • 2020-12-05 19:44

    Time::ParseDate will handle that format just fine:

    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 讨论(0)
提交回复
热议问题