1. 引言
正则表达式是处理模式匹配的强大工具,但用起来容易踩坑。本文将使用 java.util.regex
包,通过正则表达式判断字符串是否包含有效日期。
⚠️ 如果对正则基础不熟,建议先参考 Java 正则表达式 API 指南
2. 日期格式概述
我们采用国际通用的公历(Gregorian calendar),日期格式严格遵循 YYYY-MM-DD
模式。特别注意闰年规则:
闰年判定规则:年份能被4整除但不能被100整除,或者能被400整除。其他年份均为平年。
✅ 有效日期示例:
2017-12-31
2020-02-29
(闰年)2400-02-29
(闰年)
❌ 无效日期示例:
2017/12/31
(分隔符错误)2018-1-1
(缺少前导零)2018-04-31
(4月没有31日)2100-02-29
(2100不是闰年)
3. 实现解决方案
先定义一个 DateMatcher
接口作为基础:
public interface DateMatcher {
boolean matches(String date);
}
下面逐步构建完整实现:
3.1. 匹配基本格式
先实现最简单的格式校验:
class FormattedDateMatcher implements DateMatcher {
private static Pattern DATE_PATTERN = Pattern.compile(
"^\\d{4}-\\d{2}-\\d{2}$");
@Override
public boolean matches(String date) {
return DATE_PATTERN.matcher(date).matches();
}
}
这个正则要求:
- 四位年份 + 两位月份 + 两位日期
- 用短横线分隔
✅ 匹配示例:2017-12-31
, 0000-00-00
❌ 不匹配:2018-01
, 2020/02/29
3.2. 匹配特定日期范围
增加数值范围约束(限定年份1900-2999):
^((19|2[0-9])[0-9]{2})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$
拆解规则:
- 年份:
19xx
或20xx-29xx
- 月份:
01-12
- 日期:
01-31
✅ 匹配:1900-01-01
, 2999-12-31
❌ 不匹配:1899-12-31
, 2018-05-35
3.3. 匹配闰年2月29日
专门处理闰年2月29日:
^((2000|2400|2800|(19|2[0-9])(0[48]|[2468][048]|[13579][26]))-02-29)$
关键逻辑:
2000|2400|2800
:能被400整除的闰年(19|2[0-9])(0[48]|[2468][048]|[13579][26])
:能被4整除但不能被100整除的年份
✅ 匹配:2020-02-29
, 2400-02-29
❌ 不匹配:2100-02-29
(非闰年)
3.4. 匹配2月其他日期
处理2月1-28日(所有年份通用):
^(((19|2[0-9])[0-9]{2})-02-(0[1-9]|1[0-9]|2[0-8]))$
✅ 匹配:2018-02-01
, 2020-02-25
❌ 不匹配:2000-02-30
(2月没有30日)
3.5. 匹配31天的月份
处理31天的月份(1,3,5,7,8,10,12月):
^(((19|2[0-9])[0-9]{2})-(0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01]))$
✅ 匹配:2018-01-31
, 2021-07-31
❌ 不匹配:2018-01-32
(日期超限)
3.6. 匹配30天的月份
处理30天的月份(4,6,9,11月):
^(((19|2[0-9])[0-9]{2})-(0[469]|11)-(0[1-9]|[12][0-9]|30))$
✅ 匹配:2018-04-30
, 2019-06-30
❌ 不匹配:2018-04-31
(4月只有30日)
3.7. 完整公历日期匹配器
合并所有规则构建最终方案:
class GregorianDateMatcher implements DateMatcher {
private static Pattern DATE_PATTERN = Pattern.compile(
"^((2000|2400|2800|(19|2[0-9])(0[48]|[2468][048]|[13579][26]))-02-29)$"
+ "|^(((19|2[0-9])[0-9]{2})-02-(0[1-9]|1[0-9]|2[0-8]))$"
+ "|^(((19|2[0-9])[0-9]{2})-(0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01]))$"
+ "|^(((19|2[0-9])[0-9]{2})-(0[469]|11)-(0[1-9]|[12][0-9]|30))$");
@Override
public boolean matches(String date) {
return DATE_PATTERN.matcher(date).matches();
}
}
使用 |
分隔四种匹配规则:
- 闰年2月29日
- 所有年份2月1-28日
- 31天的月份
- 30天的月份
💡 这个正则没做性能优化,重点在可读性。实际使用时可考虑精简。
3.8. 性能警告
⚠️ 复杂正则可能严重影响性能!本文主要展示正则的灵活性,**生产环境推荐直接用 LocalDate.parse()
**:
// 简单粗暴的替代方案
try {
LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE);
return true;
} catch (DateTimeParseException e) {
return false;
}
4. 结论
通过逐步构建,我们实现了:
- 严格格式校验(
YYYY-MM-DD
) - 月份日期范围验证
- 闰年特殊处理
完整代码见 GitHub 仓库(Maven项目,可直接运行)。
✅ 总结:正则能搞定日期验证,但实际开发中,用
LocalDate.parse()
更省心。