8.7 根据UTC偏移量查找地区名

问题

给定某个 UTC 偏移量时,用户希望查找 ISO 8601 标准定义的地区名。

方案

根据给定的偏移量,筛选所有可用的时区 ID。

讨论

尽管“东部夏令时”(Eastern Daylight Time)和“印度标准时间”(Indian Standard Time)这样的时区名已广为人知,但它们并非 ISO 官方名称,其缩写“EDT”和“IST”在某些情况下甚至不是唯一的。ISO 8601 标准采用以下两种方式定义时区 ID。

  • 根据地区名(region name),如“America/Chicago”。
  • 根据以小时和分为单位的 UTC 偏移量(UTC offset),如“+05:30”。

那么,如何根据给定的 UTC 偏移量获取相应的地区名呢?对于任意给定的时间,虽然许多地区的 UTC 偏移量相同,但在给定偏移量的情况下,不难计算出相应的地区名列表。

ZoneOffset 类用于获取某个时区相对于格林尼治 /UTC 时间的偏移量。如果给定偏移量,也可以使用它筛选完整的地区名列表,如例 8-35 所示。

例 8-35 根据给定的偏移量,获取相应的地区名

  1. public static List<String> getRegionNamesForOffset(ZoneOffset offset) {
  2. LocalDateTime now = LocalDateTime.now();
  3. return ZoneId.getAvailableZoneIds().stream()
  4. .map(ZoneId::of)
  5. .filter(zoneId -> now.atZone(zoneId).getOffset().equals(offset))
  6. .map(ZoneId::toString)
  7. .sorted()
  8. .collect(Collectors.toList());
  9. }

在本例中,ZoneId.getAvailableZoneIds 方法返回一个由字符串构成的 List,每个字符串通过 ZoneId.of 方法映射到相应的 ZoneId。利用 LocalDateTime 类定义的 atZone 方法确定 ZoneId 对应的 ZonedDateTime 之后,就能得到所有 ZoneIdZoneOffset,并筛掉其中不匹配的 ZoneOffset。接下来,程序将结果映射到字符串、对字符串排序并将它们收集到 List 中。

反过来,如何获取 ZoneOffset 呢?一种方案是利用给定的 ZoneId,如例 8-36 所示。

例 8-36 根据给定的时区,获取相应的偏移量

  1. public static List<String> getRegionNamesForZoneId(ZoneId zoneId) {
  2. LocalDateTime now = LocalDateTime.now();
  3. ZonedDateTime zdt = now.atZone(zoneId);
  4. ZoneOffset offset = zdt.getOffset();
  5.  
  6. return getRegionNamesForOffset(offset);
  7. }

上述代码适用于任何给定的 ZoneId

例 8-37 显示了如何获取与用户当前位置对应的地区名列表。

例 8-37 获取当前的地区名

  1. @Test
  2. public void getRegionNamesForSystemDefault() throws Exception {
  3. ZonedDateTime now = ZonedDateTime.now();
  4. ZoneId zoneId = now.getZone();
  5. List<String> names = getRegionNamesForZoneId(zoneId);

  6. assertTrue(names.contains(zoneId.getId()));
  7. }

如果希望通过 GMT 偏移量(以小时和分为单位)获取地区名,也可以使用 ZoneOffset 类定义的 ofHoursMinutes 方法,如例 8-38 所示。

例 8-38 根据给定的偏移量(以小时和分为单位),获取地区名

  1. public static List<String> getRegionNamesForOffset(int hours, int minutes) {
  2. ZoneOffset offset = ZoneOffset.ofHoursMinutes(hours, minutes);
  3. return getRegionNamesForOffset(offset);
  4. }

如例 8-39 所示,我们编写几个测试,验证在给定偏移量的情况下,能否成功获取相应的地区名。

例 8-39 根据给定的偏移量,测试能否成功获取地区名

  1. @Test
  2. public void getRegionNamesForGMT() throws Exception {
  3. List<String> names = getRegionNamesForOffset(0, 0);
  4. assertTrue(names.contains("GMT"));
  5. assertTrue(names.contains("Etc/GMT"));
  6. assertTrue(names.contains("Etc/UTC"));
  7. assertTrue(names.contains("UTC"));
  8. assertTrue(names.contains("Etc/Zulu"));
  9. }

  10. @Test

  11. public void getRegionNamesForNepal() throws Exception {

  12. List<String> names = getRegionNamesForOffset(5, 45);

  13. assertTrue(names.contains(&#34;Asia/Kathmandu&#34;));
  14. assertTrue(names.contains(&#34;Asia/Katmandu&#34;));
  15. }

  16. @Test

  17. public void getRegionNamesForChicago() throws Exception {

  18. ZoneId chicago = ZoneId.of("America/Chicago");

  19. List<String> names = RegionIdsByOffset.getRegionNamesForZoneId(chicago);

  20. assertTrue(names.contains(&#34;America/Chicago&#34;));
  21. assertTrue(names.contains(&#34;US/Central&#34;));
  22. assertTrue(names.contains(&#34;Canada/Central&#34;));
  23. assertTrue(names.contains(&#34;Etc/GMT+5&#34;) || names.contains(&#34;Etc/GMT+6&#34;));
  24. }

有关时区列表的详细信息,请参见维基百科。