73

我正在尝试编写一个验证日期的正则表达式。正则表达式需要匹配以下内容

  • 月/日/年年
  • 月/日/年
  • 个位数月份可以以前导零开头(例如:03/12/2008)
  • 个位数的天数可以以前导零开头(例如:2008 年 3 月 2 日)
  • 不能包括 2 月 30 日或 2 月 31 日(例如:2/31/2008)

到目前为止我有

^(([1-9]|1[012])[-/.]([1-9]|[12][0-9]|3[01])[-/.](19|20)\d\d)|((1[012]|0[1-9])(3[01]|2\d|1\d|0[1-9])(19|20)\d\d)|((1[012]|0[1-9])[-/.](3[01]|2\d|1\d|0[1-9])[-/.](19|20)\d\d)$

这匹配正确,但仍包括 2/30/2008 和 2/31/2008。

有人有更好的建议吗?

编辑:我在 RegExLib 上找到了答案

^((((0[13578])|([13578])|(1[02]))[\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\/](([1-9])|([0-2][0-9]))))[\/]\d{4}$|^\d{4}$

它匹配遵循 MM/DD/YYYY 格式的所有有效月份。

感谢大家的帮助。

4

16 回答 16

147

这不是正则表达式的适当使用。你最好使用

[0-9]{2}/[0-9]{2}/[0-9]{4}

然后检查高级语言的范围。

于 2008-09-09T04:37:15.310 回答
60

这是匹配所有有效日期(包括闰年)的 Reg ex。接受的格式 mm/dd/yyyy 或 mm-dd-yyyy 或 mm.dd.yyyy 格式

^(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[1,3-9]|1[0-2])(\/|-|\.)(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:0?[1-9]|1\d|2[0-8])\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$

礼貌阿西克·艾哈迈德

于 2012-01-07T07:58:11.307 回答
38

我来到这里是因为这个问题的标题很宽泛,我正在寻找一个可以用来匹配特定日期格式(如 OP)的正则表达式。但后来我发现,正如许多答案和评论已经全面强调的那样,在提取混入质量差或非结构化源数据的日期时,有许多陷阱使得构建有效模式非常棘手。

在我对这些问题的探索中,我提出了一个系统,它使您能够通过将四个更简单的子表达式排列在一起来构建正则表达式,这些子表达式在分隔符上匹配,并按顺序排列年、月和日字段的有效范围你需要。

这些都是 :-

分隔符

[^\w\d\r\n:] 

这将匹配任何不是单词字符、数字字符、回车符、换行符或冒号的内容。必须有冒号以防止在看起来像日期的时间上匹配(请参阅我的测试数据)

您可以优化这部分模式以加快匹配速度,但这是检测大多数有效分隔符的良好基础。

但是请注意;它将匹配带有混合分隔符的字符串,例如 2/12-73,实际上可能不是有效日期。

年份值

(\d{4}|\d{2})

这匹配一组两位或四位数字,在大多数情况下这是可以接受的,但如果您正在处理 0-999 年或 9999 年之后的数据,您需要决定如何处理它,因为在大多数情况下是 1、3或 > 4 位数年份是垃圾。

月份值

(0?[1-9]|1[0-2])

匹配 1 到 12 之间的任何数字,带或不带前导零 - 注意:0 和 00 不匹配。

日期值

(0?[1-9]|[12]\d|30|31)

匹配 1 到 31 之间的任何数字,带或不带前导零 - 注意:0 和 00 不匹配。

此表达式匹配日期、月份、年份格式的日期

(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})

但它也会匹配一些年份、月份日期。它还应该与边界运算符一起预订,以确保选择整个日期字符串并防止从格式不正确的数据中提取有效的子日期,即没有边界标签 20/12/194 匹配为 20/12/19 和101/12/1974 匹配为 01/12/1974

将下一个表达式的结果与上一个表达式的结果与废话部分(下)中的测试数据进行比较

\b(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})\b

此正则表达式中没有验证,因此将匹配格式正确但无效的日期,例如 31/02/2001。这是一个数据质量问题,正如其他人所说,您的正则表达式不需要验证数据。

因为您(作为开发人员)无法保证您确实需要在代码中执行和处理额外验证的源数据的质量,所以如果您尝试匹配验证 RegEx 中的数据,它会变得非常混乱并且难以支持没有非常简洁的文档。

垃圾进垃圾出。

话虽如此,如果您确实有日期值不同的混合格式,那么您必须尽可能多地提取;您可以像这样将几个表达式组合在一起;

这个(灾难性的)表达式匹配 DMY 和 YMD 日期

(\b(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})\b)|(\b(0?[1-9]|1[0-2])[^\w\d\r\n:](0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](\d{4}|\d{2})\b)

但是您无法判断像 1973 年 6 月 9 日这样的日期是 9 月 6 日还是 6 月 9 日。我正在努力想一个不会在某个地方引起问题的场景,这是一种不好的做法,你不应该那样处理它 - 找到数据所有者并用治理锤击打他们.

