Java没有所有IANA时区
我想是来自前端的值这样的映射到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,混合模式)
我已经测试了Java 1.8.0_121,并且有些区域确实缺失。
最明显的方法来解决它是更新Java的版本 - 在Java中1.8.0_131上面的所有区域都可以 - 除了3个字母的名字(EST
,HST
等),低于更多。
但我知道生产环境中的更新并不像我们想的那样简单(也不快)。在这种情况下,您可以使用TZUpdater tool,它可以在不更改Java版本的情况下更新JDK的时区数据。
唯一的细节是,ZoneId
不与3字母缩写(EST
,HST
等)工作。那是因为这些名字是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个字母的名字唯一的例外是,当然,GMT和UTC,但在这种情况下,它最好只使用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个提供程序创建具有相同标识的区域:如果新提供程序创建了一个已存在的标识,则会抛出一个当你尝试注册时是例外)。
您准确使用哪个版本的Java; Java 8的最新更新?时区信息经常使用新的Java更新进行更新,因此您可能正在使用特定的Java版本,其中您所引用的版本未定义。 – Jesper
我尝试了Java 8更新144(目前是最新版本),它按预期工作。 – Jesper
Java版本“1.8.0_121” Java™SE运行时环境(build 1.8.0_121-b13) Java HotSpot™64位服务器虚拟机(版本25.121-b13,混合模式) – dvelopp