tags:

views:

487

answers:

7

How do I find a date which is 3 days earlier than a given date in Perl where the format is YYYY-MM-DD?

+1  A: 

See perldoc POSIX for the function mktime().

It will help you convert dates and times to a simple number, which is the Unix time (the number of seconds since January 1, 1970, I believe). Then, just subtract 3 (days) times 24 (hours in a day) times 60 (minutes in an hour) times 60 (seconds in a minute), or 259200 seconds from that number, and use localtime() to convert that number of seconds back to a string representation.

This is probably the best solution*, because it will handle month and year changes automatically. Any other solution will probably end up being more complicated after factoring in checking to see if we ran out of days in a month, or ran out of months in a year.


EDIT: *Outside of looking on CPAN.

Chris Lutz
If you are going to do this, I would suggest using a time in the middle of the day as input to mktime, rather than sec,min,hour=0 to avoid any chance of problem with daylight savings, leap seconds etc, then throw away the time component from localtime
Cebjyre
It's probably better to be using Date::Calc or some other CPAN module anyway, I just don't have the guru-level CPAN knowledge of a true Perl master.
Chris Lutz
This will fail in some situations. Everyone thinks it's a simple problem, but they've never really considered what it takes to get it right.
brian d foy
+10  A: 

Date::Calc can be used for such calculations:

#!/usr/bin/perl

use strict;
use warnings;
use Date::Calc qw(Add_Delta_Days);

my ( $yyyy, $mm, $dd ) = ( 2009, 9, 2 );
my @date = Add_Delta_Days( $yyyy, $mm, $dd, -3 );
print join( '-', @date );
Alan Haggai Alavi
+11  A: 

Date::Calc is the champion module here:

use strict;
use warnings;
use Date::Calc qw(Add_Delta_YMD);

my $startDate = '2000-01-01';
my ($startYear, $startMonth, $startDay) = $startDate =~ m/(\d{4}-(\d{2})-\d{2})/;

# 1 year, 2 months, 3 days, after startDate
my $endDate = join('-', Add_Delta_YMD($startYear, $startMonth, $startDay, 1, 2, 3));

The module has a huge number of time conversion routines, particularly those dealing with deltas. DateTime and Date::Manip are also worth checking out.

Ether
+4  A: 

There are so many options that it is moderately embarrassing. It depends in part on what other calculations you might need in the future, and whether times and time zones will ever be a factor, and similar things.

You could look at any of these

to name but three (sets of) modules. I'd suggest Date::Calc or Date::Manip for simplicity - but if you're going to need to get fancy in future, the DateTime modules may be better.

Jonathan Leffler
http://stackoverflow.com/revisions/bd3f965e-1edb-47b2-bbd0-b4149fd5af18/view-source
Brad Gilbert
@Brad: thanks for the fixups on the URLs.
Jonathan Leffler
A: 

The neat thing about mktime is that it will handle any time of offset. It uses January=0; and Year 2009 = 109 in this scheme. Thus, printed month - 1 and full year - 1900.

use POSIX qw<mktime>;

my ( $year, $month, $day ) = split '-', $date;
my $three_day_prior = mktime( 0, 0, 0, $day - 3, $month - 1, $year - 1900 );

mktime is useful for finding the last day of the month as well. You just go to day 0 of the next month.

mktime( 0, 0, 0, 0, $month, $year - 1900 );
Axeman
A: 

This is simple with Date::Simple

C:\>perl -MDate::Simple=today -e "print today()-3"
2009-08-30
Chris Charley
Can you update your answer to show how to convert the string '2009-09-04' to the answer '2009-09-01'; that is, instead of using 'today()', how do you convert a given string to the internal format?
Jonathan Leffler
C:\>perl -MDate::Simple=date -e "print date('2009-09-04')-3"2009-09-01
Chris Charley
+4  A: 

DateTime is the canonical way for dealing with dates in modern Perl:

use DateTime;

my ($year, $month, $day) = split '-', '2009-09-01';

my $date = DateTime->new( year => $year, month => $month, day => $day );

$date->subtract( days => 3 );

# $date is now three days earlier (2009-08-29T00:00:00)

/I3az/

draegtun