Java如何获取给定时区缩写的时区ID列表

是否可以找到给定时区缩写的时区ID列表? 例如,对于缩写IST ,时区ID是Asia/JerusalemAsia/KolkataEurope/Dublin

有趣的问题。 由于缩写没有标准化,因此无法获得权威答案,也无法获得这样的答案。 在我的脑海中,我想到了两种方法:

  1. 从JVM中获取它们。
  2. 在网上找到他们。

从JVM获取区域:

  String givenAbbr = "IST"; LocalDateTime summerSouthernHemisphere = LocalDate.of(2018, Month.JANUARY, 31).atStartOfDay(); LocalDateTime summerNorthernHemisphere = LocalDate.of(2018, Month.JULY, 31).atStartOfDay(); DateTimeFormatter dtf = DateTimeFormatter.ofPattern("z"); Set zones = new HashSet<>(); for (String id : ZoneId.getAvailableZoneIds()) { ZoneId zone = ZoneId.of(id); String abbr = summerSouthernHemisphere.atZone(zone).format(dtf); if (abbr.equals(givenAbbr)) { zones.add(zone); } abbr = summerNorthernHemisphere.atZone(zone).format(dtf); if (abbr.equals(givenAbbr)) { zones.add(zone); } } System.out.println(zones); 

这打印:

 [Asia/Calcutta, Eire, Europe/Dublin, Asia/Jerusalem, Asia/Tel_Aviv, Israel, Asia/Kolkata, Asia/Colombo] 

其中一些只是同一时区的名称。 例如,Eire与欧洲/都柏林有相同的规则。 因此,如果需要,可以进行进一步的过滤。 您可以使用oneZoneId.getRules().equals(anotherZoneId.getRules())来确定两个ZoneId对象是否具有相同的区域规则。

对于缩写CST,列表甚至更长并且具有更多同义词:

 [PRC, America/Matamoros, Asia/Taipei, America/Regina, America/El_Salvador, America/North_Dakota/New_Salem, Asia/Harbin, America/Costa_Rica, America/North_Dakota/Center, America/Guatemala, America/Winnipeg, Asia/Chongqing, America/Rankin_Inlet, America/Indiana/Knox, America/Belize, SystemV/CST6CDT, Mexico/General, America/North_Dakota/Beulah, CST6CDT, America/Swift_Current, America/Knox_IN, Asia/Chungking, Asia/Macao, Asia/Shanghai, America/Indiana/Tell_City, America/Menominee, America/Bahia_Banderas, America/Managua, Canada/East-Saskatchewan, Asia/Macau, America/Havana, America/Resolute, US/Central, US/Indiana-Starke, Cuba, America/Monterrey, America/Chicago, America/Merida, America/Mexico_City, Canada/Central, America/Tegucigalpa, America/Rainy_River, Canada/Saskatchewan, SystemV/CST6] 

我的方法的一个限制是一些时区被多个名称所知,因此超过一个三或四个字母的缩写。 我上面的代码只捕获其中一个。

另一个限制是选择像我这样的两个日期永远不会给你过去和未来的所有可能性,甚至可能会错过一些我没有达到正确日期的地方。 我试图选择一个日期,它是北半球的冬天和南半球的夏天,另一个是相反的日期。 这将涵盖目前的大多数情况,但你永远不知道是否有一个时区或三个时间段,过渡不遵循我们所知的夏季和冬季。 如果你想要更好的报道,在雨果的回答中有一些很好的建议。

从互联网上获取它们

另一个答案当然是你的搜索毫无疑问已经提出的答案:这些列表在互联网上是公开的。 例如, 维基百科的时区缩写列表和时区缩写 – timeanddate.com上的全球列表 。 正如所料,提到的两个名单不同意。 例如,后者知道ADT的两种解释,前者只有一种。 后一个列表给出了许多同义词缩写,从而说明了我的观点,即每个区域可以有多个缩写。

@Ole VV的答案已经提供了很好的详细信息,比如3个字母的缩写(如ISTPST )是模糊的而不是标准的 ,你应该更喜欢长IANA时区名称 (总是采用Continent/City格式,像Asia/KolkataEurope/Berlin )。

但是在这个答案中有一个棘手的细节:它将2018年1月和7月作为冬季和夏季的基准日期(因此可以根据标准和夏令时期间检查缩写)。 但是并不能保证所有情况都需要冬季和夏季时间,因为时区规则可以改变 – 只是因为时区今天有DST,它并不意味着它会永远拥有它(相反的情况也是如此) 。

因此,不是选择一些日期/时间并希望所有时区都在它们之间进行DST更改,最好的方法是从ZoneRules对象获取所有更改 – 它包含所有转换日期(偏移量更改的时刻)时区 – 由于DST开始/结束或因为某些政府决定他们的国家现在将在另一个时区)。

它还涵盖了时区在过去使用缩写但随后更改为另一个时区的情况,因为我正在查看所有时区的更改历史记录。

