Java中的日历无法使用“America/Santiago”夏令时时区。

4

我在处理日历对象时遇到了问题,它在美国/圣地亚哥时区的夏令时(DST)中没有显示正确的偏移量:

以下是我的示例代码:

TimeZone fromTimeZone = TimeZone.getTimeZone("America/Santiago");
    // Get a Calendar instance using the default time zone and locale.
    Calendar calendar = Calendar.getInstance();

    // Set the calendar’s time with the given date
    calendar.setTimeZone(fromTimeZone);

    calendar.set(Calendar.YEAR, 2015); // 2015
     calendar.set(Calendar.MONTH, 2); // Mar
     calendar.set(Calendar.DATE, 31); // 31

    calendar.set(Calendar.HOUR_OF_DAY, 7);
    calendar.set(Calendar.MINUTE, 45);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);

    System.out.println("offset: " + fromTimeZone.getOffset(calendar.getTimeInMillis())/(TimeUnit.MINUTES.toMillis(1)*60));

输出结果为:偏移量为-4。在美国/圣地亚哥时区于2015年3月31日,偏移量应该是-3。(参考:http://www.timeanddate.com/worldclock/converted.html?iso=20150331T00&p1=218&p2=232)

为什么这种情况下日历能得到正确的偏移量?我已经测试了其他时区,在夏令时期间它也能正常工作。

谢谢。


我已经使用jdk 1.8.0_45尝试了您的代码,输出为'offset: -3'。 - Fran Montero
访问http://www.oracle.com/technetwork/java/javase/tzdata-versions-138805.html并搜索关键词“America/Santiago”。然后,你就会知道为什么。 - Howard Wang
谢谢大家。我解决了我的问题。 根本原因是:我的系统上的JDK版本为1.7,所以在这种情况下Calendar没有得到正确的时间。 我尝试使用jdk 1.8,然后得到了预期的输出。 - Ranga Ron
1
@RangaRon 不,根本原因是不同的时区数据。当然,使用JDK 8会再次获得不同的数据。要在Java-8中获取版本信息,只需评估:ZoneRulesProvider.getVersions("America/Santiago").lastEntry().getKey(); - Meno Hochschild
@RangaRon 另一个 JDK 更改不是根本原因的原因是,尽管它有时可以解决您的问题:如果 JDK-8 的基础 tz-data 过时,因为智利政府可能在未来更改夏令时规则,那么您将再次遇到同样的问题。分析您的 tz-data,确定正确的版本并应用 TZUPDATER 工具。 - Meno Hochschild
这是什么操作系统? - Lennart Regebro
3个回答

2
智利通常在三月份从夏令时切换到标准时间,因此您的日期通常应该是标准时间。但是自2010年以来,转换时间已经延迟到四月份(除了2011年的五月份),2015年智利宣布将停止更改,并永久保持夏令时。(编辑:智利已经撤回了2015年的决定, 那一年是智利唯一没有回归标准时间的一年。)
因此,我认为问题最有可能是您的zoneinfo数据库非常过时。如果您正在运行一个不是最新版本的JVM副本,或者如果您正在运行一个从操作系统获取其zoneinfo数据的JVM副本(这在某些Linux发行版上发生),并且您没有使操作系统保持最新状态,或者如果您正在运行不再接收更新的操作系统,则可能会发生这种情况。
无论如何,Java的解决方案通常是升级到最新的JVM。

2
只是一个小修正。JVM的时区数据并没有嵌入到操作系统中,可能会与操作系统的时区不同(在Windows平台上肯定如此)。 - Meno Hochschild
没错,如果你没有使用支持的操作系统分发版,可能会发生这种情况。需要更新。 - Lennart Regebro

1
没有关于您所使用的时区数据版本的额外信息,很难确定原因。但是,您最后的评论告诉我,您显然对任何分析都不感兴趣。因此,我更新了这个答案,为那些只想要解决方案而不需要知道为什么解决方案有效的用户提供帮助。 如果用户遇到获取正确时区偏移量的问题,则原因通常是使用过时的数据。由于时区数据内置在底层JDK中(无论是否使用Java-8),您可以采取以下急救措施:
  1. 将您的JVM更新到最新版本(也就是Java 8u45的最新次要版本)。

  2. 如果这并没有帮助,那么请使用TZUpdater-Tool的最新版本来更新您JVM的时区数据 - 使用预构建的数据。

  3. 如果这仍然无法解决问题,则可以从TZUpdater-Tool的2.0版本开始使用该工具根据直接利用原始IANA/TZDB版本的最新版本来更改您的时区数据(感谢Matt Johnson评论中提供的信息)。请记住,在发布托管在IANA上的新TZDB版本和Oracle-Java发布计划之间总会存在时间差。

  4. 如果您不想依赖于Oracle的发布计划或TZUpdater工具,则可以使用一个内置时区存储库并为您提供尽快更新它的机会的外部库(我至少知道有2-3个这样的库,但不会在此给出任何具体建议。但是做适当的研究很容易)。


你可能会感兴趣了解,新的TZUpdater 2.0让你可以直接从IANA源更新,而不必等待Oracle的发布计划。 - Matt Johnson-Pint
@MattJohnson 非常感谢您的信息。这对我来说是新的。那么只有系统管理员需要同意这样的工作;-) - Meno Hochschild

1
其他答案已经正确指出,您需要保持tzdata在您的JVM中最新。

java.time

另外,您正在使用已经过时的日期时间类,应该使用在JSR 310中定义的现代java.time类。

ZoneId z = ZoneId.of( "America/Santiago" ) ;
LocalDate ld = LocalDate.of( 2015 , Month.MARCH , 31 ) ;
LocalTime lt = LocalTime.of( 7 , 45 , 0 , 0 ) ;
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;

将那个时刻调整为协调世界时(UTC)。时间线上的同一点,但是墙上的时钟显示不同。

Instant instant = zdt.toInstant() ;

获取该时区的规则,即ZoneRules对象。

ZoneRules rules = z.getRules() ;

审查规则。获取此时使用的UTC偏移量,由ZoneOffset类表示。

ZoneOffset offset = rules.getOffset( instant ) ;

询问该时区是否使用夏令时(DST)。请注意Java团队在命名此方法时使用的常见拼写错误(“Saving”应该是单数形式)。

boolean isDst = rules.isDaylightSavings( instant ) ;

如果在夏令时中,获取夏令时调整的时间量作为一个Duration对象。 Duration::toString方法以标准的ISO 8601格式报告其值(小时数、分钟数和秒数)。例如,PT1H表示1小时。
if( isDst ) {
    Duration d = getDaylightSavings​( instant ) ;
}

请看

在 IdeOne.com 上运行此代码的链接

zdt = 2015-03-31T07:45-03:00[America/Santiago]
instant = 2015-03-31T10:45:00Z
offset = -03:00
isDst = true
d = PT1H

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接