How can I calculate the number of days between two dates in Perl?
Solution 1
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:
- The calendar distance between the two dates.
- 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.
Solution 2
If you care about accuracy, keep in mind that not all days have 86400 seconds. Any solution based on that assumption will not be correct for some cases.
Here's a snippet I keep around to calculate and display date/time differences a few different ways using the DateTime library. The last answer printed is the one you want, I think.
#!/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";
Solution 3
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);
Solution 4
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);
Solution 5
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.
Admin
Updated on June 29, 2020Comments
-
Admin almost 4 years
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 couldn't find any tutorials that discussed that date format. Should I be building a custom date checker for this format? Further reading of the Date::Calc on CPAN it looks unlikely that this format is supported.
-
Powerlord almost 15 yearsThe UNIX/C epoch is January 1, 1970 00:00:00 UTC.
-
Chas. Owens almost 15 yearsDate::Calc is not part of Core Perl
-
Michael Cramer almost 15 yearsObviously the mention of Date::Calc in the question implies CPAN's fair game.
-
Chas. Owens almost 15 yearsThe question also says "using default perl installation only"
-
John Siracusa almost 15 yearsThis will give you the number of 86400-seconds blocks of time between two dates, which is not the same thing as the number of (calendar) days between two dates. (Okay, it's actually not even the same thing as 86400-second blocks either, due to leap seconds...)
-
Juan A. Navarro over 12 yearsThis doesn't always work accurately, see my answer.
-
tchrist over 12 yearsI'm not sure that calling
int
on the number of days to round toward zero is right, but since it depends what he is doing with it, I wouldn't know what is right, either. I can see sometimes wanting thefloor()
(which for positives gives the same as you got), sometimes theceil()
, sometimessprintf "%.0f"
, and sometimes just keeping the float. But those are easy to adjust according to his needs. The hard thing is saying you can't use CPAN but can solicit code. Find: the code requires that he suck all ofDateTime
into his program with cut and paste, the worst kind of code reuse. Alas! -
tchrist over 12 years@John: The DST flag turning on and off is worse than leap seconds.
-
Bharat Pahalwani almost 10 yearswon't work if month,day etc value is of length = 1. i.e., $d1="2014-5-29 9:0:00"; and $d2="2014-5-29 10:0:00";
-
basic6 over 9 yearsI downvoted - going from winter to summer, the difference in seconds / 86400 may be 2.95, which is NOT 3. Cutting of the fraction (what int() does) turns 2.95 into 2, which is not 3 and therefore wrong. See my answer for an example that demonstrates this.
-
basic6 over 9 yearsAlso, please explain why
days, delta_days or in_units('days') wont work
- you mentionsubtract_datetime_absolute
, but that calculates the absolute difference in seconds and nanoseconds, which is not what we want. -
basic6 over 9 yearsThis does work accurately (upvoted). The subtract_datetime_absolute() call may return a number of seconds that is not a multiple of 86400 (like 255600 != (3* 86400 = 259200) in my example). The delta_md() call returns the actual difference in days. It may be confusing at first that you're using the $dur variable for 2 different things (absolute time difference and calendar date difference). The op asked for days, so the date difference is the interesting part of your answer.
-
Juan A. Navarro over 9 yearsAgreed. I understand now the source of the confusion and edited my answer hopefully to clarify much more the issue.
-
hlidka over 9 yearsFor calendar distance,
my $days = $dt2->delta_days($dt1)->days;
returned a wrong number for me, I needed to change it tomy $days = $dt2->delta_days($dt1)->delta_days;
-
Juan A. Navarro over 9 years@hlidka can you provide examples where the results come out wrong?
-
hlidka over 9 years@JuanA.Navarro here, take a look: gist.github.com/losomo/dea65735d5e6f763dc7c
-
Juan A. Navarro over 9 yearsYou are absolutely right, I'll fix the answer. DateTime::Duration
days()
andweeks()
need to be used together in order to make any sense. In this context, however,delta_days()
is clearly what we need. I'm surprised how long it passed before anybody noticing! For myself, I guess I always used this operation to compare two dates very near to each other. -
basic6 almost 9 years@JuanA.Navarro: Great to see the improved answer (which uses
delta_days()
now), downvote removed.