2017-08-09 38 views
5

我想是来自前端的值这样的映射到ZoneId类信息:Java没有所有IANA时区

Optional.ofNullable(timeZone).map(ZoneId::of).orElse(null) 

对于大多数时区,它工作正常,然而,对于一些价值的Java抛出异常:

java.time.zone.ZoneRulesException: Unknown time-zone ID: America/Punta_Arenas 

然而,根据IANA的有效时区: https://www.iana.org/time-zones

区美洲/ Punta_Arenas -4:43:40 - LMT 1890年

我在想这样的时区(只是硬编码值)使用偏移,但我想应该有更方便的方法解决问题。有没有一种Java可以处理的方法?

不支持的

其他时区:

  • 美国/ Punta_Arenas
  • 亚洲/阿特劳
  • 亚洲/法马古斯塔
  • 亚洲/仰光
  • EST
  • 欧洲/萨拉托夫
  • HST
  • MST
  • ROC

我的Java版本: “1.8.0_121的” Java(TM)SE运行时环境(建1.8.0_121-B13)的HotSpot的Java(TM)64位服务器VM(建25.121- b13,混合模式)

+2

您准确使用哪个版本的Java; Java 8的最新更新?时区信息经常使用新的Java更新进行更新,因此您可能正在使用特定的Java版本,其中您所引用的版本未定义。 – Jesper

+0

我尝试了Java 8更新144(目前是最新版本),它按预期工作。 – Jesper

+0

Java版本“1.8.0_121” Java™SE运行时环境(build 1.8.0_121-b13) Java HotSpot™64位服务器虚拟机(版本25.121-b13,混合模式) – dvelopp

回答

7

我已经测试了Java 1.8.0_121,并且有些区域确实缺失。

最明显的方法来解决它是更新Java的版本 - 在Java中1.8.0_131上面的所有区域都可以 - 除了3个字母的名字(ESTHST等),低于更多。

但我知道生产环境中的更新并不像我们想的那样简单(也不快)。在这种情况下,您可以使用TZUpdater tool,它可以在不更改Java版本的情况下更新JDK的时区数据。


唯一的细节是,ZoneId不与3字母缩写(ESTHST等)工作。那是因为这些名字是ambiguous and not standard

但是,如果您想使用它们,您可以使用自定义ID的地图。 ZoneId配有内置地图:

ZoneId.of("EST", ZoneId.SHORT_IDS); 

的问题是,choices used in the SHORT_IDS map是 - 就像任何其他的选择 - 任意的,甚至是有争议的。如果你想用不同的区域对每个缩写,刚刚创建自己的地图:

Map<String, String> map = new HashMap<>(); 
map.put("EST", "America/New_York"); 
... put how many names you want 
System.out.println(ZoneId.of("EST", map)); // creates America/New_York 

3个字母的名字唯一的例外是,当然,GMTUTC,但在这种情况下,它最好只使用ZoneOffset.UTC常数。


如果您不能更新您的Java版本,也不运行TZUpdater工具,还有另一种(要困难得多)的替代方案。

您可以扩展java.time.zone.ZoneRulesProvider类并创建一个可以创建缺失ID的提供程序。类似的东西:

public class MissingZonesProvider extends ZoneRulesProvider { 

    private Set<String> missingIds = new HashSet<>(); 

    public MissingZonesProvider() { 
     missingIds.add("America/Punta_Arenas"); 
     missingIds.add("Europe/Saratov"); 
     // add all others 
    } 

    @Override 
    protected Set<String> provideZoneIds() { 
     return this.missingIds; 
    } 

    @Override 
    protected ZoneRules provideRules(String zoneId, boolean forCaching) { 
     ZoneRules rules = null; 
     if ("America/Punta_Arenas".equals(zoneId)) { 
      rules = // create rules for America/Punta_Arenas 
     } 
     if ("Europe/Saratov".equals(zoneId)) { 
      rules = // create rules for Europe/Saratov 
     } 
     // and so on 

     return rules; 
    } 

    // returns a map with the ZoneRules, check javadoc for more details 
    @Override 
    protected NavigableMap<String, ZoneRules> provideVersions(String zoneId) { 
     TreeMap<String, ZoneRules> map = new TreeMap<>(); 
     ZoneRules rules = provideRules(zoneId, false); 
     if (rules != null) { 
      map.put(zoneId, rules); 
     } 
     return map; 
    } 
} 

创建ZoneRules是最复杂的部分。

一种方法是获取最新的IANA files并阅读它们。你可以看看JDK source code来看看它是如何创建ZoneRules(尽管我不确定JDK内部的文件是否与IANA文件的格式完全相同)。

无论如何,解释了如何阅读IANA的文件。然后,您可以查看ZoneRules javadoc以了解如何将IANA信息映射到Java类。在this answer我创建了一个非常简单的ZoneRules只有2个转换规则,所以你可以得到一个基本的想法,如何做到这一点。

然后,你需要注册商:

ZoneRulesProvider.registerProvider(new MissingZonesProvider()); 

而现在的新区域将提供:

ZoneId.of("America/Punta_Arenas"); 
ZoneId.of("Europe/Saratov"); 
... and any other you added in the MissingZonesProvider class 

还有其他的方法使用提供者(而不是注册),check the javadoc更多细节。在同样的javadoc中,还有更多关于如何正确实现区域规则提供者的细节(我上面的版本非常简单,可能缺少一些细节,比如provideVersions的实现 - 它应该使用提供者的版本作为关键字,而不是我正在做的区域ID等)。

当您更新Java版本时,必须立即丢弃此提供程序(因为您不能让2个提供程序创建具有相同标识的区域:如果新提供程序创建了一个已存在的标识,则会抛出一个当你尝试注册时是例外)。

相关问题