在Perl中,如何找到给定日期的上一个星期一的日期?

10
我正在寻找一个Perl脚本,它可以为任何指定的日期提供上一个星期一。 例如,对于日期2011-06-11,该脚本应返回2011-06-06。

7
你已经尝试过这个了吗?你尝试了什么? - Flexo
9个回答

26

我假设如果给定的日期是周一,你想要相同的日期(而不是上个周一)。以下是使用DateTime的一种方法:

use DateTime;

my $date = DateTime->new(year => 2011, month => 6, day => 11);
my $desired_dow = 1;            # Monday
$date->subtract(days => ($date->day_of_week - $desired_dow) % 7);
print "$date\n";

实际上,对于星期一这个特殊情况,% 7并不是必要的,因为$date->day_of_week - 1始终为0-6,并且mod 7没有任何操作。但是使用% 7,它适用于任何所需的星期几,而不仅仅是星期一。

如果您想要前一个星期一,可以更改减法:

$date->subtract(days => ($date->day_of_week - $desired_dow) % 7 || 7);

如果您需要解析从命令行输入的日期,您可能需要查看DateTime::Format::Natural

2
有很多方法可以做到这一点,也有很多关于日期/时间的模块在 CPAN 上,但是 DateTime 模块无疑是处理日期和时间的最佳模块。 - Eric Johnson

12

你还可以使用 Time::ParseDate,它能够理解“last Monday”这样的时间表达。

一个用于维护 Perl 声誉的单行代码:

perl -MTime::ParseDate -M'POSIX qw(strftime)' -l -e'foreach (@ARGV) { my $now= parsedate( $_); my $e= parsedate( "last Monday", NOW => $now) ; print "$_ : ", strftime "%F", localtime( $e)}' 2011-06-11 2011-06-12 2011-06-13 2011-06-14

以下是一个健全的脚本:

#!/usr/bin/perl

use strict;
use warnings;

use Time::ParseDate;    # to parse dates
use POSIX qw(strftime); # to format dates

foreach my $date (@ARGV) 
  { my $date_epoch= parsedate( $date) || die"'cannot parse date '$date'\n";
    my $monday= parsedate( "last Monday", NOW => $date_epoch);                  # last Monday before NOW
    print "Monday before $date: ", strftime( "%F", localtime( $monday)), "\n";  # %F is YYYY-MM-DD
  }

需要注意的是:如果日期是星期一,则会得到上一个星期一,这可能不是您想要的,要更改只需将NOW设置为下一天(将60*60*24,即一天,添加到$date_epoch)。然后Time::ParseDate相当宽容,例如它将愉快地解析2011-23-38(作为2012-12-09)。


1
虽然在 Perl 中,DateTime 是处理日期和时间的规范方式,但是 Time::ParseDate 一直是我处理这类事情的首选。 - Grrrr
1
我实际上很少需要处理日期,这也许是为什么我觉得parsedate("last Monday", NOW => $date_epoch)很可爱,更重要的是易于记忆。 - mirod

4
使用标准的Perl库非常简单。
#!/usr/bin/perl

use strict;
use warnings;
use 5.010;

use Time::Local;
use POSIX 'strftime';

my $date = shift || die "No date given\n";

my @date = split /-/, $date;
$date[0] -= 1900;
$date[1]--;

die "Invalid date: $date\n" unless @date == 3;

my $now = timelocal(0, 0, 12, reverse @date);

while (strftime('%u', localtime $now) != 1) {
  $now -= 24 * 60 * 60;
}

我会把使用的各种模块和函数留给读者自己练习查找。

如果您使用DateTime,可能会更简单。


3
以Perl的精神,做事情的方式不止一种。
use Modern::Perl;
use Date::Calc qw/Day_of_Week/;
my $date = '2011/6/11';
my @fields = split /\//, $date;
my @new_date = Add_Delta_Days( @fields , 1 - Day_of_Week( @fields ) );
say join "/", @new_date;

