Java 8+ 如何绕过日期校验生成非法java.util.Date类型实例
构造Java非法Date实例的实现方法
核心认知澄清
首先需要明确:java.util.Date类本身完全没有内置日期合法性校验逻辑,它本质只是一个封装了毫秒级Unix时间戳的薄对象,所有自动进位、日期合法性判断的逻辑,全部来自配套的SimpleDateFormat、Calendar类的默认宽松解析策略。
你之前代码的问题,是SimpleDateFormat内部依赖的Calendar实例默认开启了宽松模式,当解析到"2021年2月29日"这种超出当月实际天数的值时,不会保留非法输入,而是自动把溢出的天数累加到下一个月,最终得到2021年3月1日的时间戳。
可落地的绕过校验方案
方案1:自定义无校验逻辑的Calendar,适配解析流程
重写GregorianCalendar的时间计算方法,直接跳过合法性校验和自动进位逻辑,配合SimpleDateFormat使用即可保留非法输入对应的Date实例,示例代码:
// 自定义跳过校验的Calendar实现 class UnvalidatedCalendar extends GregorianCalendar { @Override protected void computeTime() { // 覆写计算逻辑,完全跳过日期合法性校验、自动进位流程 } } // 解析非法日期字符串 String sDate1 = "29/02/2021"; SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); // 将自定义的无校验Calendar绑定到格式化类 sdf.setCalendar(new UnvalidatedCalendar()); Date date1 = sdf.parse(sDate1);
这种方式生成的Date实例,在使用相同配置的格式化类输出时,会完整保留你输入的"29/02/2021"的日历值,不会自动进位到3月1日。
方案2:直接构造目标毫秒值,绕开上层解析逻辑
因为Date本身没有校验能力,你可以完全不经过SimpleDateFormat的解析流程,直接通过反射或手动计算,把目标非法日历值对应的毫秒值传入Date构造器,示例代码:
Calendar cal = Calendar.getInstance(); cal.clear(); cal.set(Calendar.YEAR, 2021); cal.set(Calendar.MONTH, Calendar.FEBRUARY); cal.set(Calendar.DAY_OF_MONTH, 29); // 通过反射获取Calendar内部未校验的字段值,跳过自动计算逻辑 Field fieldsField = Calendar.class.getDeclaredField("fields"); fieldsField.setAccessible(true); int[] fieldValues = (int[]) fieldsField.get(cal); // 手动将非法的日历字段映射为对应时间戳,完全不触发合法性判断 long customMillis = fieldValues[Calendar.YEAR] * 365L * 24 * 60 * 60 * 1000L + (fieldValues[Calendar.MONTH] + 1) * 30L * 24 * 60 * 60 * 1000L + fieldValues[Calendar.DAY_OF_MONTH] * 24L * 60 * 60 * 1000L; Date illegalDate = new Date(customMillis);
注意事项
- 上述方案生成的非法Date实例,仅用于测试日期校验类逻辑的场景,不要在正常业务流程中使用
- 非法时间戳在做时间差计算、时区转换、跨日历字段运算时,依然会按照真实日历规则触发自动进位,无法永久保持非法状态
内容的提问来源于stack exchange,提问作者Gaj Julije
相关产品推荐
相关产品推荐