代码非常相似。 唯一的区别是,我没有使用固定日期(2018年1月/ 7月),而是查看区域的所有过渡(如果时区没有过渡 – 这意味着它从未有过DST或任何其他变化 – 我得到当前日期)。 我还创建了一个String Set (您只需要名称,但您也可以存储ZoneId对象):

 String ist = "IST"; Set zones = new HashSet<>(); // formatter to get the abbreviation DateTimeFormatter fmt = DateTimeFormatter.ofPattern("z"); for (String id : ZoneId.getAvailableZoneIds()) { ZoneId zone = ZoneId.of(id); ZoneRules rules = zone.getRules(); List transitions = rules.getTransitions(); if (transitions.isEmpty()) { // no transitions found, just pick any date String abbrev = fmt.format(ZonedDateTime.now(zone)); if (ist.equals(abbrev)) { zones.add(id); } } else { for (ZoneOffsetTransition transition : transitions) { // get the instant that the transition occurred and convert to this zone String abbrev = fmt.format(transition.getInstant().atZone(zone)); if (ist.equals(abbrev)) { zones.add(id); } } } } System.out.println(zones); 

在这种情况下,输出将是相同的:

[Asia / Calcutta,Eire,Europe / Dublin,Asia / Jerusalem,Asia / Tel_Aviv,Israel,Asia / Kolkata,Asia / Colombo]

虽然这段代码看起来更加冗余(因为它遍历了发生DST更改时的所有日期),但更能保证获得所有案例。 如果您查找过去有DST的时区,但在2018年(或任何其他任意日期)没有,则使用此任意日期将无效。 只有通过检查所有转换,您才能确保涵盖所有案例。

一个例子:如果不是IST ,我想查看缩写AEDT (Australian Eastern Daylight Time)。

使用@Ole VV的代码,我会得到:

[澳大利亚/悉尼,澳大利亚/墨尔本,澳大利亚/霍巴特,澳大利亚/维多利亚,澳大利亚/澳大利亚/澳大利亚/堪培拉,澳大利亚/新南威尔士州,澳大利亚/塔斯马尼亚州,澳大利亚/库里]

使用我的代码,我会得到:

[澳大利亚/悉尼,澳大利亚/布里斯class,澳大利亚/墨尔本,澳大利亚/昆士兰,澳大利亚/霍巴特,澳大利亚/维多利亚,澳大利亚/澳大利亚/澳大利亚/堪培拉,澳大利亚/新南威尔士州,澳大利亚/塔斯马尼亚州,澳大利亚/柯里,澳大利亚/林德曼]

注意差异。 其中一个例子是Australia/Brisbane , 直到90年代都有DST ,但现在却没有(因此它也不会在2018年出现 )。 因此,如果你试图在2018年获得AEDT(夏令时),@ Ole VV的代码将不会选择这个时区,因为它在2018年不会有DST。

但我正在检查历史上所有的变化,无论何时发生。 这保证我覆盖所有案件。

PS:如果你想获得在特定日期有效的缩写,那么你可以使用@Ole VV的代码(只需相应地更改日期)。


另一种方式(不是更容易)是下载IANA时区数据库文件,并按照本教程了解如何阅读文件(不是琐碎的,IMO)。 举例来说,都柏林的参赛作品:

 # Zone NAME GMTOFF RULES FORMAT [UNTIL] Zone Europe/Dublin -0:25:00 - LMT 1880 Aug 2 -0:25:21 - DMT 1916 May 21 2:00 # Dublin MT -0:25:21 1:00 IST 1916 Oct 1 2:00s 0:00 GB-Eire %s 1921 Dec 6 # independence 0:00 GB-Eire GMT/IST 1940 Feb 25 2:00 0:00 1:00 IST 1946 Oct 6 2:00 ... etc 

您可以看到IST用于Europe/Dublin 。 嗯,这不是最直接的方式,但每次IANA更新其数据库时,都需要一些时间将更改包含在JDK中(尽管您可以根据需要更新时区数据 )。

因此,如果您想获得最新信息,可以定期查看IANA网站上的更新。

请参阅oracle docs上的ZoneDateTime api的 Java 8文档。

实现此方法的github上的示例maven项目的链接

实施明智,你可以使用下面的代码,

 ZoneId losAngeles = ZoneId.of("America/Los_Angeles"); ZoneId berlin = ZoneId.of("Europe/Berlin"); // 2014-02-20 12:00 LocalDateTime dateTime = LocalDateTime.of(2014, 02, 20, 12, 0); // 2014-02-20 12:00, Europe/Berlin (+01:00) ZonedDateTime berlinDateTime = ZonedDateTime.of(dateTime, berlin); // 2014-02-20 03:00, America/Los_Angeles (-08:00) ZonedDateTime losAngelesDateTime = berlinDateTime.withZoneSameInstant(losAngeles); int offsetInSeconds = losAngelesDateTime.getOffset().getTotalSeconds(); // -28800 // a collection of all available zones Set allZoneIds = ZoneId.getAvailableZoneIds();