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 根据给定的偏移量,获取相应的地区名
- public static List<String> getRegionNamesForOffset(ZoneOffset offset) {
- LocalDateTime now = LocalDateTime.now();
- return ZoneId.getAvailableZoneIds().stream()
- .map(ZoneId::of)
- .filter(zoneId -> now.atZone(zoneId).getOffset().equals(offset))
- .map(ZoneId::toString)
- .sorted()
- .collect(Collectors.toList());
- }
在本例中,ZoneId.getAvailableZoneIds 方法返回一个由字符串构成的 List,每个字符串通过 ZoneId.of 方法映射到相应的 ZoneId。利用 LocalDateTime 类定义的 atZone 方法确定 ZoneId 对应的 ZonedDateTime 之后,就能得到所有 ZoneId 的 ZoneOffset,并筛掉其中不匹配的 ZoneOffset。接下来,程序将结果映射到字符串、对字符串排序并将它们收集到 List 中。
反过来,如何获取 ZoneOffset 呢?一种方案是利用给定的 ZoneId,如例 8-36 所示。
例 8-36 根据给定的时区,获取相应的偏移量
- public static List<String> getRegionNamesForZoneId(ZoneId zoneId) {
- LocalDateTime now = LocalDateTime.now();
- ZonedDateTime zdt = now.atZone(zoneId);
- ZoneOffset offset = zdt.getOffset();
- return getRegionNamesForOffset(offset);
- }
上述代码适用于任何给定的 ZoneId。
例 8-37 显示了如何获取与用户当前位置对应的地区名列表。
例 8-37 获取当前的地区名
- @Test
- public void getRegionNamesForSystemDefault() throws Exception {
- ZonedDateTime now = ZonedDateTime.now();
- ZoneId zoneId = now.getZone();
- List<String> names = getRegionNamesForZoneId(zoneId);
assertTrue(names.contains(zoneId.getId()));}
如果希望通过 GMT 偏移量(以小时和分为单位)获取地区名,也可以使用 ZoneOffset 类定义的 ofHoursMinutes 方法,如例 8-38 所示。
例 8-38 根据给定的偏移量(以小时和分为单位),获取地区名
- public static List<String> getRegionNamesForOffset(int hours, int minutes) {
- ZoneOffset offset = ZoneOffset.ofHoursMinutes(hours, minutes);
- return getRegionNamesForOffset(offset);
- }
如例 8-39 所示,我们编写几个测试,验证在给定偏移量的情况下,能否成功获取相应的地区名。
例 8-39 根据给定的偏移量,测试能否成功获取地区名
- @Test
- public void getRegionNamesForGMT() throws Exception {
- List<String> names = getRegionNamesForOffset(0, 0);
- assertTrue(names.contains("GMT"));
- assertTrue(names.contains("Etc/GMT"));
- assertTrue(names.contains("Etc/UTC"));
- assertTrue(names.contains("UTC"));
- assertTrue(names.contains("Etc/Zulu"));
- }
@Test
public void getRegionNamesForNepal() throws Exception {
List<String> names = getRegionNamesForOffset(5, 45);
assertTrue(names.contains("Asia/Kathmandu"));assertTrue(names.contains("Asia/Katmandu"));}
@Test
public void getRegionNamesForChicago() throws Exception {
ZoneId chicago = ZoneId.of("America/Chicago");
List<String> names = RegionIdsByOffset.getRegionNamesForZoneId(chicago);
assertTrue(names.contains("America/Chicago"));assertTrue(names.contains("US/Central"));assertTrue(names.contains("Canada/Central"));assertTrue(names.contains("Etc/GMT+5") || names.contains("Etc/GMT+6"));}
有关时区列表的详细信息,请参见维基百科。
