关于正则表达式:正则表达式以匹配有效日期

关于正则表达式:正则表达式以匹配有效日期

Regular Expression to match valid dates

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

  • M / D / YYYY
  • MM / DD / YYYY
  • 单位数字月份可以以前导零开头(例如:03/12/2008)
  • 单位位数可以以前导零开头(例如:2008年3月2日)
  • 不能包括2月30日或2月31日(例如:2/31/2008)

到目前为止,我有

1
^(([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上找到了答案

1
^((((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格式的所有有效月份。

谢谢大家的帮助。


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

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

然后使用高级语言检查范围。


这是与所有有效日期(包括leap年)匹配的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})$

由Asiq Ahamed提供


我之所以来到这里,是因为这个问题的标题很宽泛,我一直在寻找可用于匹配特定日期格式(例如OP)的正则表达式。但是我随后发现,正如许多答案和评论已全面突出显示的那样,当提取与质量低劣或非结构化源数据混在一起的日期时,有许多陷阱使构建有效模式变得非常棘手。

在探讨问题时,我想出了一个系统,使您可以通过将四个在分隔符上匹配的更简单的子表达式以及顺序中的年,月和日字段的有效范围排列在一起,来构建正则表达式您需要。

这些是 :-

测力计

1
2
3
[^\\w\\d\
\
:]

这将匹配不是单词字符,数字字符,回车符,换行符或冒号的任何内容。冒号必须在那儿以防止与日期类似的时间匹配(请参阅我的测试数据)

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

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

年值

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

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

月值

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

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

日期值

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

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

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

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

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

将下一个表达式的结果与上面的表达式的结果与无意义部分中的测试数据进行比较(如下)

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

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

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

垃圾进垃圾出。

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

此(灾难性)表达式匹配DMY和YMD日期

1
2
3
4
5
6
7
8
9
(\\b(0?[1-9]|[12]\\d|30|31)[^\\w\\d\
\
:](0?[1-9]|1[0-2])[^\\w\\d\
\
:](\\d{4}|\\d{2})\\b)|(\\b(0?[1-9]|1[0-2])[^\\w\\d\
\
:](0?[1-9]|[12]\\d|30|31)[^\\w\\d\
\
:](\\d{4}|\\d{2})\\b)

但是您将无法确定1973年6月9日这样的日期是9月6日还是6月9日。我正在努力思考这样一种情况,即不会在某个地方造成问题,这是一种不好的做法,您不必这样处理-找到数据所有者并用治理锤来打击他们。

最后,如果要匹配不带分隔符的YYYYMMDD字符串,则可以消除一些不确定性,表达式如下所示

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

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

测试数据

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

我希望这对某人有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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

可维护的Perl 5.10版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/
  (?:
      (?<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

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

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

(未尝试限制年份的值。)


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

YYYY/MM/DD or YYYY-MM-DD

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

1
(((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月的leap年。

尝试一下,它可以很好地工作,并防止您的代码出现很多错误!

仅供参考:我为SQL日期时间做了一个变体。您将在此处找到它(查找我的名字):正则表达式以验证时间戳

欢迎反馈:)


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


如果您没有得到上述建议的支持,请使用此方法,因为它可以通过50个链接运行此表达式的任何日期,并且每个页面上都有所有日期。

1
^20\\d\\d-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(0[1-9]|[1-2][0-9]|3[01])$

Perl扩展版本

注意使用/x修饰符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/^(
      (
        ( # 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

原版的

1
^((((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}$

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

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

1
2
3
4
5
6
7
    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;
        }
    }

Perl 6版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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)

  $
}

使用此命令检查输入后,值在$/中可用,或分别作为$$$提供。 (这些只是用于访问$/中的值的语法)

未尝试检查年份,或在非leap年不匹配2月29日。


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

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

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

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


正则表达式不是要验证数字范围的(当它前面的数字恰好是2而前面的数字恰好是6以下时,此数字必须从1到5)。
只需查找正则表达式中数字的放置方式即可。如果您需要验证日期的质量,请将其放入日期对象js / c#/ vb中,然后在其中查询数字。


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

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

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


略有不同的方法可能对您有用也可能没有用。

我在php中。

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

适用于我的情况,也许也适合您?


推荐阅读