正则匹配日期时间

由于比较好奇如何用正则完美匹配日期时间,于是发现了一位不知名的大神贡献的一套正则表达式

看起来相当复杂,分析了好长时间,下面贴出来给大家看一下

((\d{2}(([02468][048])|([13579][26]))[-/\s]?((((0?[13578])|(1[02]))[-/\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[-/\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[-/\s]?((0?[1-9])|([1-2][0-9])))))|(\d{2}(([02468][1235679])|([13579][01345789]))[-/\s]?((((0?[13578])|(1[02]))[-/\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[-/\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[-/\s]?((0?[1-9])|(1[0-9])|(2[0-8]))))))

但是我经过测试发现,这个表达式是存在一些小问题的,比如: 1980-2-29只能匹配到1980-2-2;而2100-2-29(2100年非闰年)是会发生匹配的,这样就会出现闰年和日期匹配不正确的情况。

phpstorm 插件 Regex Tester

于是,我在这个基础上做了一些修改,先把结果贴出来然后在做一下说明

(((((([2468][048])|([13579][26]))00)|(\d{2}((([2468][048])|([02468][48]))|([13579][26]))))[-/\s]?((((1[02])|(0?[13578]))[-/\s]?(([1-2][0-9])|(3[01])|(0?[1-9])))|(((11)|(0?[469])) [-/\s]?(([1-2][0-9])|(30)|(0?[1-9])))|(0?2[-/\s]?(([1-2][0-9])|(0?[1-9])))))|((((([2468][1235679])|([13579][01345789]))00)|(\d{2}(([02468][1235679])|([13579][01345789]))))[-/\s]?((((1[02])|(0?[13578]))[-/\s]?(([1-2][0-9])|(3[01])|(0?[1-9])))|(((0?[469])|(11)) [-/\s]?(([1-2][0-9])|(30)|(0?[1-9])))|(0?2[-/\s]?((1[0-9])|(2[0-8])|(0?[1-9]))))))

以下是对这个正则的解析:

日期和时间的匹配均是数字,但是年月日时分秒之间的有效范围是有一些区别的,所以导致整个正则表达式非常复杂。

首先分析年份的匹配,年份在有效范围内是最多4位的整数,而且会分为平年和闰年,下面贴出闰年年份的匹配部分

(((([2468][048])|([13579][26]))00)|(\d{2}((([2468][048])|([02468][48]))|([13579][26]))))

有同学说了,你的年份都放在一起进行条件匹配不行吗?

真的不行,因为闰年内的二月是29天,平年是不允许匹配到29这一天的,所以要把日期整体作为条件分支。

这一串匹配闰年的正则要怎么看呢?如何得出的呢?下面分析一下

闰年定义:满足能被400整除,或被4整除且不能被100整除,举个例子:2004满足被4整除且不能被100整除,所以是闰年;2000满足能被400整除,所以是闰年;2100年两个条件均不满足,平年。

这个定义写成编程语言:

(year % 400 === 0 || ( year % 4 === 0 && year % 100 !== 0 ))

判断条件已知了,但是正则表达式内如何匹配4的倍数,400的倍数,非100的倍数?

经过查阅: 4的倍数有以下特征:

(1)十位数为偶数时且个位是4的倍数的(含0);十位[2,4,6,8], 个位[0,4,8]

(2)十位数为奇数且个位数为偶数且个位数字不是4的倍数;十位[1,3,5,7,9],个位[2,6]

(3)十位和个位可被4整除则此数字即可被4整除;十位[0,4,8],个位[0,4,8]

100的整数倍可想而知,大于等于100,且个位与十位均为0。同理400倍就是结合二者,大于等于400,个位十位均为0,将数字除以100后满足4的倍数的特征。

这样,闰年应该如何书写和判断分支就明朗了,如下列出

I. 匹配可被400整除的年份

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

II. 匹配可被4整除但不能被100整除的年份

(\d{2}((([2468][048])|([02468][48]))|([13579][26])))

然后就是匹配月份,月份的匹配相对简单很多,需注意的 即使2月分的29号 和区别大小月份,1 3 5 7 8 10 12 这些是大月,这没什么好讲的

((((1[02])|(0?[13578]))[-/\s]?(([1-2][0-9])|(3[01])|(0?[1-9])))|(((11)|(0?[469]))[-/\s]?(([1-2][0-9])|(30)|(0?[1-9])))|(0?2[-/\s]?(([1-2][0-9])|(0?[1-9]))))

月份这里解决的一个问题就是上面说的月份匹配错误(2-29只能匹配2-2)

改动前:(0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))
改动后:([1-2][0-9])|(3[01])|(0?[1-9])))|(((11)|(0?[469]))

原因是:原作者将最初补零的验证置前,且0为匹配1次或0次,则匹配到[1-9]后不再进行 ‘或’ 后的匹配

后面的就是正常的年份匹配了

(((([2468][1235679])|([13579][01345789]))00)|(\d{2}(([02468][1235679])|([13579][01345789]))))

平年的匹配很简单,不满足闰年条件的年份就是平年。

经过测试,年月日可以正常匹配了,不过实际工作中这种过长的正则还是尽量不要用或者少用,虽然计算机执行起来对于人来说是几乎无感的,但是效率相对来说要低一些,日期判断是否有效有很多函数工具可以满足。

不过这个正则对于分析正则以及学习它的条件匹配还是有好处的。

Thanks

发表评论

电子邮件地址不会被公开。 必填项已用*标注