1、概述

首先需要说明的一点,无论是 Winform ,还是 Webform ,都有很成熟的日历控件,无论从易用性还是可扩展性上看,日期的选择和校验还是用日历控件来实现比较好。

前几天在 CSDN 多个版块看到需要日期正则的帖子,所以整理了这篇文章,和大家一起讨论交流,如有遗漏或错误的地方,还请大家指正。

日期正则一般是对格式有要求,且数据不是直接由用户输入时使用。因应用场景的不同,写出的正则也不同,复杂程度也自然不同。正则的书写需要根据具体情况具体分析,一个基本原则就 是:只写合适的,不写复杂的 。

对于日期提取,只要能与非日期区分开,写最简单的正则即可,如

\d{4}-\d{2}-\d{2}

如果可以在源字符串中唯一定位 yyyy-MM-dd 格式的日期,则可用做提取。

对于验证,如果仅仅是验证字符组成及格式是没有多大意义的,还要加入对规则的校验。由于闰年的存在,使得日期的校验正则变得比较复杂。

先来考察一下日期的有效范围以及什么是闰年。

2、日期的规则

2.1 日期的有效范围

对于日期的有效范围,不同的应用场景会有所不同。

MSDN 中定义的 DateTime 对象的有效范围是: 0001-01-01 00:00:00 到 9999-12-31 23:59:59 。

UNIX 时间戳的 0 按照 ISO 8601 规范为 : 1970-01-01T00:00:00Z 。

而实际应用中,日期的范围基本上不会超出 DateTime 所规定的范围,所以正则验证取其中常用的日期范围即可。

2.2 什么是闰年

(以下摘自百度百科)

闰年 (leap year) 是为了弥补因人为历法规定造成的年度天数与地球实际公转周期的时间差而设立的。补上时间差的年份为闰年。

地球绕日运行周期为 365 天 5 小时 48 分 46 秒(合 365.24219 天),即一 回归年( tropical year )。公历的平年只有 365 日,比回归年短约 0.2422 日,每四年累积约一天,把这一天加于 2 月末(即 2 月 29 日),使当年时间长度变为 366 日,这一年就为闰年。

需要注意的是 , 现在的公历是根据罗马人的“儒略历”改编而得。由于当时没有了解到每年要多算出 0.0078 天的问题,从公元前 46 年,到 16 世纪,一共累计多出了 10 天。为此,当时的教皇格雷果里十三世,将 1582 年 10 月 5 日人为规定为 10 月 15 日。并开始了新闰年规定。即规定公历年份是整百数的,必须是 400 的倍数才是闰年,不是 400 的倍数的就是平年。比如, 1700 年、 1800 年和 1900 年为平年, 2000 年为闰年。此后,平均每年长度为 365.2425 天,约 4 年出现 1 天的偏差。按照每四年一个闰年计算,平均每年就要多算出 0.0078 天,经过四百年就会多出大约 3 天来,因此,每四百年中要减少三个闰年。闰年的计算,归结起来就是通常说的:四年一闰;百年不闰,四百年再闰 。

2.3 日期的格式

根据不同的语言文化,日期的连字符会有所不同,通常有以下几种格式:

yyyyMMdd

yyyy-MM-dd

yyyy/MM/dd

yyyy.MM.dd

3、日期正则表达式构建

3.1 规则分析

写复杂正则的一个常用方法,就是先把不相关的需求拆分开,分别写出对应的正则,然后组合,检查一下相互的关联关系以及影响,基本上就可以得出对应的正则。

按闰年的定义可知,日期可以有几种分类方法。

3.1.1 根据天数是否与年份有关划分为两类

与年份无关的一类中,根据每月天数的不同,又可细分为两类

1 、 3 、 5 、 7 、 8 、 10 、 12 月为 1-31 日

4 、 6 、 9 、 11 月为 1-30 日

与年份有关的一类中

平年 2 月为 1-28 日

闰年 2 月为 1-29 日

所有年份的所有月份都包含 1-28 日

所有年份除 2 月外都包含 29 和 30 日

所有年份 1 、 3 、 5 、 7 、 8 、 10 、 12 月都包含 31 日

闰年 2 月包含 29 日

3.1.2 根据包含日期不同可划分为四类

3.1.3 分类方法选择

因为日期分类之后的实现,是要通过 (exp1|exp2|exp3) 这种分支结构来实现的,而分支结构是从左侧分支依次向右开始尝试匹配,当有一个分支匹配成功时,就不再向右尝试,否则尝试所有分支后并报告失败。

分支的多少,每个分支的复杂程度都会影响匹配效率,考虑到被验证日期概率分布,绝大多数都是落到 1-28 日内,所以采用第二种分类方法,会有效提高匹配效率。

