5

I was wondering if there is an in-built Perl function that adjusts the date if you take a month from it. E.g. if date is the 31st, it will adjust to be the end of the previous month if it doesn't have 31 days.

I would just change it to 30th easily if it weren't for the months with 31 days next to each other (Dec/Jan, Jul/Aug) and February. I just want to store the date a certain amount of time away from the current date, e.g.

my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);

$current_date = join("-", (1900+$year), ($mon+1), $mday);
$one_month_ago = join("-", (1900+$year), ($mon), $mday);
$one_year_ago = join("-", (1899+$year), ($mon+1), $mday);

I can deal with the February instance as it only applies to years, but if this was taken on the 31st December 2012 then taking away a month would mean 31st Nov 2012, which of course didn't exist. I thought I would ask if there was a function before complicating things for myself... thanks :)

dgBP
  • 1,681
  • 6
  • 27
  • 42
  • 2
    [DateTime](https://metacpan.org/module/DateTime) maybe you search this module? (it has date' math) – gaussblurinc Aug 29 '12 at 11:19
  • Both answers below good! Date Time looks like a really useful option, but as I want to use it for a small part of my code I think I might just plunge in and make a bunch of clauses for it. Long winded, but it will help me learn... this is my first day using Perl :P Thanks for your help! – dgBP Aug 29 '12 at 11:30

3 Answers3

10

DateTime is not a built-in module, but once you've installed it, it makes this math trivial:

#!/usr/bin/perl
use strict;
use warnings;

use feature qw( say );
use DateTime;

my $dt = DateTime->now;
say $dt->ymd;

$dt->truncate( to => month );

say $dt->ymd;

$dt->add( days => -1 );
say $dt->ymd;

foreach ( 1 .. 12 ) { 
    $dt->add( months => -1 );
    say $dt->ymd;
} 

When I run this today (Aug 29, 2012) I get the following output:

[~] $ perl dt.pl 
2012-08-29
2012-08-01
2012-07-31
2012-06-30
2012-05-31
2012-04-30
2012-03-31
2012-02-29
2012-01-31
2011-12-31
2011-11-30
2011-10-31
2011-09-30
2011-08-31
2011-07-31
oalders
  • 5,239
  • 2
  • 23
  • 34
  • Honestly, if I could give two best answers I would, but you've got a bit more info :) thanks! I've never seen the say feature - that is useful to know! – dgBP Aug 29 '12 at 11:32
  • This feature depends on version of Perl. It should be >= 5.10 – Pavel Vlasov Aug 29 '12 at 11:39
9

Others have suggested DateTime, but it's quite large, non-core, and can be slow.

A much simpler solution is to use the builtin localtime and POSIX::mktime functions:

use POSIX qw( mktime );

my @t = localtime $epoch;
$t[4] -= 2;  # $t[4] is tm_mon
my $two_months_ago = mktime @t;

The mktime() function specifically handles denormalised values; it will cope with the fact that Janurary minus 2 months is November of the previous year, etc.. It will keep the same second/minute/hour of the day, and the same day of the month.

LeoNerd
  • 8,344
  • 1
  • 29
  • 36
  • This looks very good, but I can't get it to print out the $two_months_ago figure as it says it is uninitialized (I then initialized it with no change). – dgBP Aug 29 '12 at 13:21
  • with that other code it does indeed! I have to say that that is a better version that I was looking for :) kudos – dgBP Aug 29 '12 at 15:41
  • Any advantage in using `POSIX::mktime()` over `Time::Local::timelocal()`? Looks to me like they both do the same thing. – Dave Cross Aug 29 '12 at 16:15
  • Nothing really. The POSIX one is a real POSIX function, the Time::Local one there is just for completeness in the Time::Local package. They ought to give identical results. I usually prefer POSIX because in any non-trivially-sized program it's likely that POSIX.{pm,so} is already loaded, so using more functions from it is free. – LeoNerd Aug 29 '12 at 16:27
  • I don't think this works the way OP asked for. If you subtract one month from 2012-12-31 you will end up with 2012-12-01 and not 2012-11-30. Add one month to 2012-01-31 and you end up with 2012-03-02 and not 2012-02-29. If the resulting month has fewer days than the day of month for the given date, then the result is counted forward or backward into the next or previous month by the number of excessive days. http://codepad.org/ZPJIIi08 – chansen Aug 30 '12 at 14:55
8

If you have a chance to install module DateTime. It gives your a lot of perks, when you have deal with dates.

use strict;
use DateTime;

my $epoch = ...;
my $dt    = DateTime->from_epoch( epoch => $epoch );
$dt->subract(months => 1);

printf "%s", $dt->datetime();
Pavel Vlasov
  • 3,455
  • 21
  • 21