最后,如果你想匹配一个没有分隔符的 YYYYMMDD 字符串,你可以去掉一些不确定性,表达式看起来像这样

\b(\d{4})(0[1-9]|1[0-2])(0[1-9]|[12]\d|30|31)\b

但请再次注意,它将匹配格式正确但无效的值,例如 20010231(2 月 31 日!):)

测试数据

在试验这个线程中的解决方案时,我最终得到了一个测试数据集,其中包括各种有效和无效日期以及一些您可能希望或可能不希望匹配的棘手情况,即可以匹配为日期和日期的时间多行。

我希望这对某人有用。

Valid Dates in various formats

Day, month, year
2/11/73
02/11/1973
2/1/73
02/01/73
31/1/1973
02/1/1973
31.1.2011
31-1-2001
29/2/1973
29/02/1976 
03/06/2010
12/6/90

month, day, year
02/24/1975 
06/19/66 
03.31.1991
2.29.2003
02-29-55
03-13-55
03-13-1955
12\24\1974
12\30\1974
1\31\1974
03/31/2001
01/21/2001
12/13/2001

Match both DMY and MDY
12/12/1978
6/6/78
06/6/1978
6/06/1978

using whitespace as a delimiter

13 11 2001
11 13 2001
11 13 01 
13 11 01
1 1 01
1 1 2001

Year Month Day order
76/02/02
1976/02/29
1976/2/13
76/09/31

YYYYMMDD sortable format
19741213
19750101

Valid dates before Epoch
12/1/10
12/01/660
12/01/00
12/01/0000

Valid date after 2038

01/01/2039
01/01/39

Valid date beyond the year 9999

01/01/10000

Dates with leading or trailing characters

12/31/21/
31/12/1921AD
31/12/1921.10:55
12/10/2016  8:26:00.39
wfuwdf12/11/74iuhwf
fwefew13/11/1974
01/12/1974vdwdfwe
01/01/99werwer
12321301/01/99

Times that look like dates

12:13:56
13:12:01
1:12:01PM
1:12:01 AM

Dates that runs across two lines

1/12/19
74

01/12/19
74/13/1946

31/12/20
08:13

Invalid, corrupted or nonsense dates

0/1/2001
1/0/2001
00/01/2100
01/0/2001
0101/2001
01/131/2001
31/31/2001
101/12/1974
56/56/56
00/00/0000
0/0/1999
12/01/0
12/10/-100
74/2/29
12/32/45
20/12/194

2/12-73
于 2016-10-28T16:45:10.893 回答
13

可维护的 Perl 5.10 版本

/
  (?:
      (?<month> (?&mon_29)) [\/] (?<day>(?&day_29))
    | (?<month> (?&mon_30)) [\/] (?<day>(?&day_30))
    | (?<month> (?&mon_31)) [\/] (?<day>(?&day_31))
  )
  [\/]
  (?<year> [0-9]{4})
  
  (?(DEFINE)
    (?<mon_29> 0?2 )
    (?<mon_30> 0?[469]   | (11) )
    (?<mon_31> 0?[13578] | 1[02] )

    (?<day_29> 0?[1-9] | [1-2]?[0-9] )
    (?<day_30> 0?[1-9] | [1-2]?[0-9] | 30 )
    (?<day_31> 0?[1-9] | [1-2]?[0-9] | 3[01] )
  )
/x

您可以在此版本中按名称检索元素。

say "Month=$+{month} Day=$+{day} Year=$+{year}";

(没有尝试限制年份的值。)

于 2008-09-13T21:28:43.710 回答
8

在以下格式下控制日期有效性:

YYYY/MM/DD 或 YYYY-MM-DD

我建议您使用以下正则表达式:

(((19|20)([2468][048]|[13579][26]|0[48])|2000)[/-]02[/-]29|((19|20)[0-9]{2}[/-](0[4678]|1[02])[/-](0[1-9]|[12][0-9]|30)|(19|20)[0-9]{2}[/-](0[1359]|11)[/-](0[1-9]|[12][0-9]|3[01])|(19|20)[0-9]{2}[/-]02[/-](0[1-9]|1[0-9]|2[0-8])))

火柴

2016-02-29 | 2012-04-30 | 2019/09/31

不匹配

2016-02-30 | 2012-04-31 | 2019/09/35

如果您只想允许 '/' 或 '-' 分隔符,您可以自定义它。此 RegEx 严格控制日期的有效性并验证 28,30 和 31 天的月份,甚至闰年的 29/02 月份。

试试吧,它工作得很好,可以防止你的代码出现很多错误!

仅供参考:我为 SQL 日期时间做了一个变体。你会在那里找到它(寻找我的名字):正则表达式来验证时间戳

欢迎反馈:)

于 2013-04-12T09:44:16.847 回答
4

听起来您为此目的过度扩展了正则表达式。我要做的是使用正则表达式来匹配一些日期格式,然后使用单独的函数来验证如此提取的日期字段的值。

于 2008-09-09T04:34:14.243 回答
3

Perl 扩展版

注意/x修饰符的使用。

