所以我想使用函数localtime()
,但我在正确获取上个月的第一天和最后一天时遇到了问题。现在我有工作功能,但我敢打赌有更好的方法来解决这个问题。
use Time::Piece;
use Time::Seconds;
$start_of_month = localtime();
while($start_of_month->mday < 10) {
$start_of_month += ONE_DAY;
}
$start_of_month -= ONE_MONTH; # Subtract one month to get previous month. "ONE_MONTH" is defined by Time::Seconds
$end_of_month = $start_of_month; # Copy start_of_month to end_of_month as they both have same year and month.
# Subtract day from $start_of_month until mday is the first day of the month.
while($start_of_month->mday != 1) {
$start_of_month -= ONE_DAY;
}
# Silly workaround to bring $end_of_month to last day of the month as Time::Piece object does not have good way to change mday.
while($end_of_month->mday != $start_of_month->month_last_day) {
$end_of_month += ONE_DAY;
}
$period_start = $start_of_month->dmy("."); # End result has to be same!
谁能给我一个更好的方法来解决这个问题?
不要依赖 Time::P iece 和 Time::Seconds 中的常量进行日期操作
以下是我建议使用 Time::P iece 和 Time::Seconds 简化 OP 的日期操作。 如果这适用于重载运算符,它实际上可能会按预期工作,但正如 ikegami 指出的那样,这并不能保证。 所以我在下面测试了。
如果你想走数学路线,至少你可以简化一点:
use strict;
use warnings;
use Time::Piece;
use Time::Seconds;
my $date_start = localtime();
$date_start -= ONE_MONTH;
$date_start -= ($date_start->mday - 1) * ONE_DAY;
my $date_end = $date_start + ($date_start->month_last_day - 1) * ONE_DAY;
print $date_start , "n"; # Prints Mar 1st (at least today it does)
print $date_end , "n"; # Prints Mar 31st
我会考虑为此使用不同的模块,例如 Date::Calc
或 DateTime
,但这可能适用于您的目的。
测试上述解决方案 - 许多故障
我创建了一个脚本,下面的脚本循环访问从 1 月 15 日开始的一年中的每个日期,显示上述代码无法按预期工作的每个范围。
use strict;
use warnings;
use Time::Piece;
use Time::Seconds;
my $t = 1389812400; # Wed 2014-Jan-15 7pm GMT. 11am PST
my $t_max = 1421348400; # Thu 2015-Jan-15 7pm GMT. 11am PST
my %prev_month = map {$_ => ($_ - 1) || 12} (1..12);
my $fail = '';
while ($t <= $t_max) {
$t += 60; # Increment by 1 minute
# Testing potentially overloaded math of Time::Piece & Time::Seconds
my $start = my $src = localtime($t);
$start -= ONE_MONTH;
$start -= ($start->mday - 1) * ONE_DAY;
if ($start->mon != $prev_month{$src->mon}) {
print "From ($t) $src -> $startn" if !$fail;
$fail = " To ($t) $src -> $startnn";
} elsif ($fail) {
print $fail;
$fail = '';
}
}
下面是此脚本的输出,其中插入了注释以解释每个范围失败的原因:
# ONE_MONTH is exactly 2_629_744 seconds, or 30.437 days.
# ONE_MONTH is too short for January
From (1391193000) Fri Jan 31 10:30:00 2014 -> Wed Jan 1 00:00:56 2014
To (1391241540) Fri Jan 31 23:59:00 2014 -> Wed Jan 1 13:29:56 2014
# ONE_MONTH is too long for February
From (1393660800) Sat Mar 1 00:00:00 2014 -> Wed Jan 1 13:30:56 2014
To (1393871340) Mon Mar 3 10:29:00 2014 -> Wed Jan 1 23:59:56 2014
# ONE_MONTH is too short for March
From (1396290600) Mon Mar 31 11:30:00 2014 -> Sat Mar 1 00:00:56 2014
To (1396335540) Mon Mar 31 23:59:00 2014 -> Sat Mar 1 12:29:56 2014
# ONE_DAY is 86_400 seconds, or 24 hours.
# March 9th is only 23 hours long due to DST, ONE_DAY goes to far.
From (1397064600) Wed Apr 9 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1397068140) Wed Apr 9 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1397151000) Thu Apr 10 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1397154540) Thu Apr 10 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1397237400) Fri Apr 11 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1397240940) Fri Apr 11 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1397323800) Sat Apr 12 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1397327340) Sat Apr 12 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1397410200) Sun Apr 13 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1397413740) Sun Apr 13 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1397496600) Mon Apr 14 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1397500140) Mon Apr 14 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1397583000) Tue Apr 15 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1397586540) Tue Apr 15 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1397669400) Wed Apr 16 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1397672940) Wed Apr 16 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1397755800) Thu Apr 17 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1397759340) Thu Apr 17 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1397842200) Fri Apr 18 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1397845740) Fri Apr 18 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1397928600) Sat Apr 19 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1397932140) Sat Apr 19 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1398015000) Sun Apr 20 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1398018540) Sun Apr 20 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1398101400) Mon Apr 21 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1398104940) Mon Apr 21 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1398187800) Tue Apr 22 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1398191340) Tue Apr 22 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1398274200) Wed Apr 23 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1398277740) Wed Apr 23 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1398360600) Thu Apr 24 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1398364140) Thu Apr 24 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1398447000) Fri Apr 25 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1398450540) Fri Apr 25 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1398533400) Sat Apr 26 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1398536940) Sat Apr 26 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1398619800) Sun Apr 27 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1398623340) Sun Apr 27 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1398706200) Mon Apr 28 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1398709740) Mon Apr 28 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1398792600) Tue Apr 29 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1398796140) Tue Apr 29 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
From (1398879000) Wed Apr 30 10:30:00 2014 -> Fri Feb 28 23:00:56 2014
To (1398882540) Wed Apr 30 11:29:00 2014 -> Fri Feb 28 23:59:56 2014
# ONE_MONTH is too long for April
From (1398927600) Thu May 1 00:00:00 2014 -> Sat Mar 1 12:30:56 2014
To (1398965340) Thu May 1 10:29:00 2014 -> Sat Mar 1 22:59:56 2014
# ONE_MONTH is too short for May
From (1401557400) Sat May 31 10:30:00 2014 -> Thu May 1 00:00:56 2014
To (1401605940) Sat May 31 23:59:00 2014 -> Thu May 1 13:29:56 2014
# ONE_MONTH is too long for June
From (1404198000) Tue Jul 1 00:00:00 2014 -> Thu May 1 13:30:56 2014
To (1404235740) Tue Jul 1 10:29:00 2014 -> Thu May 1 23:59:56 2014
# ONE_MONTH is too short for July
From (1406827800) Thu Jul 31 10:30:00 2014 -> Tue Jul 1 00:00:56 2014
To (1406876340) Thu Jul 31 23:59:00 2014 -> Tue Jul 1 13:29:56 2014
# ONE_MONTH is too short for August
From (1409506200) Sun Aug 31 10:30:00 2014 -> Fri Aug 1 00:00:56 2014
To (1409554740) Sun Aug 31 23:59:00 2014 -> Fri Aug 1 13:29:56 2014
# ONE_MONTH is too long for September
From (1412146800) Wed Oct 1 00:00:00 2014 -> Fri Aug 1 13:30:56 2014
To (1412184540) Wed Oct 1 10:29:00 2014 -> Fri Aug 1 23:59:56 2014
# ONE_MONTH is too short for October
From (1414776600) Fri Oct 31 10:30:00 2014 -> Wed Oct 1 00:00:56 2014
To (1414825140) Fri Oct 31 23:59:00 2014 -> Wed Oct 1 13:29:56 2014
# ONE_MONTH is too long for November
From (1417420800) Mon Dec 1 00:00:00 2014 -> Wed Oct 1 14:30:56 2014
To (1417454940) Mon Dec 1 09:29:00 2014 -> Wed Oct 1 23:59:56 2014
# ONE_MONTH is too short for December
From (1420050600) Wed Dec 31 10:30:00 2014 -> Mon Dec 1 00:00:56 2014
To (1420099140) Wed Dec 31 23:59:00 2014 -> Mon Dec 1 13:29:56 2014
替代使用时间::P iece->add_months? 不
Time::Piece
有两个函数,add_months
和add_years
用于日期计算。 不幸的是,文档指出:
请注意,在月末加减月时有一些"奇怪"的行为。通常,当生成的月份短于起始月份时,将添加重叠天数。例如,从 2008-03-31 中减去一个月不会得到 2008-02-31,因为这是一个不可能的日期。相反,你会得到2008-03-02。这似乎与其他日期操作工具一致。
下面的代码充分演示了时区 PST 的此行为。
use strict;
use warnings;
use Time::Piece;
my $t = localtime(1420012800);
print $t->add_months($_),"n" for (0..12)
输出:
Wed Dec 31 00:00:00 2014
Sat Jan 31 00:00:00 2015
Tue Mar 3 00:00:00 2015
Tue Mar 31 00:00:00 2015
Fri May 1 00:00:00 2015
Sun May 31 00:00:00 2015
Wed Jul 1 00:00:00 2015
Fri Jul 31 00:00:00 2015
Mon Aug 31 00:00:00 2015
Thu Oct 1 00:00:00 2015
Sat Oct 31 00:00:00 2015
Tue Dec 1 00:00:00 2015
Thu Dec 31 00:00:00 2015
现在,这个函数能够在一年后准确地循环回同一日期,这很好,但在那之前有很多不利的月份。
结论
Time::Seconds
中的常量就是常量。 它们是用于表示时间段的确切秒数。 没有运算符重载来方便花哨的日期操作。 相反,这些值只是有利于数学比较。
为了操纵特定的日期,我建议使用Date::Calc
、DateTime
或这个问题中建议的任何其他模块。
一个较少的 OO,但替代解决方案:
use POSIX qw(mktime);
my @now = localtime;
# mday = 1, month = month - 1
my $date_start = mktime(@now[0 .. 2], 1, $now[4] - 1, @now[5 .. 8]);
# mday = 0, the 0th day of this month == last day of prior month
my $date_end = mktime(@now[0 .. 2], 0, @now[4 .. 8]);
print scalar localtime $date_start, "n";
print scalar localtime $date_end, "n";
这与夏令时有关;如果你不关心一天中的时间,你可以$now[2]
设置为中午12点,并希望没有国家宣布黑夜是白天,白天是黑夜:
my @now = (0, 0, 12, (localtime)[3..8]);
我不信任使用Time::P iece的代码。它依赖于重载运算符,使它看起来像你做错了($time += ONE_DAY;
看起来像你在添加一个常量),而你实际上做对了。或。很难说。你必须对模块的内涵有深入的了解,才能知道你做得是否正确。
日期时间解决方案:
my $dt = DateTime
->now( time_zone => 'local' )
->set_time_zone('floating') # Do this when you want to date arithmetic.
->truncate( to => 'day' );
$dt->set( day => 1 )->subtract( days => 1 );
my $last = $dt->ymd('-');
$dt->set( day => 1 );
my $first = $dt->ymd('-');
say "$first .. $last";
使用ONE_MONTH
的Time::Seconds
定义可以为您提供一年中几天错误的解决方案。它是一个月的平均长度(以秒为单位),大约等于 30.4 天。例如,从 3 月 1 日减去它,你会得到 1 月下旬的日期。
但是,上个月的第一天可以简单地根据 localtime
返回的月份和日期字段计算。将其转换为Time::Piece
对象可以让我们计算同月的最后一天。
use strict;
use warnings;
use Time::Piece;
my ($m, $y) = (localtime)[4,5];
$y += 1900;
if ($m == 0) {
$m = 11;
$y -= 1;
}
my $period_start = sprintf '%02d.%02d.%04d', 1, $m, $y;
my $period_end = do {
my $tp = Time::Piece->strptime($period_start, '%d.%m.%Y');
sprintf '%02d.%02d.%04d', $tp->month_last_day, $m, $y;
};
print $period_start, "n";
print $period_end, "n";
输出
01.03.2014
31.03.2014
对不起,如果我不理解属性,但是:每月天数(检查coment)
my @monthDays= qw( 31 28 31 30 31 30 31 31 30 31 30 31 );
sub MonthDays {
my $month= shift(@_);
my $year= @_ ? shift(@_) : 1900+(localtime())[5];
if( $year <= 1752 ) {
# Note: Although September 1752 only had 19 days,
# they were numbered 1,2,14..30!
return 19 if 1752 == $year && 9 == $month;
return 29 if 2 == $month && 0 == $year % 4;
} else {
return 29 if 2 == $month and
0 == $year%4 && 0 != $year%100 || 0 == $year%400;
}
return $monthDays[$month-1];
}
如果您希望轻松的数据到秒,请查看时间::本地
$time = timelocal( $sec, $min, $hour, $mday, $mon, $year );