2
你可以使用Date::Manip,它有一个Date_GetPrev函数,并且理解“星期一”。
$ perl -MDate::Manip -le 'print UnixDate(Date_GetPrev(shift, "Monday", 0), "%Y-%m-%d")' 2011-06-11
2011-06-06

1

泽勒公式可以帮助你计算出星期几。从那里开始,应该就很容易了。


1
这是一篇新闻发布的新闻发布。原始帖子的替代链接,更易读:<Message-ID: 1990Oct2.172114.6421@amd.com> - daxim
2
@daxim,更不用说在 Perl 5 中完全没有必要了。( localtime )[6] 表示的是一周中的星期几。 - Axeman

1
在这之后,$tstamp将拥有你想要的时间戳。
use strict;
use warnings;
use POSIX qw<mktime>;

my $time = '2011-06-11';

my ( $year, $month, $day ) = split /-0?/, $time;
my $tstamp = mktime( 0, 0, 0, $day, $month - 1, $year - 1900 );
my $dow  = ( localtime $tstamp )[6];
$tstamp -= (( $dow > 1 ? 0 : 7 ) + $dow - 1 ) * 24 * 60 * 60;

这里假设“上个星期一”是指在给定日期之前最后一次出现的星期一。所以如果给定日期是星期一(1),那么它会再减去7天。


你的 dow 测试有一个偏差,2011-06-07(星期二)将返回 2011-05-30 的时间戳。 - Ven'Tatsu
@Ven'Tatsu 啊,我聪明反被聪明误了。我测试、发布后才发现比较错误,匆忙地将其更改为2。 - Axeman

1

如果你像我一样是个奴隶,必须使用没有库的企业版Perl,而且也不允许安装它们,那就老老实实地按照传统方式操作:

my $datestring = "";
my $secondsEpoc = time(); #gives the seconds from system epoch
my $secondsWantedDate;
my $seconds2substract;

$datestring = localtime($secondsEpoc);
print "Today's date and time ".$datestring."\n";
my ($second,$minute,$hour,$d,$M,$y,$wd,$yd) = (localtime)[0,1,2,3,4,5,6,7];
#print "hour: ".$hour;
#print "minute: ".$minute;
#print "second: ".$second;
#print "week day: ".$wd; #week day is 1=Monday .. 7=Sunday

$seconds2substract = 24 * 60 * 60;  # seconds in 24 hours
$secondsWantedDate=$secondsEpoc-$seconds2substract;
$datestring = localtime($secondsWantedDate);
print "Yesterday at the same time ".$datestring."\n";


my $days2Substract = $wd-1;
$seconds2substract = ($days2Substract * 24 * 60 * 60);
$secondsWantedDate=$secondsEpoc-$seconds2substract;
$datestring = localtime($secondsWantedDate);
print "Past Monday same time ".$datestring."\n";

$seconds2substract = ($days2Substract * 24 * 60 * 60) + ($hour *60 *60) + ($minute * 60) + $second;
$secondsWantedDate = $secondsEpoc-$seconds2substract;
$datestring = localtime($secondsWantedDate);
print "Past Monday at 00:00:00  ".$datestring."\n";

希望能帮到某些人!

你说得没错,但在许多情况下,闰秒并不重要:相信我,不允许添加库是一种痛苦,它会使你的代码行数急剧增加,而你所关心的只是找到一个足够快速且能完成所需任务的解决方案 :) - Cristina

1

有很多种方法可以在Perl中实现。以下是使用Perl库Moment的方法。

#!/usr/bin/perl

use strict;
use warnings FATAL => 'all';
use feature 'say';

use Moment;

sub get_monday_date {
    my ($date) = @_;

    my $moment = Moment->new( dt => "$date 00:00:00" );

    my $weekday_number = $moment->get_weekday_number( first_day => 'monday' );

    my $monday = $moment->minus( day => ($weekday_number - 1) );

    return $monday->get_d();
}

say get_monday_date('2011-06-11'); # 2011-06-06

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接