/^(
      (
        ( # 31 day months
            (0[13578])
          | ([13578])
          | (1[02])
        )
        [\/]
        (
            ([1-9])
          | ([0-2][0-9])
          | (3[01])
        )
      )
    | (
        ( # 30 day months
            (0[469])
          | ([469])
          | (11)
        )
        [\/]
        (
            ([1-9])
          | ([0-2][0-9])
          | (30)
        )
      )
    | ( # 29 day month (Feb)
        (2|02)
        [\/]
        (
            ([1-9])
          | ([0-2][0-9])
        )
      )
    )
    [\/]
    # year
    \d{4}$
  
  | ^\d{4}$ # year only
/x

原来的

^((((0[13578])|([13578])|(1[02]))[\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\/](([1-9])|([0-2][0-9]))))[\/]\d{4}$|^\d{4}$
于 2008-09-13T20:56:42.570 回答
3

如果您没有使上述建议起作用,我使用它,因为它可以获取任何日期,我通过 50 个链接运行此表达式,并且它获取了每个页面上的所有日期。

^20\d\d-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(0[1-9]|[1-2][0-9]|3[01])$ 
于 2012-01-21T00:03:21.327 回答
3

此正则表达式使用匹配的分隔符验证 01-01-2000 和 12-31-2099 之间的日期。

^(0[1-9]|1[012])([- /.])(0[1-9]|[12][0-9]|3[01])\2(19|20)\d\d$
于 2013-04-29T17:53:16.907 回答
2
    var dtRegex = new RegExp(/[1-9\-]{4}[0-9\-]{2}[0-9\-]{2}/);
    if(dtRegex.test(date) == true){
        var evalDate = date.split('-');
        if(evalDate[0] != '0000' && evalDate[1] != '00' && evalDate[2] != '00'){
            return true;
        }
    }
于 2012-11-23T18:00:48.723 回答
1

正则表达式并不是为了验证数字范围(当它前面的数字恰好是 2 并且前面的数字恰好低于 6 时,这个数字必须是从 1 到 5)。只需在正则表达式中查找数字放置模式即可。如果您需要验证日期的质量,请将其放入日期对象 js/c#/vb 中,并在那里询问数字。

于 2008-09-09T04:36:22.380 回答
1

我知道这不能回答您的问题,但是您为什么不使用日期处理例程来检查它是否是有效日期?即使您使用 (?!31/0?2) 之类的负前瞻断言修改正则表达式(即不匹配 31/2 或 31/02),您仍然会遇到在非闰年接受 29 02 的问题以及关于单个分隔符日期格式。

如果您想真正验证日期,问题并不容易,请查看此论坛主题

有关示例或更好的方法,在 C# 中,请查看此链接

如果您使用其他平台/语言,请告诉我们

于 2008-09-09T04:43:17.607 回答
1

Perl 6 版本

rx{
  ^

  $<month> = (\d ** 1..2)
  { $<month> <= 12 or fail }

  '/'

  $<day> = (\d ** 1..2)
  {
    given( +$<month> ){
      when 1|3|5|7|8|10|12 {
        $<day> <= 31 or fail
      }
      when 4|6|9|11 {
        $<day> <= 30 or fail
      }
      when 2 {
        $<day> <= 29 or fail
      }
      default { fail }
    }
  }

  '/'

  $<year> = (\d ** 4)

  $
}

在您使用它来检查输入后,这些值可以在, ,中$/或单独使用。(这些只是访问值的语法)$<month>$<day>$<year>$/

没有尝试检查年份,或者它与非闰年的 2 月 29 日不匹配。

于 2008-09-13T21:42:45.657 回答
0

如果您要坚持使用正则表达式执行此操作,我建议您使用以下方法:

( (0?1|0?3| <...> |10|11|12) / (0?1| <...> |30|31) |
  0?2 / (0?1| <...> |28|29) ) 
/ (19|20)[0-9]{2}

可能使阅读和理解成为可能。

于 2008-09-09T04:45:27.647 回答
0

/(([1-9]{1}|0[1-9]|1[0-2])\/(0[1-9]|[1-9]{1}|[12]\d|3[01])\/[12]\d{3})/

这将验证以下内容 -

  • 一位和 2 位数的日期,范围从 1 到 31。例如,1、01、11、31。
  • 单个和 2 位数月份,范围从 1 到 12。例如。1、01、12。
  • 4 位数年份。例如。2021 年,1980 年。
于 2021-09-11T04:50:15.827 回答
-1

一种稍微不同的方法,可能对您有用,也可能没用。

我在 php.ini 中。

与此相关的项目永远不会有早于 2008 年 1 月 1 日的日期。因此,我采用输入的“日期”并使用 strtotime()。如果答案是 >= 1199167200,那么我有一个对我有用的日期。如果输入了看起来不像日期的内容,则返回 -1。如果输入 null,它会返回今天的日期编号,因此您需要先检查非 null 条目。

适用于我的情况,也许你的也是?

于 2008-10-21T12:56:45.993 回答