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

发布于 2024-11-08 14:11:28 字数 245 浏览 0 评论 0 原文

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

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

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

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

I need help coming up with a regex to make sure the user enters a valid date
The string will be in the format of mm/dd/yyyy

Here is what I have come up with so far.

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

I have validated the regex where the user cannot enter a day higher than 12 and the years have to start with either "19" or "20". What I am having trouble is figuring out some logic for validating the day. The day should not go over 31.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

单身狗的梦 2024-11-15 14:11:28

0-31 的正则表达式:

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

或者如果您不想要前面带有零的日期(例如 05):

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

Regex for 0-31:

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

Or if you don't want days with a preceding zero (e.g. 05):

([1-9]|[12]\d|3[01])
青衫负雪 2024-11-15 14:11:28
  • 正如许多人上面指出的,如果我们想验证整个日期,那么正则表达式是一个非常糟糕的选择。
  • 但是,如果我们想要匹配数字模式,在本例中是 01-31,那么只要有一些后端逻辑将日期验证为整个,如果需要的话。
  • 我看到当前的预期答案对于 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 [更新]

  • As many have noted above, if we want to validate the date as a whole then a RegEx is a very poor choice.
  • But if we want to match a pattern of numbers, in this case from 01-31 then RegEx is fine so long as there is some backend logic that validates the date as a whole, if so desired.
  • I see the expected answer currently fails for 10, 20.

    • Test: gawk 'BEGIN{ for(i=0;i<=32;i++){ if (i ~ /^([0-2]?[1-9]|3[01])$/){print i " yes"}else {print i " no"} } }
    • This can be corrected as follows: ^([0-2]?[1-9]|3[01]|10|20)$

So kindly consider the following solution...

1. Identify the sets that need to be matched:

  • Days with prefix "0": {01,...,09},{10,...,31}
    • Sub-set {10,...,31} can be split into => {10,...,29},{30,31}
  • Without any prefix: {1,...,31} => {1,...,9},{10,...,31}

2. Corresponding regular expressions for each sub-set:

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

Now we can group ([0][1-9]) and ([1-9]) together as ([0]?[1-9]). Where ? signifies 0 or 1 occurrences of the pattern/symbol. [UPDATE] - Thank you @MattFrear for pointing it out.

So the resulting RegEx is: ^(([0]?[1-9])|([1-2][0-9])|(3[01]))$

Tested here: http://regexr.com/?383k1 [UPDATE]

你的笑 2024-11-15 14:11:28
use DateTime;

其他解决方案很好,可能有效,等等。通常,您最终会想要多做一点,然后再多做一点,最终您会得到一些疯狂的代码和闰年,为什么您还要自己再做一次呢?

DateTime 及其格式化程序 是您的解决方案。使用它们!有时它们有点矫枉过正,但通常这对你来说是有效的。

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

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

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

use DateTime;

Other solutions are fine, probably work, etc. Usually, you end up wanting to do a bit more, and then a bit more, and eventually you have some crazy code, and leap years, and why are you doing it yourself again?

DateTime and its formatters are your solution. Use them! Sometimes they are a bit overkill, but often that works out for you down the road.

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

$foo is now a DateTime object. Enjoy.

If your date string wasn't properly formatted, $foo will be "undef" and $dayFormat->errstr will tell you why.

路还长,别太狂 2024-11-15 14:11:28
^(((((((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])))))$

来自 类别中的表达式:日期和时间

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

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

^(((((((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])))))$

From Expressions in category: Dates and Times

Validates the correct number of days in a month, looks like it even handles leap years.

You can of course change [\.\-/] with / to only allow slashes.

青瓷清茶倾城歌 2024-11-15 14:11:28

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

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

This isn't all that hard...

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
墨落成白 2024-11-15 14:11:28

如果您想检查有效日期,您要做的不仅仅是检查数字和范围。幸运的是,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),这会立即导致整个匹配失败。不再需要棘手的交替。这个使用了 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";
        }
    }

If you want to check for valid dates, you have to do much more than check numbers and ranges. Fortunately, Perl already has everything you need for this. The Time::Piece module comes with Perl and can parse a date. It knows how to parse dates and do the first round of checks:

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

The output is interesting and no other solution here is close to handling this. Why is 10/6/1582 an invalid date? It doesn't exist in the Gregorian calendar, but there's a simpler reason here. strptime doesn't handle dates before 1900.

But also notice that 2/29/1900 gets turned into 3/1/1900. That's weird and we should fix that, but there's no leap years in years divisible by 100. Well, unless they are divisible by 400, which is why 2/29/2000 works.

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

But let's fix that leap year issue. The tm struct is going a dumb conversion. If the individual numbers are within a reasonable range (0 to 31 for days) regardless of the month, then it converts those days to seconds and adds them to the offset. That's why 2/29/1900 ends up a day later: that 29 gives the same number of seconds as 3/1/1900. If the date is valid, it should come back the same. And since I'm going to roundtrip this, I fix up the date for leading zeros before I do anything with it:

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

Now the output is:

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

That's all a bit long, but that's why we have subroutines:

if( date_is_valid( $date ) ) { ... }

Still want a regex? Okay, lets use the (??{...}) construct to decide if a pattern should fail. Match a bunch of digits and capture that into $1. Now, use (??{...}) to make the next part of the pattern, using any Perl code you like. If you accept the capture, return a null pattern. If you reject it, return the pattern (*FAIL), which immediately causes the whole match to fail. No more tricky alternations. And this one uses the new chained comparison in v5.32 (although I still have misgivings about it):

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";
        }
    }
疾风者 2024-11-15 14:11:28

尝试一下:
<代码>/(0?[1-9]|1[012])\/(0?[1-9]|[12][0-9]|3[01])\/((19|20 )\d\d)/

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

七颜 2024-11-15 14:11:28

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

Is regular expression a must? If not, you better off using a different approach, such as 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";
}
抹茶夏天i‖ 2024-11-15 14:11:28

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

Regex for 0-31 day:

0[1-9]|[12]\d|3[01]) without prefix 0 - when "1", "23"...

([1-9]|[12]\d|3[01]) with prefix 0 - when "01", "04"

(0?[1-9]|[12]\d|3[01]) - with or without "0" - when ""

可可 2024-11-15 14:11:28

更简单的正则表达式:

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

考虑接受的答案和这个表达式:

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

This matches 01 but not 1

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

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

This matches 1 but not 01

不可能添加 OR 子句来让它们都工作。

我建议的那个与两者都匹配。希望这有帮助。

Simpler regex:

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

Consider the accepted answer and this expression:

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

This matches 01 but not 1

The other expression in the accepted answer:

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

This matches 1 but not 01

It is not possible to add an OR clause to get them both working.

The one I suggested matches both. Hope this helps.

却一份温柔 2024-11-15 14:11:28

我已经处理这个问题有一段时间了,我想出的最好的正则表达式如下:

\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

I have been working with this some time and the best regex I've came up with is the following:

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

It will match the following numbers:

1 01 10 15 20 25 30 31

It does not match the following:

32 40 50 90
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文