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

Calendar的getActualMinimum方法返回异常值原因咨询

问题:Calendar设置HOUR_OF_DAY后日期意外回退到前一天

我完全理解你遇到的这个诡异问题——在巴西利亚标准时间(BRT/GMT-3)下,尝试设置当月第一天的0点,结果设置小时后反而跳到了前一年的12月31日23点。咱们来拆解一下原因和解决办法:

首先看你的代码和输出:

你的代码

Calendar cal = Calendar.getInstance();
System.out.println("After instantiation:                  " + cal.getTime());

cal.set(Calendar.DAY_OF_MONTH, cal.getActualMinimum(Calendar.DAY_OF_MONTH));
System.out.println("After configuring the Day of Month:   " + cal.getTime());

cal.set(Calendar.HOUR_OF_DAY, cal.getActualMinimum(Calendar.HOUR_OF_DAY));
System.out.println("After configuring the Hour of day:    " + cal.getTime());

cal.set(Calendar.MINUTE, cal.getActualMinimum(Calendar.MINUTE));
System.out.println("After configuring the Minutes:        " + cal.getTime());

cal.set(Calendar.SECOND, cal.getActualMinimum(Calendar.SECOND));
System.out.println("After configuring the Seconds:        " + cal.getTime());

cal.set(Calendar.MILLISECOND, cal.getActualMinimum(Calendar.MILLISECOND));
System.out.println("After configuring the Millis:         " + cal.getTime());

控制台输出

After instantiation:                  Thu Jan 19 10:40:18 BRT 2023
After configuring the Day of Month:   Sun Jan 01 10:40:18 BRT 2023
After configuring the Hour of day:    Sat Dec 31 23:40:18 BRT 2022
After configuring the Minutes:        Sat Dec 31 23:00:18 BRT 2022
After configuring the Seconds:        Sat Dec 31 23:00:00 BRT 2022
After configuring the Millis:         Sat Dec 31 23:00:00 BRT 2022

问题根源

这个问题是过时的Calendar类的时区处理缺陷,再加上你使用的JDK版本(1.8.0_241)时区数据老旧共同导致的:

  1. Calendar的分步计算陷阱:每次调用getTime()时,Calendar都会根据当前时区规则重新计算时间戳。当你设置DAY_OF_MONTH为1后,得到的是Jan 01 10:40:18 BRT 2023,这个时间对应的UTC是Jan 01 13:40:18 UTC(因为BRT比UTC晚3小时)。
  2. 时区规则的错误判断:你的JDK 8u241的时区数据是tzdata 2019c,而巴西的夏令时规则在近几年有调整。JVM错误地认为2023年1月1日巴西利亚处于夏令时(BRST,GMT-2),而非你设置的BRT(GMT-3)。当你尝试把小时设为0时,Calendar会先将Jan 01 00:40:18按照BRST规则转换成UTC(Jan 01 02:40:18 UTC),再转回BRT时区时,就变成了Dec 31 23:40:18 BRT 2022(UTC时间减3小时)。

解决方案

1. 推荐:迁移到Java 8的java.time API(JSR-310)

这是Java官方推荐的替代Calendar/Date的新API,设计更合理,时区处理更准确,完全避免这类诡异问题:

import java.time.ZoneId;
import java.time.ZonedDateTime;

public class FirstDayOfMonthExample {
    public static void main(String[] args) {
        // 使用巴西利亚时区的标准ID(比直接用BRT更可靠)
        ZoneId brasiliaZone = ZoneId.of("America/Sao_Paulo");
        ZonedDateTime now = ZonedDateTime.now(brasiliaZone);
        
        // 直接调整到当月第一天的午夜
        ZonedDateTime firstDayMidnight = now.withDayOfMonth(1)
                                            .withHour(0)
                                            .withMinute(0)
                                            .withSecond(0)
                                            .withNano(0);
        
        System.out.println("Instantiation time:           " + now);
        System.out.println("First day of month at midnight: " + firstDayMidnight);
    }
}

这个代码会输出正确的当月第一天午夜时间,自动适配当前时区规则。

2. 兼容旧代码:优化Calendar的使用方式

如果你必须保留Calendar,请一次性设置所有字段,避免分步调用getTime()触发多次计算:

Calendar cal = Calendar.getInstance();
// 一次性设置到当月第一天的0点0分0秒
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);

System.out.println("First day of month at midnight: " + cal.getTime());

另外,也可以考虑更新JDK的时区数据(通过安装tzdata更新包),但这只是临时方案,长远来看还是建议迁移到java.time。

内容的提问来源于stack exchange,提问作者Guilherme Barboza

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 16:56:14