3.2 正则实现

采用 3.1.2 节的分类方法,就可以针对每一个规则写出对应的正则,以下暂按 MM-dd 格式进行实现。

先考虑与年份无关的前三条规则,年份可统一写作

(?!0000)[0-9]{4}

下面仅考虑月和日的正则

包括平年在内的所有年份的月份都包含 1-28 日

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

包括平年在内的所有年份除 2 月外都包含 29 和 30 日

(0[13-9]|1[0-2])-(29|30)

包括平年在内的所有年份 1 、 3 、 5 、 7 、 8 、 10 、 12 月都包含 31 日

(0[13578]|1[02])-31)

合起来就是除闰年的 2 月 29 日外的其它所有日期

(?!0000)[0-9]{4} -((0[1-9]|1[0-2]) -(0[1-9]|1[0-9]|2[0-8]) |(0[13-9]|1[0-2]) -(29|30) |(0[13578]|1[02]) -31 )

接下来考虑闰年的实现

闰年 2 月包含 29 日

这里的月和日是固定的,就是 02-29 ,只有年是变化的。

可通过以下代码输出所有的闰年年份,考察规则

for ( int i = 1; i < 10000; i++)

{

if ((i % 4 == 0 && i % 100 != 0) || i % 400 == 0)

{

richTextBox2.Text += string .Format( "{0:0000}" , i) + "\n" ;

}

}

for (int i = 1; i < 10000; i++)

{

if ((i % 4 == 0 && i % 100 != 0) || i % 400 == 0)

{

richTextBox2.Text += string.Format("{0:0000}", i) + "\n";

}

}

根据闰年的规则,很容易整理出规则,四年一闰;

([0-9]{2}(0[48]|[2468][048]|[13579][26])

百年不闰,四百年再闰。

(0[48]|[2468][048]|[13579][26])00

合起来就是所有闰年的 2 月 29 日

([0-9]{2}(0[48]|[2468][048]|[13579][26])|(0[48]|[2468][048]|[13579][26])00)-02-29)

四条规则都已实现,且互相间没有影响,合起来就是所有符合 DateTime 范围的日期的正则

^((?!0000)[0-9]{4} -((0[1-9]|1[0-2]) -(0[1-9]|1[0-9]|2[0-8]) |(0[13-9]|1[0-2]) -(29|30) |(0[13578]|1[02]) -31 )|([0-9]{2}(0[48]|[2468][048]|[13579][26])|(0[48]|[2468][048]|[13579][26])00) -02 -29)$

考虑到这个正则表达式仅仅是用作验证,所以捕获组没有意义,只会占用资源,影响匹配效率,所以可以使用非捕获组来进行优化。

^(?:(?!0000)[0-9]{4} -(?:(?:0[1-9]|1[0-2]) -(?:0[1-9]|1[0-9]|2[0-8]) |(?:0[13-9]|1[0-2]) -(?:29|30) |(?:0[13578]|1[02]) -31 )|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00) -02 -29 )$

以上正则年份 0001-9999 ,格式 yyyy-MM-dd 。 可以通过以下代码验证正则的有效性和性能

DateTime dt = new DateTime(1, 1, 1);

DateTime endDay = new DateTime(9999, 12, 31);

Stopwatch sw = new Stopwatch();

sw.Start();

Regex dateRegex = new Regex(@ "^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$" );

//Regex dateRegex = new Regex(@"^((?!0000)[0-9]{4}-((0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-8])|(0[13-9]|1[0-2])-(29|30)|(0[13578]|1[02])-31)|([0-9]{2}(0[48]|[2468][048]|[13579][26])|(0[48]|[2468][048]|[13579][26])00)-02-29)$");

Console.WriteLine("开始日期: " + dt.ToString( "yyyy-MM-dd" ));

while (dt <= endDay)

{

if (!dateRegex.IsMatch(dt.ToString( "yyyy-MM-dd" )))

{

Console.WriteLine(dt.ToString("yyyy-MM-dd" ) + " false" );

}

if (dt == endDay)

{

break ;

}

dt = dt.AddDays(1);

}

Console.WriteLine("结束日期: " + dt.ToString( "yyyy-MM-dd" ));

sw.Stop();

Console.WriteLine("测试用时: " + sw.ElapsedMilliseconds + "ms" );

Console.WriteLine("测试完成!" );

Console.ReadLine();

DateTime

dt = new DateTime(1, 1, 1);

DateTime endDay = new DateTime(9999, 12, 31);

Stopwatch sw = new Stopwatch();

sw.Start();

Regex dateRegex = new

