Java 没有关于所有 IANA 时区的信息
我正在尝试将来自前端的值映射到 ZoneId
类,如下所示:
Optional.ofNullable(timeZone).map(ZoneId::of).orElse(null)
对于大多数时区,它可以正常工作,但是,对于某些值,Java 会抛出异常:
java.time.zone.ZoneRulesException:未知时区 ID:America/Punta_Arenas
但是,根据 IANA,它是一个有效的时区:https://www.iana.org/time-zones
<块引用>Zone America/Punta_Arenas -4:43:40 - LMT 1890
我正在考虑对此类时区使用偏移量(仅用于硬编码值),但我想应该有更方便的方法来解决这个问题.Java有没有办法处理这个问题?
不支持的其他时区:
- 美国/Punta_Arenas
- 亚洲/阿特劳
- 亚洲/法马古斯塔
- 亚洲/仰光
- 美国东部标准时间
- 欧洲/萨拉托夫
- HST
- MST
- 中华民国
我的 Java 版本:1.8.0_121"Java(TM) SE 运行时环境(构建 1.8.0_121-b13)Java HotSpot(TM) 64 位服务器 VM(构建 25.121-b13,混合模式)
解决方案我用 Java 1.8.0_121 测试过,有些区域确实缺失.
最明显的修复方法是更新 Java 的版本 - 在 Java 1.8.0_131 中,上述所有区域都可用 - 除了 3 个字母的名称(EST
、HST代码>等),更多内容如下.
但我知道,生产环境中的更新并不像我们希望的那样容易(也不快速).在这种情况下,您可以使用 TZUpdater 工具,它可以在不更改 Java 版本的情况下更新 JDK 的时区数据.
<小时>唯一的细节是 ZoneId
不适用于 3 字母缩写(EST
、HST
等).这是因为这些名称是模棱两可而不是标准.p>
不过,如果您想使用它们,您可以使用自定义 ID 的映射.ZoneId
自带地图:
ZoneId.of("EST", ZoneId.SHORT_IDS);
问题是 选项SHORT_IDS
映射 像任何其他选择一样是任意的,甚至是有争议的.如果您想为每个缩写使用不同的区域,只需创建自己的地图:
Mapmap = new HashMap<>();map.put("EST", "America/New_York");...输入你想要多少个名字System.out.println(ZoneId.of("EST", map));//创建 America/New_York
3 字母名称的唯一例外当然是 GMT 和 UTC,但在这种情况下,最好只使用 ZoneOffset.UTC
常量.
如果您无法更新 Java 版本或运行 TZUpdater 工具,还有另一种(更困难的)替代方法.
您可以扩展 java.time.zone.ZoneRulesProvider
类并创建一个可以创建缺失 ID 的提供程序.类似的东西:
公共类 MissingZonesProvider 扩展 ZoneRulesProvider {私有集合<字符串>missingIds = new HashSet<>();公共 MissingZonesProvider() {missingIds.add("America/Punta_Arenas");missingIds.add("欧洲/萨拉托夫");//添加所有其他人}@覆盖受保护的集合<字符串>提供区域标识(){返回 this.missingIds;}@覆盖protected ZoneRules providerRules(String zoneId, boolean forCaching) {ZoneRules 规则 = null;if ("America/Punta_Arenas".equals(zoneId)) {rules =//为 America/Punta_Arenas 创建规则}if ("欧洲/萨拉托夫".equals(zoneId)) {rules =//为欧洲/萨拉托夫创建规则}//等等退货规则;}//返回带有 ZoneRules 的地图,查看 javadoc 了解更多详细信息@覆盖protected NavigableMap<String, ZoneRules>提供版本(字符串 zoneId){TreeMap<String, ZoneRules>map = new TreeMap<>();ZoneRules 规则 = providerRules(zoneId, false);如果(规则!= null){map.put(zoneId, 规则);}返回地图;}}
创建ZoneRules
是最复杂的部分.
一种方法是获取最新的 IANA 文件并阅读它们.你可以看看 JDK 源代码 以查看它如何从中创建 ZoneRules
(尽管我不确定该文件是否JDK 内部的文件格式与 IANA 的文件完全相同).
无论如何,这个链接解释了如何阅读 IANA 的文件.然后你可以看看 ZoneRules
javadoc 了解如何将 IANA 信息映射到 Java 类.在 this answer 中,我创建了一个非常简单的 ZoneRules
,只有 2 个转换规则,因此您可以获得一个基本的想法.
然后你需要注册提供者:
ZoneRulesProvider.registerProvider(new MissingZonesProvider());
现在新区域将可用:
ZoneId.of("America/Punta_Arenas");ZoneId.of("欧洲/萨拉托夫");...以及您在 MissingZonesProvider 类中添加的任何其他内容
还有其他方式使用提供者(而不是注册),检查 javadoc 了解更多详情.在同一个 javadoc 中还有更多关于如何正确实现区域规则提供程序的详细信息(我上面的版本非常简单,可能缺少一些细节,例如 provideVersions
的实现 - 它应该使用提供程序的版本作为密钥,而不是我正在做的区域 ID 等).
当然,一旦您更新 Java 版本,就必须丢弃此提供程序(因为您不能有 2 个提供程序创建具有相同 ID 的区域:如果新提供程序创建的 ID 已经存在,则会引发异常当您尝试注册时).
I'm trying to map the values that come from the Front-End to ZoneId
class like this:
Optional.ofNullable(timeZone).map(ZoneId::of).orElse(null)
For most time zones it works fine, however, for some values Java throws exception:
java.time.zone.ZoneRulesException: Unknown time-zone ID: America/Punta_Arenas
However, it is a valid time-zone according to IANA: https://www.iana.org/time-zones
Zone America/Punta_Arenas -4:43:40 - LMT 1890
I was thinking about using offset for such time-zones (just to hardcode values), but I guess there should be more convenient way to solve the issue. Is there a way Java can handle that?
Other timezones that are not supported:
- America/Punta_Arenas
- Asia/Atyrau
- Asia/Famagusta
- Asia/Yangon
- EST
- Europe/Saratov
- HST
- MST
- ROC
My Java version: "1.8.0_121" Java(TM) SE Runtime Environment (build 1.8.0_121-b13) Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
解决方案I've tested with Java 1.8.0_121 and some zones are really missing.
The most obvious way to fix it is to update Java's version - in Java 1.8.0_131 all the zones above are available - except the 3-letter names (EST
, HST
, etc), more on that below.
But I know that updates in production environments are not as easy (nor fast) as we'd like. In this case, you could use the TZUpdater tool, which can update JDK's timezone data without changing Java's version.
The only detail is that ZoneId
doesn't work with the 3-letter abbreviations (EST
, HST
and so on). That's because those names are ambiguous and not standard.
If you want to use them, though, you can use a map of custom ID's. ZoneId
comes with a built-in map:
ZoneId.of("EST", ZoneId.SHORT_IDS);
The problem is that the choices used in the SHORT_IDS
map are - like any other choice - arbitrary and even controversial. If you want to use different zones for each abbreviation, just create your own 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
The only exceptions for 3-letter names are, of course, GMT and UTC, but in this case it's better to just use the ZoneOffset.UTC
constant.
If you can't update your Java version nor run the TZUpdater tool, there's another (much more difficult) alternative.
You can extend the java.time.zone.ZoneRulesProvider
class and make a provider that can create the missing ID's. Something like that:
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;
}
}
Create the ZoneRules
is most the complicated part.
One way is to get the latest IANA files and read them. You can take a look at JDK source code to see how it creates ZoneRules
from that (although I'm not sure if the file that's inside JDK is in the exact same format as IANA's files).
Anyway, this link explains how to read IANA's files. Then you can take a look at ZoneRules
javadoc to know how to map IANA information to Java classes. In this answer I create a very simple ZoneRules
with just 2 transition rules, so you can get a basic idea of how to do it.
Then you need to register the provider:
ZoneRulesProvider.registerProvider(new MissingZonesProvider());
And now the new zones will be available:
ZoneId.of("America/Punta_Arenas");
ZoneId.of("Europe/Saratov");
... and any other you added in the MissingZonesProvider class
There are other ways to use the provider (instead of registering), check the javadoc for more details. In the same javadoc there are also more details about how to properly implement a zone rules provider (my version above is very simple and probably it's missing some details, like the implementation of provideVersions
- it should use the provider's version as a key, not the zone ID as I'm doing, etc).
Of course this provider must be discarded as soon as you update the Java version (because you can't have 2 providers that create zones with the same ID: if the new provider creates an ID that already exists, it throws an exception when you try to register it).
相关文章