【问题标题】:Regular Expression to match a valid day in a date正则表达式匹配日期中的有效日期
【发布时间】:2011-08-29 22:30:01
【问题描述】:

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

这是我到目前为止的想法。

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

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

【问题讨论】:

  • 正则表达式似乎是解决这个问题的奇怪工具。您不能将文本转换为日期并检查其值吗?
  • 虽然理论上可以做到这一点,但处理每个月(包括闰年)正确天数的正则表达式将非常复杂。为什么不直接拆分日期并测试每个组件? (或者更好的是,使用 CPAN 上的众多日期解析器之一。)
  • @Zerobu,你为什么比一年前又问同样的问题??? questions/2573466/matching-a-date-in-perl。好的,一年前你没有得到正则表达式的答案,但我希望你从@Seth 看到答案正则表达式对验证日期没有用处。
  • 如果我想要闰年等所有条件,那么我会这么说
  • Zerobu:你说过:“一个确保用户输入有效日期的正则表达式”。

标签: regex perl validation date


【解决方案1】:

0-31 的正则表达式:

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

或者,如果您不希望前面有零的天数(例如 05):

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

【讨论】:

  • 感谢其他发帖人很难弄清楚我在寻找什么,尽管我已经说过我需要什么
  • 那是因为日期不一定有效。例如,它不会拒绝 2011 年 2 月 31 日。正则表达式是错误的工具。
  • 1974 年是闰年吗? 2000 年呢?
  • 这不允许值 '00' 吗?
  • 这不允许天数以零结尾——“10”或“20”。
【解决方案2】:
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 会告诉您原因。

【讨论】:

  • 我不明白为什么人们想要重新实现任何东西,更不用说像日期解析这样复杂的东西了!
【解决方案3】:
  • 如上所述,如果我们想要验证整个日期,那么 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 指出。

所以生成的正则表达式是:^(([0]?[1-9])|([1-2][0-9])|(3[01]))$

在这里测试:http://regexr.com/?383k1 [UPDATE]

【讨论】:

  • 解释的最高分。原始问题的措辞不是特别好(不是 OP 的错),但如果您正在寻找 REGEXP 来验证一个月中的哪一天,就是这样。
  • 对已接受答案的改进。但是,这将接受 001、00000001 等。我建议 ^(([0]?[1-9])|([1-2][0-9])|(3[01]))$ @987654322 @
  • @MattFrear - 不错不错!我在将([0][1-9])([1-9]) 组合为([0]*[1-9]) 时犯了一个错误......虽然它显然应该是([0]?[1-9])。感谢您分享测试用例。
【解决方案4】:
^(((((((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

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

您当然可以将 [\.\-/] 更改为 / 以只允许斜杠。

【讨论】:

  • +1 表示可笑的复杂性。 @Zerobu,希望这能告诉你为什么正则表达式对于这个问题来说是个坏主意!即使这个野兽也不是完全正确的:正如原作者所说,它与 02/29/1900 不匹配。
【解决方案5】:

这并不全是那么困难...

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

【讨论】:

    【解决方案6】:

    如果您想检查有效日期,您需要做的不仅仅是检查数字和范围。幸运的是,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";
            }
        }
    
    【解决方案7】:

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

    【讨论】:

      【解决方案8】:

      正则表达式是必须的吗?如果没有,您最好使用不同的方法,例如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";
      }
      

      【讨论】:

        【解决方案9】:

        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” - 当“”时

        【讨论】:

        • 我认为你的前两个正则表达式应该是相反的。
        【解决方案10】:

        更简单的正则表达式:

        ([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 子句来让它们都工作。

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

        【讨论】:

        • 这比接受的答案(您复制/粘贴的答案)更好吗?
        • 接受的答案使用两种不同的表达式来处理是否以 0 开头的日期。这个在一个表达式中兼顾两者。行为类似于 Kent Pawar 的回答,但表达要简单得多。此外,我无法将 Kent 的表达式插入到另一个表达式中,在该表达式中我试图为 YYMMDD 等创建正则表达式
        • 您能否解释一下为什么这在您的答案正文中更好?
        • OP 说 MM 和 "this matches 1 but 不是 01”。请注意,要适合MM,它不应匹配1,而应匹配01
        • 这正是我想要的,而且形式非常简洁。接受 01 和 1。很好,忽略讨厌的人。 :-) 接受的答案不能两者兼得。
        【解决方案11】:

        我一直在研究这个问题,我想出的最好的正则表达式如下:

        \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
        

        【讨论】:

        • OP 问题与这个答案试图回答的完全不同
        • @bradbury9 这个问题想要一个正则表达式来匹配 1-31 范围内的数字,这正是这个答案声称要做的,不是吗?
        • 我认为这种模式在可能的不同情况下非常准确。您可能会在不同的上下文中找到写为 01 或 1 的日期。假设在写日期(例如:01/02/2019)或更正式的文件中......等等。
        猜你喜欢
        • 2010-09-08
        • 2011-08-24
        • 1970-01-01
        • 2011-06-10
        • 1970-01-01
        • 1970-01-01
        • 2011-12-17
        相关资源
        最近更新 更多