【问题标题】:Regular Expression to match valid dates正则表达式匹配有效日期
【发布时间】:2010-09-08 06:00:50
【问题描述】:

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

  • 月/日/YYYY
  • MM/DD/YYYY
  • 个位数月份可以以前导零开头(例如: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 上找到了the answer

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

感谢大家的帮助。

【问题讨论】:

  • 你的同事会恨你的。
  • 这不考虑闰年。它输出 02/29/2011 作为有效日期。
  • 检查我的答案以获取考虑闰年的正则表达式。
  • It matches all valid months that follow the MM/DD/YYYY format. 无法验证 1234 ! :(

标签: regex date


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

      • 这不是一个正确的正则表达式,因为它只检查月/数据/年的位数。
      • 同意,这就像对电话号码使用正则表达式来检查所有可能的区号。如果非闰年包括 2/29,如果包括 4/31、6/31、9/31 和 11/31,那么不包括 2/30 或 2/31 有什么意义?
      • @SanjeevSingh 这就是重点——正则表达式不应用于数据验证。这将匹配类似日期的字符串,然后可以根据需要使用适当的日期库进行验证。
      • @Chris 也许,您在答案的开头添加 ^ 并在答案的末尾添加 $ 以仅匹配整个日期字符串。 (顺便说一句:我按照你的建议在代码中实现了对闰年等的检查。是的:这肯定比你说的更好)。
      • ^(([0-9]{0,2})( "分隔符" )?){0,2}[1-2]?([0-9]{0,3 })
      【解决方案4】:

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

      如果您想真正验证日期,问题并不容易,请查看forum thread

      如需示例或更好的方法,请在 C# 中查看 this link

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

      【讨论】:

        【解决方案5】:

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

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

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

        【讨论】:

          【解决方案6】:

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

          【讨论】:

            【解决方案7】:

            可维护的 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}";
            

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

            【讨论】:

            • 这不匹配“12/00/0000”吗?
            • @mwolfetech 大多数其他人也是如此,如果您需要检查,应该很容易弄清楚如何修改这个正则表达式。
            • +1 拥有一个实际上可维护的版本。
            【解决方案8】:

            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)
            
              $
            }
            

            在您使用它检查输入后,这些值在$/ 中可用,或单独作为$&lt;month&gt;$&lt;day&gt;$&lt;year&gt;。 (这些只是访问$/ 中值的语法)

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

            【讨论】:

              【解决方案9】:

              一种略有不同的方法,可能对你有用,也可能没用。

              我在php中。

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

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

              【讨论】:

                【解决方案10】:

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

                【讨论】:

                • 公元前 20 年呢? (如-20/1/1
                • @Odys - 你真的需要为某事编写程序,还是你把批评从帽子里拿出来?
                • 是的,在我发表评论时(2 多年前),我需要表示可以追溯到更早的日期。
                • 在js中声明使用方式如下var dateReg = new RegExp(['^(?:(?:(?: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})$'].join(''),"g");
                • 我们可以得到 dd/mm/yyyy 的修改版本吗?
                【解决方案11】:

                如果您没有让上述建议起作用,我会使用它,因为它可以获取我通过 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])$ 
                

                【讨论】:

                  【解决方案12】:
                      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;
                          }
                      }
                  

                  【讨论】:

                    【解决方案13】:

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

                    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 日期时间做了一个变体。你会在那里找到它(寻找我的名字):Regular Expression to validate a timestamp

                    欢迎反馈:)

                    【讨论】:

                      【解决方案14】:

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

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

                      【讨论】:

                        【解决方案15】:

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

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

                        这些是:-

                        分隔符

                        [^\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
                        

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

                        因为您(作为开发人员)无法保证您确实需要在代码中执行和处理额外验证的源数据的质量,如果您尝试匹配验证数据如果没有非常简明的文档,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
                        

                        【讨论】:

                        • 非常好的例子解释!也可以考虑添加其他月份格式,例如 MMM 和完整月份名称正则表达式!
                        • 谢谢! “灾难性”表达是否有错误?我无法让它匹配 yyyy-mm-dd-format 日期,不得不将其更改为(在 Perl 中):/( (\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) | (\d{4}|\d{2})[^\w\d\r\n:](\b(0?[1-9]|1[0-2])[^\w\d\r\n:](0?[1-9]|[12]\d|30|31)\b) )/x' --- 测试为:echo 2017-01-28 | perl -ne 'print "$1\n" if /((\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)|(\d{4}|\d{2})[^\w\d\r\n:](\b(0?[1-9]|1[0-2])[^\w\d\r\n:](0?[1-9]|[12]\d|30|31)\b))/'
                        • 是的,这对于不使用正则表达式进行验证很有用。 KISS(保持简单愚蠢!)是在这种情况下必须遵循的另一个原则。很棒的文档/描述,@Bob!
                        【解决方案16】:

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

                        这将验证跟随 -

                        • 一位和两位数的日期,范围从 1 到 31。例如,1、01、11、31。
                        • 单位和两位数月份,范围从 1 到 12。例如。 1、01、12。
                        • 4 位数年份。例如。 2021 年,1980 年。

                        【讨论】:

                          猜你喜欢
                          • 2011-08-29
                          • 2011-08-24
                          • 1970-01-01
                          • 2011-06-10
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多