初始化1900年5月31日的Calendar累加年份出现异常结果
问题原因与解决办法
为什么会出现异常输出?
你遇到的这个问题,大概率是以下两个原因之一:
时区与历法变更的影响
很多人会忽略Calendar依赖系统默认时区这一点,而1900-1920年间不少国家(比如俄罗斯)从儒略历切换到格里高利历,切换过程中会直接跳过若干天。比如俄罗斯在1918年2月14日直接跳到了3月1日,如果你的程序默认时区是这类涉及历法变更的时区,当年份累加到切换年份时,Calendar会自动根据时区的历法规则调整日期,导致输出的日/月和预期不符。java.util.Calendar的固有设计缺陷
旧的Calendar类本身就有很多反直觉的设计:- 月份采用0-based索引(虽然你用
Calendar.MAY避开了这个坑,但这是很多人常踩的雷); - 它的字段延迟计算机制,在多字段修改时可能触发意外的日期调整;
- 对边缘日期的处理虽然符合规则,但容易超出用户预期——不过你的场景里5月31日是合法日期,这个因素影响较小,但如果是2月29日加非闰年就会触发自动调整。
- 月份采用0-based索引(虽然你用
怎么解决这个问题?
最优方案是彻底抛弃老旧的java.util.Calendar和SimpleDateFormat,改用Java 8+引入的java.time API(JSR-310标准),这套API设计更合理、线程安全,还能更好地处理时区和历法问题。
用Kotlin改写你的代码如下:
import java.time.LocalDate import java.time.format.DateTimeFormatter fun main(args: Array<String>) { val formatter = DateTimeFormatter.ofPattern("yyyy.dd.MM") var date = LocalDate.of(1900, 5, 31) repeat(20) { println(formatter.format(date)) date = date.plusYears(1) } }
这个版本的优势:
LocalDate专注处理日期,无需关心时分秒和时区(需要时区支持可以用ZonedDateTime);plusYears方法会严格保留月日,除非目标日期不存在(比如2月29日加非闰年会自动调整到2月28日),而你的场景里5月31日在任何年份都存在,输出完全符合预期;- API语义清晰,没有
Calendar那种反直觉的设计坑。
如果因特殊原因必须使用旧的Calendar类,可以强制指定UTC时区来规避历法变更的影响:
import java.text.SimpleDateFormat import java.util.* fun main(args : Array<String>) { val f = SimpleDateFormat("yyyy.dd.MM") val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")) // 指定UTC时区 cal.set(1900, Calendar.MAY, 31) for(i in 1..20) { println(f.format(cal.time)) cal.add(Calendar.YEAR, 1) } }
UTC时区没有历法变更的问题,能保证日期计算的一致性。
内容的提问来源于stack exchange,提问作者Dmitriy Kornilov
相关产品推荐
相关产品推荐

