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

时间:2014-04-04 07:12:44

标签: perl datetime

所以我想使用函数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!

有人能给我一个更好的处理方法吗?

5 个答案:

答案 0 :(得分:6)

不要依赖Time::Piece中的Time::Seconds和常量进行日期操作

  

以下是我建议使用Time :: Piece和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 -> $start\n" if !$fail;
        $fail = "  To ($t) $src -> $start\n\n";

    } 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

替代使用Time :: Piece-&gt; 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或此问题中建议的任何其他模块。

答案 1 :(得分:3)

少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]);

答案 2 :(得分:1)

使用Time::Seconds的{​​{1}}定义为您提供一年一天的错误解决方案。它是一个月的平均长度,以秒为单位,相当于约30.4天。例如,从3月1日开始减去1月下旬的日期。

但是,上个月的第一天只能根据ONE_MONTH返回的月和日字段计算。将其转换为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";

答案 3 :(得分:1)

我不相信使用Time :: Piece的代码。它依赖于重载操作符,使其看起来像你做错了($time += ONE_DAY;看起来像你正在添加一个常量)当你真正做到这一点时。也许。这很难说。你必须熟悉模块的内容,才能知道你是否正确行事。

DateTime解决方案:

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";

答案 4 :(得分:0)

很抱歉,如果我不理解财产,但是:DaysInMonth(检查冥想)

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::Local

$time = timelocal( $sec, $min, $hour, $mday, $mon, $year );