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

Java Calendar先设时间再改时区 重复调用日期减一天问题

问题:重复调用时间格式化函数时日期递减一天

我电脑默认时区是UTC,写了个把时间格式化为GMT+8时区0点的函数,之前一直正常,最近发现重复调用时日期会少一天。问题出在函数执行顺序——先设时间再设时区就会出问题,先设时区再设时间就正常,想知道原因。

测试代码

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class Test {
    private static final String DEFAULT_TIME_ZONE = "GMT";
    private static final String GMT_8_TIME_ZONE = "GMT+8";

    /**
     * 先设置时间,再设置时区
     */
    public static long formatTimeToGmt8Zero(Long time) {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone(DEFAULT_TIME_ZONE));
        calendar.setTime(new Date(time));
        calendar.setTimeZone(TimeZone.getTimeZone(GMT_8_TIME_ZONE));
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar.getTimeInMillis();
    }

    /**
     * 先设置时区,再设置时间
     */
    public static long formatTimeToGmt8Zero2(Long time) {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone(DEFAULT_TIME_ZONE));
        calendar.setTimeZone(TimeZone.getTimeZone(GMT_8_TIME_ZONE));
        calendar.setTime(new Date(time));
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar.getTimeInMillis();
    }

    public static void main(String[] args) {
        Long beginTime = 1695957557093L;//Fri Sep 29 2023 03:19:17 GMT+0000
        long time1 = formatTimeToGmt8Zero(beginTime);
        long time2 = formatTimeToGmt8Zero(time1);
        System.out.println("formatTimeToGmt8Zero beginTime:" + beginTime + ",time1:" + time1 + ",time2:" + time2);
        time1 = formatTimeToGmt8Zero2(beginTime);
        time2 = formatTimeToGmt8Zero2(time1);
        System.out.println("formatTimeToGmt8Zero2 beginTime:" + beginTime + ",time1:" + time1 + ",time2:" + time2);
    }
}

运行结果

formatTimeToGmt8Zero beginTime:1695957557093,time1:1695916800000,time2:1695830400000
formatTimeToGmt8Zero2 beginTime:1695957557093,time1:1695916800000,time2:1695916800000

问题原因解析

先设时间再改时区的错误逻辑

  1. 初始解析:创建GMT时区的Calendar实例,调用setTime传入UTC时间戳1695957557093(对应UTC的2023-09-29 03:19:17),此时Calendar内部的年月日时分秒字段是基于GMT时区解析的——年2023、月8(Calendar月份从0开始)、日29、时3等。
  2. 时区切换的坑:调用setTimeZone切换到GMT+8时,Calendar不会重新用新时区解析时间戳,而是直接保留原有的字段值,用新时区去解释这些字段。原字段的“日29、时3”用GMT+8解释,对应的UTC时间是2023-09-28 19:00:00(因为GMT+8的29日3点等于UTC的28日19点)。
  3. 重置时间到0点:把时分秒毫秒设为0后,Calendar字段变成日29、时0(GMT+8),对应UTC时间2023-09-28 16:00:00,即时间戳1695916800000。
  4. 重复调用的递减:第二次调用时,传入的时间戳1695916800000对应UTC的2023-09-28 16:00:00,第一步解析为GMT时区的字段:日28、时16。切换到GMT+8后,这些字段解释为GMT+8的28日16点(对应UTC的28日8点),再设为0点后,对应UTC的27日16点,时间戳变成1695830400000——比第一次少了一天。

先设时区再设时间的正确逻辑

  1. 先切换时区:创建GMT时区的Calendar实例后,先切换到GMT+8时区。
  2. 正确解析时间:调用setTime传入时间戳时,Calendar会直接用当前的GMT+8时区解析时间戳。比如1695957557093对应GMT+8的2023-09-29 11:19:17,字段为日29、时11。
  3. 重置到0点:设为0点后,得到GMT+8的29日0点,对应UTC的28日16点(时间戳1695916800000)。
  4. 重复调用无变化:第二次调用时,传入的时间戳1695916800000解析为GMT+8的29日0点,设为0点后还是同一个时间,所以不会递减。

优化建议:使用Java 8+时间API替代Calendar

Calendar是老旧API,设计存在很多反直觉的问题,推荐使用java.time包下的线程安全API,逻辑更清晰:

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;

public class Test {
    private static final ZoneId GMT_8 = ZoneId.of("GMT+8");

    public static long formatTimeToGmt8Zero(long time) {
        return Instant.ofEpochMilli(time)
                .atZone(GMT_8)
                .truncatedTo(ChronoUnit.DAYS)
                .toInstant()
                .toEpochMilli();
    }

    public static void main(String[] args) {
        long beginTime = 1695957557093L;
        long time1 = formatTimeToGmt8Zero(beginTime);
        long time2 = formatTimeToGmt8Zero(time1);
        System.out.println("formatTimeToGmt8Zero beginTime:" + beginTime + ",time1:" + time1 + ",time2:" + time2);
        // 输出:formatTimeToGmt8Zero beginTime:1695957557093,time1:1695916800000,time2:1695916800000
    }
}

内容的提问来源于stack exchange,提问作者peter zhang

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 15:48:10