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

从ZonedDateTime创建GregorianCalendar时的行为不一致问题

问题:GregorianCalendar.from(ZonedDateTime)与其他创建方式的行为差异

问题背景

在集成两段无法修改的遗留代码时,一段使用GregorianCalendar,另一段使用ZonedDateTime,通过GregorianCalendar.from(ZonedDateTime)创建的实例,与直接构造、时间戳解析等方式创建的GregorianCalendar行为不一致——比如调用set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY)时得到的结果不同,且WEEK_OF_YEAR值也存在差异。

演示代码

import java.util.*;
import java.time.*;

public class Main {
  public static void main(String[] args) {
    final GregorianCalendar calendarPure = new GregorianCalendar(TimeZone.getTimeZone("America/Los_Angeles"));
    printInfo(calendarPure);

    ZoneId zone = ZoneId.of("America/Los_Angeles");
    final ZonedDateTime zdtNow = ZonedDateTime.now(zone);
    
    final GregorianCalendar calendarFromZdt = GregorianCalendar.from(zdtNow);
    printInfo(calendarFromZdt);
    
    final GregorianCalendar calendarFromZdtEpoch = new GregorianCalendar();
    calendarFromZdtEpoch.setTimeInMillis(zdtNow.toInstant().toEpochMilli());

    printInfo(calendarFromZdtEpoch);
  }
  
  private static void printInfo(GregorianCalendar cal) {
    System.out.printf("Now: %tc%n", cal);
    System.out.printf("Last Sunday: %tc%n", getLastSunday(cal));
    System.out.printf("Week of Year: %d%n", cal.get(Calendar.WEEK_OF_YEAR));
    System.out.printf("Time in Millis: %d%n", cal.getTimeInMillis());
    System.out.printf("toString(): %s%n", cal);
    System.out.println();
  }
  
  public static GregorianCalendar getLastSunday(GregorianCalendar referenceDate) {
    final GregorianCalendar lastSundayMidNight = (GregorianCalendar)referenceDate.clone();
    lastSundayMidNight.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
    return lastSundayMidNight;
  }

}

输出结果

Now: Wed Dec 28 16:45:36 PST 2022
Last Sunday: Sun Dec 25 16:45:36 PST 2022
Week of Year: 53
Time in Millis: 1672274736631
toString(): java.util.GregorianCalendar[time=1672274736631,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2022,MONTH=11,WEEK_OF_YEAR=53,WEEK_OF_MONTH=5,DAY_OF_MONTH=28,DAY_OF_YEAR=362,DAY_OF_WEEK=4,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=4,HOUR_OF_DAY=16,MINUTE=45,SECOND=36,MILLISECOND=631,ZONE_OFFSET=-28800000,DST_OFFSET=0]

Now: Wed Dec 28 16:45:36 PST 2022
Last Sunday: Sun Jan 01 16:45:36 PST 2023
Week of Year: 52
Time in Millis: 1672274736668
toString(): java.util.GregorianCalendar[time=1672274736668,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2022,MONTH=11,WEEK_OF_YEAR=52,WEEK_OF_MONTH=5,DAY_OF_MONTH=28,DAY_OF_YEAR=362,DAY_OF_WEEK=4,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=4,HOUR_OF_DAY=16,MINUTE=45,SECOND=36,MILLISECOND=668,ZONE_OFFSET=-28800000,DST_OFFSET=0]

Now: Thu Dec 29 00:45:36 UTC 2022
Last Sunday: Sun Dec 25 00:45:36 UTC 2022
Week of Year: 53
Time in Millis: 1672274736668
toString(): java.util.GregorianCalendar[time=1672274736668,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Etc/UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2022,MONTH=11,WEEK_OF_YEAR=53,WEEK_OF_MONTH=5,DAY_OF_MONTH=29,DAY_OF_YEAR=363,DAY_OF_WEEK=5,DAY_OF_WEEK_IN_MONTH=5,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=45,SECOND=36,MILLISECOND=668,ZONE_OFFSET=0,DST_OFFSET=0]

疑问

通过toString()输出可见,firstDayOfWeek和minimalDaysInFirstWeek字段值存在差异,推测这是行为不一致的原因。想知道:

  1. 这两个字段值的来源是什么?
  2. 是否可以通过设置ZonedDateTime的属性,让GregorianCalendar.from(ZonedDateTime)创建的实例与其他方式创建的行为一致?

解答

1. firstDayOfWeek和minimalDaysInFirstWeek的来源

这两个字段是**区域设置(Locale)**相关的核心属性:

  • firstDayOfWeek:定义一周的起始日,例如美国Locale默认是周日(对应Calendar.SUNDAY,值为1),欧洲多数Locale默认是周一(对应Calendar.MONDAY,值为2)
  • minimalDaysInFirstWeek:定义一年中第一周需要包含的最少天数,例如ISO 8601标准要求第一周至少包含4天(值为4),而美国标准是至少1天(值为1)

不同创建方式的差异根源:

  • 直接new GregorianCalendar(TimeZone):默认使用系统默认Locale的周规则
  • GregorianCalendar.from(ZonedDateTime):内部遵循java.time体系的默认规范(ISO 8601),因此会设置firstDayOfWeek=2、minimalDaysInFirstWeek=4
  • 通过时间戳setTimeInMillis创建的实例:同样继承系统默认Locale的周规则

2. 让from(ZonedDateTime)创建的实例行为一致的方法

无法通过修改ZonedDateTime的属性实现,因为ZonedDateTime本身不携带Locale相关的周规则信息。但可以在创建GregorianCalendar后,手动覆盖这两个字段的值,对齐到目标规则:

方法一:对齐到系统默认Locale的规则

ZoneId zone = ZoneId.of("America/Los_Angeles");
ZonedDateTime zdtNow = ZonedDateTime.now(zone);
GregorianCalendar calendarFromZdt = GregorianCalendar.from(zdtNow);

// 从默认构造的GregorianCalendar中复制周规则
GregorianCalendar template = new GregorianCalendar();
calendarFromZdt.setFirstDayOfWeek(template.getFirstDayOfWeek());
calendarFromZdt.setMinimalDaysInFirstWeek(template.getMinimalDaysInFirstWeek());

方法二:手动设置指定规则(比如美国标准)

GregorianCalendar calendarFromZdt = GregorianCalendar.from(zdtNow);
// 设置一周起始日为周日,第一周至少1天
calendarFromZdt.setFirstDayOfWeek(Calendar.SUNDAY);
calendarFromZdt.setMinimalDaysInFirstWeek(1);

修改后,calendarFromZdt的set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY)行为和calendarPure会保持一致,WEEK_OF_YEAR值也会对齐。


内容的提问来源于stack exchange,提问作者L. Holanda

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 19:25:16