正则表达式匹配日期中的有效日期

时间:2011-05-20 19:56:42

标签: regex perl validation date

我需要帮助提出正则表达式以确保用户输入有效日期 该字符串的格式为mm/dd/yyyy

到目前为止,我已经提出了这个问题。

/\[1-9]|0[1-9]|1[0-2]\/\d{1,2}\/19|20\d\d/

我已经验证了正则表达式,用户无法输入高于12的日期,而且年份必须以“19”或“20”开头。我遇到麻烦的是找出验证当天的一些逻辑。这一天不应该超过31岁。

11 个答案:

答案 0 :(得分:35)

0-31的正则表达式:

(0[1-9]|[12]\d|3[01])

或者如果你不想要前一个零的日子(例如05):

([1-9]|[12]\d|3[01])

答案 1 :(得分:12)

use DateTime;

其他解决方案都很好,可能是有效的,等等。通常情况下,你最终想要多做一点,然后再多做一点,最后你会有一些疯狂的代码和闰年,为什么你自己这样做呢?再次?

DateTime及其formatters是您的解决方案。使用它们!有时它们有点矫枉过正,但通常会在未来发挥作用。

my $dayFormat = new DateTime::Format::Strptime(pattern => '%d/%m/%Y');
my $foo = $dayFormat->parse_datetime($myDateString);

$foo现在是一个DateTime对象。请享用。

如果您的日期字符串格式不正确,$foo将为"undef"$dayFormat->errstr会告诉您原因。

答案 2 :(得分:8)

  • 正如上面提到的那样,如果我们想要整体验证日期,那么RegEx是一个非常糟糕的选择。
  • 但是如果我们想匹配数字模式,在这种情况下来自01-31,那么RegEx就可以了,只要有一些后端逻辑可以验证整个日期,如果愿意的话。
  • 我看到10,20的预期答案目前失败。

    • 测试:gawk 'BEGIN{ for(i=0;i<=32;i++){ if (i ~ /^([0-2]?[1-9]|3[01])$/){print i " yes"}else {print i " no"} } }
    • 可以按照以下方式更正:^([0-2]?[1-9]|3[01]|10|20)$

请考虑以下解决方案......

<强> 1。确定需要匹配的集合:

  • 前缀为“0”的天数:{01,...,09},{10,...,31}
    • 子集{10,...,31}可以拆分为=> {10,...,29},{30,31}
  • 没有任何前缀:{1,...,31} => {1,...,9},{10,...,31}

<强> 2。每个子集的相应正则表达式:

---------------------------------
Sub-Set     |  Regular-Expression
---------------------------------
{01,...,09} | [0][1-9]
{10,...,29} | [1-2][0-9]
{30,31}     | 3[01]
{1,...,9}   | [1-9]
---------------------------------

现在,我们可以将([0][1-9])([1-9])组合为([0]?[1-9])。其中?表示模式/符号出现0或1次。 [更新] - 感谢@MattFrear指出。

因此得到的RegEx是:^(([0]?[1-9])|([1-2][0-9])|(3[01]))$

在此处测试:http://regexr.com/?383k1 [更新]

答案 3 :(得分:5)

^(((((((0?[13578])|(1[02]))[\.\-/]?((0?[1-9])|([12]\d)|(3[01])))|(((0?[469])|(11))[\.\-/]?((0?[1-9])|([12]\d)|(30)))|((0?2)[\.\-/]?((0?[1-9])|(1\d)|(2[0-8]))))[\.\-/]?(((19)|(20))?([\d][\d]))))|((0?2)[\.\-/]?(29)[\.\-/]?(((19)|(20))?(([02468][048])|([13579][26])))))$

来自Expressions in category: Dates and Times

验证一个月内的正确天数,看起来甚至可以处理闰年。

您当然可以使用[\.\-/]更改/以仅允许斜杠。

答案 4 :(得分:2)

这并不是 很难......

qr#^
    (?: 0[1-9] | 1[012] )
    /
    (?:
        0[1-9] | 1[0-9] | 2[0-8]
        | (?<! 0[2469]/ | 11/ ) 31
        | (?<! 02/ ) 30
        | (?<! 02/
             (?= ... 
                 (?: 
                     .. (?: [02468][1235679] | [13579][01345789] )
                     | (?: [02468][1235679] | [13579][01345789] ) 00
                 )
             )
        ) 29
    )
    /
    [0-9]{4}
    \z
#x

答案 5 :(得分:2)

如果您想检查有效日期,您必须做的不仅仅是检查数字和范围。幸运的是,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),那么它将这些天数转换为秒并将它们添加到偏移量中。这就是 2/29/1900 在一天后结束的原因:29 给出的秒数与 3/1/1900 相同。如果日期有效,它应该返回相同。因为我要来回这个,所以我在对它做任何事情之前先确定前导零的日期:

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

答案 6 :(得分:1)

试一试:
/(0?[1-9]|1[012])\/(0?[1-9]|[12][0-9]|3[01])\/((19|20)\d\d)/

答案 7 :(得分:1)

正则表达必须吗?如果没有,最好使用其他方法,例如DateTime::Format::DateManip

my @dates = (
    '04/23/2009',
    '01/22/2010 6pm',
    'adasdas',
    '1010101/12312312/1232132'
);

for my $date ( @dates ) 
{
    my $date = DateTime::Format::DateManip->parse_datetime( $date );
    die "Bad Date $date"  unless (defined $date);
    print "$dt\n";
}

答案 8 :(得分:0)

0-31天的正则表达式:

0 [1-9] | [12] \ d | 3 [01])不带前缀0-当“ 1”,“ 23” ...

([[1-9] | [12] \ d | 3 [01]),带有前缀0-当“ 01”,“ 04”时

(0?[1-9] | [12] \ d | 3 [01])-带有或不带有“ 0”-当“”

答案 9 :(得分:0)

更简单的正则表达式:

([12]\d|3[01]|0?[1-9])

考虑接受的答案和以下表达式:

(0[1-9]|[12]\d|3[01])

此匹配01,但不匹配1

接受的答案中的另一个表达式:

([1-9]|[12]\d|3[01])

这匹配1但不匹配01

不可能添加OR子句使它们都正常工作。

我建议将两者匹配。希望这会有所帮助。

答案 10 :(得分:0)

我已经使用了一段时间,而我想到的最好的正则表达式如下:

\b(0)?(?(1)[1-9]|(3)?(?(2)[01]|[12][0-9]))\b|\b[1-9]\b

它将匹配以下数字:

1 01 10 15 20 25 30 31

它不符合以下条件:

32 40 50 90
相关问题