Regex(@"^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$");

//Regex

dateRegex = new

Regex(@"^((?!0000)[0-9]{4}-((0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-8])|

(0[13-9]|1[0-2])-(29|30)|(0[13578]|1[02])-31)|([0-9]{2}(0[48]|[2468]

[048]|[13579][26])|(0[48]|[2468][048]|[13579][26])00)-02-29)$");

Console.WriteLine("开始日期: " + dt.ToString("yyyy-MM-dd"));

while (dt <= endDay)

{ if (!dateRegex.IsMatch(dt.ToString("yyyy-MM-dd"))) {

Console.WriteLine(dt.ToString("yyyy-MM-dd") + " false"); } if (dt ==

endDay) { break; } dt = dt.AddDays(1);

}

Console.WriteLine("结束日期: " + dt.ToString("yyyy-MM-dd"));

sw.Stop();

Console.WriteLine("测试用时: " + sw.ElapsedMilliseconds + "ms");

Console.WriteLine("测试完成!");

Console.ReadLine();

4、日期正则表达式扩展

4.1 “年月日”形式扩展

以上实现的是 yyyy-MM-dd 格式的日期验证,考虑到连字符的不同,以及月和日可能为 M 和 d ,即 yyyy-M-d 的格式,可以对以上正则进行扩展

^(?:(?!0000)[0-9]{4} ([-/.]?) (?:(?:0?[1-9]|1[0-2]) ([-/.]?) (?:0?[1-9]|1[0-9]|2[0-8]) |(?:0?[13-9]|1[0-2]) ([-/.]?) (?:29|30) |(?:0?[13578]|1[02]) ([-/.]?) 31 )|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00) ([-/.]?) 0?2 ([-/.]?) 29 )$

使用反向引用进行简化,年份 0001-9999 ,格式 yyyy-MM-dd 或 yyyy-M-d ,连字符可以没有或是“ - ”、“ / ”、“ . ”之一。

^(?:(?!0000)[0-9]{4} ([-/.]?) (?:(?:0?[1-9]|1[0-2]) \1 (?:0?[1-9]|1[0-9]|2[0-8]) |(?:0?[13-9]|1[0-2]) \1 (?:29|30) |(?:0?[13578]|1[02]) \1 (?:31) )|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00) ([-/.]?) 0?2 \2 (?:29) )$

这就是“年月日”这种形式最全的一个正则了,不同含义部分以不同颜色标识,可以根据自己的需要进行栽剪。

4.2 其它形式扩展

了解了以上正则各部分代表的含义,互相间的关系后,就很容易扩展成其它格式的日期正则,如 dd/MM/yyyy 这种“日月年”格式的日期。

^(?:(?:(?:0?[1-9]|1[0-9]|2[0-8]) ([-/.]?) (?:0?[1-9]|1[0-2]) |(?:29|30) ([-/.]?) (?:0?[13-9]|1[0-2]) |31 ([-/.]?) (?:0?[13578]|1[02]) )([-/.]?) (?!0000)[0-9]{4} |29 ([-/.]?) 0?2 ([-/.]?) (?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00) )$

这种格式需要注意的就是不能用反向引用来进行优了。连字符等可根据自己的需求栽剪。

4.3 添加时间的扩展

时间的规格很明确,也很简单,基本上就 HH:mm:ss 和 H:m:s 两种形式。

