如果您想检查有效日期,您需要做的不仅仅是检查数字和范围。幸运的是,Perl 已经拥有了你需要的一切。 Time::Piece 模块是 Perl 自带的,可以解析日期。它知道如何解析日期并进行第一轮检查:
use v5.10;
use Time::Piece; # comes with Perl
my @dates = qw(
01/06/2021 01/37/456 10/6/1582 10/18/1988
2/29/1900 2/29/1996 2/29/2000
);
foreach my $date ( @dates ) {
my $t = eval { Time::Piece->strptime( $date, '%m/%d/%Y' ) };
unless( $t ) {
say "Date <$date> is not valid";
next;
}
say $t;
}
输出很有趣,这里没有其他解决方案可以处理这个问题。为什么 10/6/1582 是无效日期?它在公历中不存在,但这里有一个更简单的原因。 strptime 不处理 1900 年之前的日期。
但也要注意2/29/1900 变成了3/1/1900。这很奇怪,我们应该解决这个问题,但是年份中没有闰年可以被 100 整除。好吧,除非它们可以被 400 整除,这就是 2/29/2000 起作用的原因。
Wed Jan 6 00:00:00 2021
Date <01/37/456> is not valid
Date <10/6/1582> is not valid
Tue Oct 18 00:00:00 1988
Thu Mar 1 00:00:00 1900
Thu Feb 29 00:00:00 1996
Tue Feb 29 00:00:00 2000
但是让我们解决那个闰年问题。 tm 结构正在进行愚蠢的转换。如果各个数字在合理的范围内(天数为 0 到 31),而与月份无关,则它将这些天数转换为秒数并将它们添加到偏移量中。这就是为什么 1900 年 2 月 29 日在一天后结束的原因:29 给出的秒数与 1900 年 3 月 1 日相同。如果日期是有效的,它应该返回相同的。因为我要往返这个,所以在我对它做任何事情之前,我会修复前导零的日期:
use v5.10;
use Time::Piece; # comes with Perl
my @dates = qw(
01/06/2021 2/29/1900 2/2/2020
);
foreach my $date ( @dates ) {
state $format = '%m/%d/%Y';
$date =~ s/\b(\d)\b/0$1/g; # add leading zeroes to lone digits
my $t = eval { Time::Piece->strptime( $date, $format ) };
unless( $t ) {
say "Date <$date> is not valid";
next;
}
unless( $t->strftime( $format ) eq $date ) {
say "Round trip failed for <$date>: Got <"
. $t->strftime( $format ) . ">";
next;
};
say $t;
}
现在的输出是:
Wed Jan 6 00:00:00 2021
Round trip failed for <02/29/1900>: Got <03/01/1900>
Sun Feb 2 00:00:00 2020
这有点长,但这就是我们有子例程的原因:
if( date_is_valid( $date ) ) { ... }
还需要正则表达式吗?好的,让我们使用(??{...}) 构造来决定模式是否应该失败。匹配一堆数字并将其捕获到$1。现在,使用(??{...}) 制作模式的下一部分,使用您喜欢的任何 Perl 代码。如果您接受捕获,则返回一个空模式。如果你拒绝它,返回模式(*FAIL),这会立即导致整个匹配失败。没有更多棘手的交替。而这个使用的是新的chained comparison in v5.32(虽然我还是有疑虑):
use v5.32;
foreach ( qw(-1 0 1 37 31 5 ) ) {
if( /\A(\d+)(??{ (1 <= $1 <= 31) ? '' : '(*FAIL)' })\z/ ) {
say "Value <$1> is between 1 and 31";
}
}