Java Calendar调用clear()方法触发DST夏令时偏移异常问题咨询
我已知晓Java Calendar类正被其他公认更优的类库所替代,本次我遇到了该类在夏令时结束的重叠小时场景下的异常问题,具体复现逻辑如下:
public void annoying_issue() { Calendar midnightPDT = Calendar.getInstance(TimeZone.getTimeZone("US/Pacific")); midnightPDT.set(Calendar.YEAR, 2021); midnightPDT.set(Calendar.MONTH, 10); midnightPDT.set(Calendar.DAY_OF_MONTH, 7); midnightPDT.set(Calendar.HOUR_OF_DAY, 0); midnightPDT.set(Calendar.MINUTE, 0); midnightPDT.set(Calendar.SECOND, 0); midnightPDT.set(Calendar.MILLISECOND, 0); Calendar oneAMPDT = Calendar.getInstance(TimeZone.getTimeZone("US/Pacific")); oneAMPDT.setTimeInMillis(midnightPDT.getTimeInMillis() + (60*60*1000));// 夏令时重叠时段的第一个1点(PDT) System.out.println(new Date(midnightPDT.getTimeInMillis()));// 输出符合预期:Sun Nov 7 00:00:00 PDT 2021 System.out.println(new Date(oneAMPDT.getTimeInMillis()));// 输出符合预期:Sun Nov 7 01:00:00 PDT 2021 oneAMPDT.clear(Calendar.MINUTE);// 此时MINUTE字段已经是0,理论上不会产生任何变更 // 实际结果不符合预期:时区切换为PST,时间戳增加了3600000毫秒 System.out.println(new Date(oneAMPDT.getTimeInMillis()));// 输出:Sun Nov 7 01:00:00 PST 2021 }
我首先构造了美国太平洋时区2021年11月7日零点的Calendar实例,累加1小时得到PDT时区的凌晨1点实例,此时打印输出符合预期。但当我对该实例执行
clear(Calendar.MINUTE)操作时,此时MINUTE字段本身取值为0,理论上不会产生任何变更,实际却发现实例时区切换为PST,时间戳增加了3600000毫秒,打印输出变为PST时区的11月7日凌晨1点。我调用oneAMPDT.set(Calendar.MINUTE, 0)也会触发相同的异常。请问该行为属于Calendar的预期设计吗?是否有方案可以避免该问题?
问题解答
该行为是否属于预期设计?
是,这是java.util.Calendar的固有设计特性,并非bug。
其底层逻辑是:Calendar内部采用延迟计算机制,且默认对夏令时结束产生的重叠本地时间,采用后出现的时刻作为解析结果:
- 通过
setTimeInMillis初始化oneAMPDT实例时,传入的时间戳对应的时区偏移是固定的PDT(UTC-7),此时输出正常。 - 调用
clear(Calendar.MINUTE)或set(Calendar.MINUTE, 0)时,会触发Calendar的本地时间字段重计算逻辑:它会放弃已有的时间戳和偏移量信息,仅用年、月、日、时、分、秒等本地字段重新解析时间戳。而2021年11月7日1:00在太平洋时区对应两个不同的时间戳:PDT的1:00和1小时后PST的1:00,Calendar默认选择后者,因此出现了时间戳增加1小时、时区切换为PST的现象。
可行的规避方案
- 优先使用Java 8及以上版本内置的
java.time包(JSR-310时间API),这也是官方推荐的Calendar替代方案。ZonedDateTime支持自定义夏令时重叠场景的解析策略,可以明确指定使用早/晚的偏移量,不会出现这类非预期的时间跳转。 - 如果必须兼容旧版本Java无法使用
java.time,尽量避免直接修改Calendar的单个时间字段,所有时间调整都通过add()方法或者直接操作时间戳后调用setTimeInMillis()实现,避开本地字段重计算逻辑。 - 若必须使用
set()/clear()操作字段,可以在修改后主动设置DST_OFFSET字段保留夏令时偏移,不过该方案容错性低,仅作为临时备选。
内容的提问来源于stack exchange,提问作者Jonathan Cameron
相关产品推荐
相关产品推荐