([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]

合入到日期的正则中, yyyy-MM-dd HH:mm:ss

^(?:(?!0000)[0-9]{4} -(?:(?:0[1-9]|1[0-2]) -(?:0[1-9]|1[0-9]|2[0-8]) |(?:0[13-9]|1[0-2]) -(?:29|30) |(?:0[13578]|1[02]) -31 )|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00) -02 -29 )\s+([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9] $

4.4 年份定制

以上所有涉及到平年的年份里,使用的是 0001-9999 。当然,年份也可以根据闰年规则定制。

如年份 1600-9999 ,格式 yyyy-MM-dd 或 yyyy-M-d ,连字符可以没有或是“ - ”、“ / ”、“ . ”之一。

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

5、特别说明

以上正则采用的是最基本的正则语法规则,绝大多数采用传统 NFA 引擎的语言都可以支持,包括 JavaScript 、 Java 、 .NET 等。

另外需求说明的是,虽然日期的规则相对明确,可以采用这种方式裁剪来得到符合要求的日期正则,但是并不推荐这样使用正则,正则的强大在于它的灵活性,可以根据需求,量身打造最合适的正则,如果只是用来套用模板,那正则也就不称其为正则了。

正则的语法规则并不多,而且很容易入门,掌握语法规则,量体裁衣,才是正则之“道”。

python正则匹配日期2019-03-11_正则表达式验证日期(多种日期格式)——转载相关推荐

  1. python正则匹配ip地址_IP地址正则表达式匹配方法,ip正则表达式匹配

    IP地址正则表达式匹配方法,ip正则表达式匹配 正则表达式(Regular Expression,在代码中常简写为regex.regexp或RE)是计算机科学的一个概念.正则表达式使用单个字符串来描述 ...

  2. python正则匹配11个数字_Python正则表达式匹配字符串中的数字

    导读 这篇文章主要介绍了Python正则表达式匹配字符串中的数字,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下 1.使用"\d+"匹配全数字 ...

  3. python正则匹配固定汉字_Python用正则表达式匹配汉字

    原博文 2019-11-07 19:54 − #### Python用正则表达式匹配汉字 ##### 匹配多个汉字,不包括空格 ```python import re res = re.match(r ...

  4. python正则表达式匹配数字或者逗号_python正则表达式去掉数字中的逗号(python正则匹配逗号)...

    分析 数字中经常是3个数字一组,之后跟一个逗号,因此规律为:***,***,*** 正则式[a-z]+,[a-z]? import re sen = "abc,123,456,789,mnp ...

  5. python正则匹配表达式(2)

    上节主要讲解python正则匹配的匹配表达式,而需要调用相应的API才能解决如何匹配的问题. 在python官方文档Regular Expression HOWTO给出了python匹配函数,官方给出 ...

  6. Python正则匹配 去除文本中的各类emoji表情符号

    我们的文本数据中经常会带有很多表情,如何完整地清除得到高质量的文本供我们利用呢? p = re.compile(u'['u'\U0001F300-\U0001F64F' u'\U0001F680-\U ...

  7. 使用Python 正则匹配两个特定字符之间的字符方法

    string = "<KeysViewHDF5 ['Inoisy']>" import redef cut_out(a,b,string):result = re.fi ...

  8. 手机号判断正则php2019,2019手机号码JS正则表达式验证实例代码

    概念 正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符.及这些特定字符的组合,组成一个"规则字符串",这个"规则字符串"用来表达对字符串的 ...

  9. python正则匹配ABAC型词语

    python正则匹配ABAC型词语 前言 代码 前言 本人第一次写博客,欢迎指出不足. 代码 今天做python作业,发现很多文章不能很好地解决匹配ABAC型的词语,例如: patten =r'((. ...

  10. python正则匹配txt特定字符串(有换行)

    python正则匹配txt特定字符串(有换行) 在原txt文件中,我们需要匹配出的字符串为:休闲服务(中间参杂着换行) 直接复制到notebook里进行处理 完整代码 在原txt文件中,我们需要匹配出 ...

最新文章

  1. Android实践 -- 监听应用程序的安装、卸载
  2. 苹果的程序员三大定律
  3. SAP Fiori internationalization(国际化)实现的一些例子
  4. [bzoj1036]树的统计
  5. strong vs copy
  6. Kotlin学习笔记30 补充 作用域函数
  7. 貌似必须背下的一组数据。 C语言中的优先级顺序
  8. linux内存管理源码分析 - 页框分配器
  9. 软件测试思想者 - 软件评测师考试顺利通关
  10. Linux操作系统知识点总结
  11. javaScript特殊知识点归纳
  12. PD3.0 PPS限流
  13. idea选中多行的一列、一竖(不是多行的全部内容)
  14. 带修莫队 的 小优化 (针对yxc版本)
  15. 主攻文推荐攻守都有系统_坚守最后一道防线-第五十五章 攻守转换在线阅读-顶点小说...
  16. include在HTML中的用法
  17. 计算机维修志愿服务,计控学院深入社区开展“电脑维修”学雷锋志愿活动
  18. SEO之网站快速被收录
  19. 低蓝光认证:TUV莱茵与TUV南德 有啥区别?
  20. 毕业答辩PPT制作和讲述要点

热门文章

  1. 华为ensp 多区域OSPF配置
  2. 联想小新锁屏壁纸怎么换_联想_ThinkPad|ThinkCentre|ThinkStation服务与驱动下载_常见问题...
  3. 金山办公推出协同办公全家桶 WPS升级为超级工作入口
  4. 八款优秀的 Linux 轻量级 Web 浏览器
  5. linux服务器查sn,命令查看服务器SN号
  6. C++模板编程(18)---模板实例化instantiation
  7. 商业模式及其 SubDAO 深入研究
  8. GDAL坐标转换六参的使用方法
  9. 电机与拖动综合控制实验matlab,电机与拖动控制实验及其MATLAB仿真
  10. 有关CCF的CSP认证