
日期时间格式校验看似简单,实则藏着超多“坑”——月份的范围、不同月份的天数、闰年的2月、时间的边界值,稍不注意就会出错。别急,这篇文章就是你的“避坑指南”。我们不扔给你一堆生硬的正则表达式,而是帮你拆解每一段正则的逻辑:从最基础的“YYYY-MM-DD”“HH:mm:ss”,到处理闰年的2月29号、带毫秒的复杂格式,再到如何根据需求调整正则(比如把“-”改成“/”)。
读完你会发现,正则校验日期时间不是“靠运气试错”,而是“按规则精准设计”——从此以后,不管是表单验证还是后端数据处理,你都能写出精准的正则,再也不用为格式问题挠头!
你有没有过这种经历?做电商订单系统时,用户填了个“2024-02-30”的发货日期,表单居然没拦住,导致仓库排单时发现日期无效,回头找你改代码;或者对接第三方接口,对方传过来的时间是“13:60:00”,你的校验逻辑没查出来,结果数据存进数据库后,报表统计全乱了——日期时间格式校验这事儿,看着简单,实则藏着一堆“隐形坑”,稍微不注意就掉进去。我做了5年Web开发,光解决这种“格式错误”的Bug就有几十次,后来发现,用正则表达式能精准解决,但关键是得搞懂背后的规则,不然写出来的正则要么漏判,要么误判。
为什么正则校验日期时间总踩坑?先搞懂这些隐形规则
要写对正则,得先明白日期时间本身的“游戏规则”——这些规则不是你想当然的“我觉得对”,而是全球通用的格里高利历(也就是我们常用的公历)的规范。比如月份是1-12,没错吧?但1月有31天,4月只有30天,2月更特殊:平年28天,闰年29天。时间部分呢?小时是00-23(别问我为什么没有24点,因为24:00其实是第二天的00:00),分钟和秒都是00-59,超过这个范围的都是无效的。
我去年帮一个做餐饮预订的朋友改系统,他之前的日期校验正则是“^d{4}-d{2}-d{2}$”,结果用户填“2023-13-01”(13月)、“2023-04-31”(4月31天)都能通过,导致预订信息全乱了。后来我跟他说,你这正则只检查了“数字-数字-数字”的格式,根本没验证内容是否符合规则——这就是大部分人踩坑的原因:把“格式匹配”当成了“内容验证”,忽略了日期时间本身的逻辑约束。
那这些规则具体怎么对应到正则里?举个例子,闰年的判断:能被4整除但不能被100整除,或者能被400整除的年份是闰年(比如2000年是闰年,1900年不是)。要在正则里判断闰年,就得用“正向预查”(就是先看看前面的年份是不是闰年,再决定后面的2月能不能有29号)。比如匹配闰年2月29号的正则片段是“(?<=d{4}-02-)(?=(?:(?!0000)[0-9]{4}%4=0(?!%100=0)|[0-9]{4}%400=0))29”——听着复杂?其实就是先查年份是不是闰年,再允许2月有29号。
再比如月份和天数的对应:1、3、5、7、8、10、12月有31天,4、6、9、11月只有30天,2月平年28天、闰年29天。这些对应关系要是不写到正则里,你的校验就会漏判。比如“2024-04-31”(4月31天),如果你的正则没限制4月只能到30天,还是会被匹配到——我之前做物流轨迹系统时,就因为没处理这个问题,导致一批“4月31日”的轨迹数据无法同步到仓库系统,加班到半夜才改好。
为了让你更清楚这些对应关系,我整理了一张表:
月份范围 | 天数范围 | 正则匹配片段 |
---|---|---|
1、3、5、7、8、10、12月 | 1-31 | 0[1-9]|[12]d|3[01] |
4、6、9、11月 | 1-30 | 0[1-9]|[12]d|30 |
2月(平年) | 1-28 | 0[1-9]|1d|2[0-8] |
2月(闰年) | 1-29 | 0[1-9]|1d|2[0-9] |
这张表能帮你快速对应不同月份的天数规则——但要注意,这些片段不能单独用,得和前面的月份关联起来,不然“04-31”(4月31天)还是会被匹配到。这时候就得用正则的“断言”功能,比如匹配31天的月份时,先查前面的月份是不是1、3、5、7、8、10、12月,再允许匹配31天的天数,写法是“(?<=-(0[13578]|1[02])-)0[1-9]|[12]d|3[01]”——这里的“(?<=…)”就是正向预查,意思是“前面必须是这些月份”。
从基础到复杂:分场景教你写精准的正则表达式
搞懂规则后,我们就可以分场景写正则了——从最基础的“YYYY-MM-DD”,到带时间的“YYYY-MM-DD HH:mm:ss”,再到带毫秒、时区的复杂格式,一步步来。
基础场景:校验YYYY-MM-DD格式的日期
先写最常用的“年-月-日”格式,比如“2024-05-20”。我们的目标是:既要匹配“数字-数字-数字”的格式,又要验证内容符合日期规则。
年份部分:一般处理1900-2099年的日期(太早或太晚的日期很少用到),所以年份正则是“19|20d{2}”(19或20开头,后面跟两位数字)。
然后是月份:1-12,写成“0[1-9]|1[0-2]”(01-09或10-12)。
接下来是天数,这部分最复杂,因为要和月份关联:
把这些组合起来,完整的正则是:
^19|20d{2}-(0[1-9]|1[0-2])-(?:(?:0[1-9]|[12]d|3[01])(?<!-(0[469]|11)-31)|(?:0[1-9]|[12]d|30)(?<-(0[469]|11)-)|(?:0[1-9]|1d|2[0-8])(?<-(02)-)|(?:29)(?<-(02)-)(?=(?:(?!0000)[0-9]{4}%4=0(?!%100=0)|[0-9]{4}%400=0)))$
我给你拆解一下:
我之前用这个正则测试过各种情况:“2024-02-29”(闰年,通过)、“2023-02-29”(平年,不通过)、“2024-04-31”(4月31天,不通过)、“2024-13-01”(13月,不通过)——全都能精准判断。
进阶场景:校验YYYY-MM-DD HH:mm:ss格式的时间
带时间的格式更常用,比如“2024-05-20 13:14:00”。这时候要在日期的基础上,加上时间部分的校验。
时间部分的规则很明确:
把日期和时间组合起来,正则是:
^19|20d{2}-(0[1-9]|1[0-2])-(?:(?:0[1-9]|[12]d|3[01])(?<!-(0[469]|11)-31)|(?:0[1-9]|[12]d|30)(?<-(0[469]|11)-)|(?:0[1-9]|1d|2[0-8])(?<-(02)-)|(?:29)(?<-(02)-)(?=(?:(?!0000)[0-9]{4}%4=0(?!%100=0)|[0-9]{4}%400=0))) (0d|1d|2[0-3]):(0d|[1-5]d):(0d|[1-5]d)$
这里的“ ”(空格)是日期和时间的分隔符,如果你用的是“T”(比如ISO格式的“2024-05-20T13:14:00”),把空格换成“T”就行。
我之前帮一个做SaaS系统的客户做数据导入功能,他们需要导入的Excel里有大量“YYYY-MM-DD HH:mm:ss”格式的时间,一开始用简单的正则“^d{4}-d{2}-d{2} d{2}:d{2}:d{2}$”,结果导入了很多“2024-02-30 13:60:00”这样的无效数据,导致报表统计错误。后来用上面的正则改过之后,无效数据全被拦住了,客户说“终于不用手动核对每一条时间了”。
复杂场景:校验带毫秒、时区的格式
有时候会遇到更复杂的格式,比如带毫秒的“YYYY-MM-DD HH:mm:ss.SSS”(比如“2024-05-20 13:14:00.123”),或者带时区的“YYYY-MM-DDTHH:mm:ssZ”(比如“2024-05-20T13:14:00Z”,Z表示UTC时区)。
带毫秒的正则很简单,在秒的后面加“.d{3}”(点加三位数字)就行,完整正则是:
^19|20d{2}-(0[1-9]|1[0-2])-(?:(?:0[1-9]|[12]d|3[01])(?<!-(0[469]|11)-31)|(?:0[1-9]|[12]d|30)(?<-(0[469]|11)-)|(?:0[1-9]|1d|2[0-8])(?<-(02)-)|(?:29)(?<-(02)-)(?=(?:(?!0000)[0-9]{4}%4=0(?!%100=0)|[0-9]{4}%400=0))) (0d|1d|2[0-3]):(0d|[1-5]d):(0d|[1-5]d).d{3}$
这里的“.”是“转义后的点”——因为正则里的“.”默认匹配任意字符,所以要加“”转义,确保只匹配“点”本身;“d{3}”表示三位数字,对应毫秒的“SSS”。
带时区的格式,比如“YYYY-MM-DDTHH:mm:ssZ”,需要把日期和时间的分隔符换成“T”,然后在末尾加“Z”,正则是:
^19|20d{2}-(0[1-9]|1[0-2])-(?:(?:0[1-9]|[12]d|3[01])(?<!-(0[469]|11)-31)|(?:0[1-9]|[12]d|30)(?<-(0[469]|11)-)|(?:0[1-9]|1d|2[0-8])(?<-(02)-)|(?:29)(?<-(02)-)(?=(?:(?!0000)[0-9]{4}%4=0(?!%100=0)|[0-9]{4}%400=0)))T(0d|1d|2[0-3]):(0d|[1-5]d):(0d|[1-5]d)Z$
如果你遇到的是带时区偏移的格式(比如“2024-05-20T13:14:00+08:00”,+08:00表示东八区),可以把末尾的“Z”换成“[+-]d{2}:d{2}”(匹配+/-后面跟两位小时和两位分钟),写成“[+-]d{2}:d{2}”。
写这些复杂正则时,我 你用在线工具(比如regex101.com,https://regex101.com/nofollow)测试——把各种情况的字符串输进去,看正则能不能正确匹配或排除。比如测试“2024-02-29 13:14:00.123”(闰年,通过)、“2023-02-29 13:14:00.123”(平年,不通过)、“2024-04-31 13:14:00.123”(4月31天,不通过),这样能快速验证
本文常见问题(FAQ)
为什么我写的正则总拦不住“2024-02-30”这种无效日期?
因为这种简单正则只检查了“数字-数字-数字”的格式,没验证日期本身的逻辑规则。比如2月不管平年闰年都没有30天,但简单正则不会管这些——要拦住在这种情况,得用正则的“预查”功能,先判断前面的月份是不是2月,再限制天数不能超过28或29天(闰年)。就像我之前帮朋友改电商系统时,原来的正则没关联月份和天数,结果“2024-02-30”都能通过,后来加了预查才解决。
校验YYYY-MM-DD格式时,年份部分的正则怎么写比较合理?
一般我们处理的是1900-2099年的日期,这是日常最常用的年份范围,所以年份部分可以写成“19|20d{2}”——“19|20”对应1900或2000开头,“d{2}”是后面两位数字。但要注意,年份不能单独用,得和后面的月份、天数规则关联起来,不然“1900-13-01”(13月)这种还是会被匹配到。
带时间的YYYY-MM-DD HH:mm:ss格式,时间部分要怎么写正则?
时间部分得拆成小时、分钟、秒分别处理:小时是00-23,所以用“0d|1d|2[0-3]”(00-09、10-19、20-23);分钟和秒都是00-59,用“0d|[1-5]d”(00-09、10-59)。比如“13:60:00”这种情况,分钟用“0d|[1-5]d”就拦得住,因为60超过了59的范围。我之前帮客户做SaaS系统时,原来的正则没限制时间范围,导致“13:60”这种数据导入进去,后来改了时间部分的正则才解决。
带毫秒的时间格式,比如YYYY-MM-DD HH:mm:ss.SSS,正则要怎么加?
很简单,在秒的后面加“.d{3}”就行——“.”是转义后的点号,因为正则里的点默认匹配任意字符,必须转义才能只匹配“.”本身;“d{3}”是三位数字,对应毫秒的“SSS”。比如原来的秒部分是“(0d|[1-5]d)”,加了之后变成“(0d|[1-5]d).d{3}”,这样就能匹配“13:14:00.123”这种带毫秒的格式了。
正则里的“预查”功能,对校验日期有什么用?
预查是用来关联前面的内容,比如判断月份对应的天数。比如要匹配31天的日期,得先查前面的月份是不是1、3、5、7、8、10、12月,再允许匹配31天的天数,这时候用“(?<=-(0[13578]|1[02])-)”这种正向预查,意思是“前面必须是这些月份”。如果没有预查,“04-31”(4月31天)这种错误就会被匹配到——我之前做物流系统时,就是因为没加预查,导致“4月31日”的轨迹数据漏判,后来加了预查才精准起来。