2017-08-09 83 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個提供程序創建具有相同標識的區域:如果新提供程序創建了一個已存在的標識,則會拋出一個當你嘗試註冊時是例外)。

相關問題