Лучший или самый простой? Я всегда находил, что нормализация даты strftime
пригодится для такого рода вещей:
#!/usr/bin/perl
use strict;
use warnings;
use POSIX qw/strftime/;
my @date = localtime;
print strftime "today is %Y-%m-%d\n", @date;
$date[3] -= 5 * 7;
print strftime "five weeks ago was %Y-%m-%d\n", @date;
Какое решение лучше всего, отчасти зависит от того, что вы хотите сделать с датой, когда вы закончите. Вот тест с реализациями различных методов:
#!/usr/bin/perl
use strict;
use warnings;
use Benchmark;
use Date::Manip qw/UnixDate/;
use Date::Simple qw/today/;
use Date::Calc qw/Add_Delta_Days Today/;
use DateTime;
use POSIX qw/strftime/;
use Class::Date;
my %subs = (
cd => sub {
(Class::Date::now - [0, 0, 5 * 7])->strftime("%Y-%m-%d");
},
dc => sub {
sprintf "%d-%02s-%02d", Add_Delta_Days Today, -5 * 7;
},
dm => sub {
UnixDate("5 weeks ago", "%Y-%m-%d");
},
ds => sub {
(today() - 5 * 7)->strftime("%Y-%m-%d");
},
dt => sub {
my $now = DateTime->from_epoch(epoch => time, time_zone => "local");
my $five_weeks = DateTime::Duration->new(weeks => 5);
($now - $five_weeks)->ymd('-');
},
p => sub {
my @date = localtime;
$date[3] -= 5 * 7;
strftime "%Y-%m-%d", @date;
},
y => sub {
my ($d, $m, $y) = (localtime)[3..5];
my $date = join "/", $m+1, $d, $y+1900;
my $days_ago = 7*5;
$date =~ s#^([0-9]+)/([0-9]+)/([0-9]+)\z#@{[sprintf"%.2d",$1]}/@{[sprintf"%.2d",$2]}/$3/$days_ago#;
until ( $date =~ s#^([0-9]+)/([0-9]+)/([0-9]+)/0\z#@{[$1+0]}/@{[$2+0]}/$3# ) {
$date =~ s#([0-9]+)/([0-9]+)/([0-9]+)/([0-9]+)#@{[$2==1?sprintf"%.2d",$1-1||12:$1]}/@{[sprintf"%.2d",$2-1||31]}/@{[$1==1 && $2==1?$3-1:$3]}/@{[$4-1]}#;
$date =~ s#([0-9]+)\z#@{[$1+1]}# unless $date =~ m#^(?:0[1-9]|1[012])/(?:0[1-9]|1[0-9]|2[0-8]|(?<!0[2469]/|11/)31|(?<!02/)30|(?<!02/(?=...(?:..(?:[02468][1235679]|[13579][01345789])|(?:[02468][1235679]|[13579][01345789])00)))29)/#;
}
return $date;
},
);
print "$_: ", $subs{$_}(), "\n" for keys %subs;
Benchmark::cmpthese -1, \%subs;
А вот и результаты. Метод strftime
кажется самым быстрым, но он также обладает наименьшими возможностями.
y: 6/14/2011
dm: 2011-06-14
p: 2011-06-14
dc: 2011-06-14
cd: 2011-06-14
dt: 2011-06-15
ds: 2011-06-14
Rate dt dm y ds cd dc p
dt 1345/s -- -5% -28% -77% -82% -96% -98%
dm 1408/s 5% -- -24% -75% -81% -96% -98%
y 1862/s 38% 32% -- -68% -75% -95% -97%
ds 5743/s 327% 308% 208% -- -24% -84% -90%
cd 7529/s 460% 435% 304% 31% -- -78% -87%
dc 34909/s 2495% 2378% 1775% 508% 364% -- -39%
p 56775/s 4121% 3931% 2949% 889% 654% 63% --
Лучше, чем эталонный тест, проверить, как они обрабатывают DST (этот тест обнаружил бы ошибку в предположении о том, что DateTime->now
возвращает).
#!/usr/bin/perl
use strict;
use warnings;
use Time::Mock;
use Date::Manip qw/UnixDate/;
use Date::Simple qw/today/;
use Date::Calc qw/Add_Delta_Days Today/;
use DateTime;
use POSIX qw/strftime mktime/;
use Class::Date;
sub target {
my @date = localtime;
$date[3] -= 5 * 7;
strftime "%Y-%m-%d", @date;
}
my %subs = (
cd => sub {
(Class::Date::now - [0, 0, 5 * 7])->strftime("%Y-%m-%d");
},
dc => sub { sprintf "%d-%02s-%02d", Add_Delta_Days Today, -5 * 7;
},
dm => sub {
UnixDate("5 weeks ago", "%Y-%m-%d");
},
ds => sub {
(today() - 5 * 7)->strftime("%Y-%m-%d");
},
dt => sub {
my $now = DateTime->from_epoch( epoch => time, time_zone => 'local' );
my $five_weeks = DateTime::Duration->new(weeks => 5);
($now - $five_weeks)->ymd('-');
},
y => sub {
my ($d, $m, $y) = (localtime)[3..5];
my $date = join "/", $m+1, $d, $y+1900;
my $days_ago = 7*5;
$date =~ s#^([0-9]+)/([0-9]+)/([0-9]+)\z#@{[sprintf"%.2d",$1]}/@{[sprintf"%.2d",$2]}/$3/$days_ago#;
until ( $date =~ s#^([0-9]+)/([0-9]+)/([0-9]+)/0\z#@{[$1+0]}/@{[$2+0]}/$3# ) {
$date =~ s#([0-9]+)/([0-9]+)/([0-9]+)/([0-9]+)#@{[$2==1?sprintf"%.2d",$1-1||12:$1]}/@{[sprintf"%.2d",$2-1||31]}/@{[$1==1 && $2==1?$3-1:$3]}/@{[$4-1]}#;
$date =~ s#([0-9]+)\z#@{[$1+1]}# unless $date =~ m#^(?:0[1-9]|1[012])/(?:0[1-9]|1[0-9]|2[0-8]|(?<!0[2469]/|11/)31|(?<!02/)30|(?<!02/(?=...(?:..(?:[02468][1235679]|[13579][01345789])|(?:[02468][1235679]|[13579][01345789])00)))29)/#;
}
return join "-", map { sprintf "%02d", $_ }
(split "/", $date)[2,0,1];
},
);
my $time = mktime 0, 0, 0, 13, 2, 111; #2011-03-13 00:00:00, DST in US
for my $offset (map { $_ * 60 * 60 } 1 .. 24) {
print strftime "%Y-%m-%d %H:%M:%S\n", (localtime $time + $offset);
Time::Mock->set($time + $offset);
my $target = target;
for my $sub (sort keys %subs) {
my $result = $subs{$sub}();
if ($result ne $target) {
print "$sub disagrees: ",
"time $time target $target result $result\n";
}
}
}