日期校验 / 时间校验正则表达式解析
0 结论
以下为常用的日期格式校验表达式
0.1 日期格式校验
以下日期校验可满足“四年一闰,百年不闰,四百年再闰”
yyyyMMdd
^(?:(?!0000)[0-9]{4}(?:(?:0[13578]|1[02])(?:0[1-9]|[12][0-9]|3[01])|(?:0[469]|11)(?:0[1-9]|[12][0-9]|30)|02(?:0[1-9]|1[0-9]|2[0-8]))|(?:(((\d{2})(0[48]|[2468][048]|[13579][26])|(([02468][048])|([13579][26]))00))0229))$
yyyy-MM-dd
^(?:(?!0000)[0-9]{4}\-(?:(?:0[13578]|1[02])(?:\-0[1-9]|\-[12][0-9]|\-3[01])|(?:0[469]|11)(?:\-0[1-9]|\-[12][0-9]|\-30)|02(?:\-0[1-9]|\-1[0-9]|\-2[0-8]))|(?:(((\d{2})(0[48]|[2468][048]|[13579][26])|(([02468][048])|([13579][26]))00))\-02\-29))$
0.2 时间格式校验
hhmmss
12 小时制,范围为 000000 - 115959
^(?!120000)(?:0[0-9]|1[12])(?:[0-5][0-9]){2}$
24 小时制,范围为 000000 - 235959
^(?!240000)(?:[01][0-9]|2[1-4])(?:[0-5][0-9]){2}$
hh:mm:ss
12 小时制,范围为 00:00:00 - 11:59:59
^(?!12\:00\:00)(?:0[0-9]|1[12])(?:\:[0-5][0-9]){2}$
24 小时制,范围为 00:00:00 - 23:59:59
^(?!24\:00\:00)(?:[01][0-9]|2[1-4])(?:\:[0-5][0-9]){2}$
0.3 使用方式
String uncheckedStr = "2023-12-12";
final String REGEX_PATTERN = "^(?:(?!0000)[0-9]{4}\-(?:(?:0[13578]|1[02])(?:\-0[1-9]|\-[12][0-9]|\-3[01])|(?:0[469]|11)(?:\-0[1-9]|\-[12][0-9]|\-30)|02(?:\-0[1-9]|\-1[0-9]|\-2[0-8]))|(?:(((\d{2})(0[48]|[2468][048]|[13579][26])|(([02468][048])|([13579][26]))00))\-02\-29))$";
if (!Pattern.matches(REGEX_PATTERN, uncheckedStr)) {
throw new BusinessException(ErrorCodeEnum.INVALID_TIME_PATTERN);
}
1 日期校验正则表达式解析
以 yyyyMMdd
为例解析日期校验正则表达式组装过程
- 匹配前四位年份,匹配范围为
0001-9999
,使用(?!)
排除0000
,获得yyyy
部分的校验正则表达式
(?!0000)[0-9]{4}
- 由于月份与日期关联存在多种可能,因此采用模式匹配
1. 31 天的月份 01 03 05 07 08 10 12
(?:0[13578]|1[02]) 匹配月份
(?:0[1-9]|[12][0-9]|3[01]) 匹配日期 01 - 31
2. 30 天的月份 04 06 09 11
(?:0[469]|11) 匹配月份
(?:0[1-9]|[12][0-9]|30) 匹配日期 01 - 30
3. 02 月存在闰月的问题,且条件较为复杂,因此先匹配 01 - 28 的日期范围
02 匹配月份
(?:0[1-9]|1[0-9]|2[0-8]) 匹配日期 01 - 28
- 使用
|
组装匹配模式,多个模式使用(?:MODELA|MODELB|MODELC)
的方式进行组合,此处组合匹配模式后获得mmdd
部分的校验正则表达式
匹配 MMdd 的表达式
(?:(?:0[13578]|1[02])(?:0[1-9]|[12][0-9]|3[01])|(?:0[469]|11)(?:0[1-9]|[12][0-9]|30)|02(?:0[1-9]|1[0-9]|2[0-8]))
|-- 31 天的月份日期 --|-- 30 天的月份日期 --|-- 28 天的月份日期 --|
- 拼接第 1 步和第 3 步的匹配模式以组装基本的
yyyyMMdd
模式,该模式下不考虑闰年,因此将第 2 步的二月的日期匹配范围改到了29
,该模式无法辨认1900-02-29
的非闰年情形
不考虑闰年的简易模式(二月的日期匹配范围改到了29,以下模式能正常使用)
^(?!0000)[0-9]{4}(?:(?:0[13578]|1[02])(?:0[1-9]|[12][0-9]|3[01])|(?:0[469]|11)(?:0[1-9]|[12][0-9]|30)|02(?:0[1-9]|1[0-9]|2[0-9]))$
- 进一步处理闰年年份
1. 非百年的年份处理较为简单,枚举 4 的倍数且不为 100 的倍数的年份
((\d{2})(0[48]|[2468][048]|[13579][26])
2. 百年年份保留 400 的倍数
(([02468][048])|([13579][26]))00)
- 拼接模式以匹配闰年日期
yyyy0229
(((\d{2})(0[48]|[2468][048]|[13579][26])|(([02468][048])|([13579][26]))00))0229
|-- 非百年 --|-- 百年(保留400年) --|
- 结合第 3 步和第 6 步的结果组装最终的匹配模式,该模式可以满足
四年一闰,百年不闰,四百年再闰
的需求
^(?:(?!0000)[0-9]{4}(?:(?:0[13578]|1[02])(?:0[1-9]|[12][0-9]|3[01])|(?:0[469]|11)(?:0[1-9]|[12][0-9]|30)|02(?:0[1-9]|1[0-9]|2[0-8]))|(?:(((\d{2})(0[48]|[2468][048]|[13579][26])|(([02468][048])|([13579][26]))00))0229))$