8.6 查找具有非整数小时偏移量的时区

问题

用户希望查找所有具有非整数小时偏移量(non-integral hour offset)的时区。

方案

获取每个时区的时区偏移量,并计算总秒数除以 3600 之后的剩余时间。

讨论

大部分时区的 UTC 偏移量为小时的整数。例如,通常所说的北美东部时区(EST)为 UTC-05:00,而欧洲中部时间(CET)为 UTC+01:00。不过也存在 UTC 偏移量为半小时甚至 45 分钟的时区,如印度标准时间(IST)为 UTC+05:30,而查塔姆标准时间(CHAST)为 UTC+12:45。本范例将讨论利用 java.time 包查找所有具有非整数小时偏移量的时区。

如例 8-33 所示,我们通过 ZoneOffset 类查找每个时区 ID 相对于 UTC 的偏移量,并将其总秒数与 3600 秒(1 小时)进行比较。

例 8-33 查找每个时区 ID 的偏移量(以秒为单位)

  1. public class FunnyOffsets {
  2. public static void main(String[] args) {
  3. Instant instant = Instant.now();
  4. ZonedDateTime current = instant.atZone(ZoneId.systemDefault());
  5. System.out.printf("Current time is %s%n%n", current);
  6.  
  7. System.out.printf("%10s %20s %13s%n", "Offset", "ZoneId", "Time");
  8. ZoneId.getAvailableZoneIds().stream()
  9. .map(ZoneId::of)
  10. .filter(zoneId -> {
  11. ZoneOffset offset = instant.atZone(zoneId).getOffset();
  12. return offset.getTotalSeconds() % (60 * 60) != 0;
  13. })
  14. .sorted(comparingInt(zoneId ->
  15. instant.atZone(zoneId).getOffset().getTotalSeconds()))
  16. .forEach(zoneId -> {
  17. ZonedDateTime zdt = current.withZoneSameInstant(zoneId);
  18. System.out.printf("%10s %25s %10s%n",
  19. zdt.getOffset(), zoneId,
  20. zdt.format(DateTimeFormatter.ofLocalizedTime(
  21. FormatStyle.SHORT)));
  22. });
  23. }
  24. }

❶ 将地区 ID(字符串)映射到时区 ID

❷ 计算偏移量

❸ 仅返回偏移量无法被 3600 整除的时区 ID

ZoneId.getAvailableZoneIds 静态方法返回 Set,表示所有可用的时区 ID;ZoneId.of 方法将生成的字符串流转换为 ZoneId 实例流。

在本例中,筛选器中的 lambda 表达式首先将 atZone 方法应用到 Instant 以创建 ZonedDateTime,然后应用 getOffset 方法。最后,利用 ZoneOffset 类定义的 getTotalSeconds 方法获取时区偏移量(以秒为单位)。根据 Javadoc 的描述,getTotalSeconds 方法是“访问偏移量的主要方式,它返回小时、分、秒字段的总和(以秒为单位),作为一个偏移量添加到给定的时间”。仅当总秒数无法被 3600(60 sec/min * 60 min/hour)整除时,筛选器中的 Predicate 才返回 true

在打印结果前,程序对生成的 ZoneId 实例排序。sorted 方法传入 Comparator 作为参数。本例使用 Comparator 接口定义的静态方法 comparingInt,它生成一个根据给定整数键排序的 Comparator。程序同样采用 getTotalSeconds 方法获取时区偏移量(以秒为单位),ZoneId 实例根据偏移量进行排序。

接下来,针对每个 ZoneId,程序使用 withZoneSameInstant 方法计算默认时区中当前的 ZonedDateTime,以打印结果。打印的字符串将显示偏移量、时区 ID 以及相应时区中经过格式化的本地时间。

程序的执行结果如例 8-34 所示。

例 8-34 具有非整数小时偏移量的时区

  1. Current time is 2016-08-08T23:12:44.264-04:00[America/New_York]
  2. Offset ZoneId Time
  3. -09:30 Pacific/Marquesas 5:42 PM
  4. -04:30 America/Caracas 10:42 PM
  5. -02:30 America/St_Johns 12:42 AM
  6. -02:30 Canada/Newfoundland 12:42 AM
  7. +04:30 Iran 7:42 AM
  8. +04:30 Asia/Tehran 7:42 AM
  9. +04:30 Asia/Kabul 7:42 AM
  10. +05:30 Asia/Kolkata 8:42 AM
  11. +05:30 Asia/Colombo 8:42 AM
  12. +05:30 Asia/Calcutta 8:42 AM
  13. +05:45 Asia/Kathmandu 8:57 AM
  14. +05:45 Asia/Katmandu 8:57 AM
  15. +06:30 Asia/Rangoon 9:42 AM
  16. +06:30 Indian/Cocos 9:42 AM
  17. +08:45 Australia/Eucla 11:57 AM
  18. +09:30 Australia/North 12:42 PM
  19. +09:30 Australia/Yancowinna 12:42 PM
  20. +09:30 Australia/Adelaide 12:42 PM
  21. +09:30 Australia/Broken_Hill 12:42 PM
  22. +09:30 Australia/South 12:42 PM
  23. +09:30 Australia/Darwin 12:42 PM
  24. +10:30 Australia/Lord_Howe 1:42 PM
  25. +10:30 Australia/LHI 1:42 PM
  26. +11:30 Pacific/Norfolk 2:42 PM
  27. +12:45 NZ-CHAT 3:57 PM
  28. +12:45 Pacific/Chatham 3:57 PM

可以看到,将 java.time 包中的多个类结合在一起使用,就能解决复杂而有趣的问题。