You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

初始化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日加非闰年就会触发自动调整。

怎么解决这个问题?

最优方案是彻底抛弃老旧的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:17:49