如何使用 Perl 获取上个月的第一天



所以我想使用函数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::CalcDateTime ,但这可能适用于您的目的。

测试上述解决方案 - 许多故障

我创建了一个脚本,下面的脚本循环访问从 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_monthsadd_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::CalcDateTime或这个问题中建议的任何其他模块。

一个较少的 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_MONTHTime::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 );

相关内容

  • 没有找到相关文章

最新更新