«1. Обзор

Всякий раз, когда мы имеем дело со временем и датами, нам нужна система отсчета. Стандартом для этого является UTC, но мы также видим GMT в некоторых приложениях.

Короче говоря, UTC — это стандарт, а GMT — это часовой пояс.

Вот что говорит нам Википедия относительно того, что использовать:

For most purposes, UTC is considered interchangeable with Greenwich Mean Time (GMT), but GMT is no longer precisely defined by the scientific community.

Другими словами, как только мы составим список со смещением часовых поясов в формате UTC, мы получим его и для GMT.

Сначала мы рассмотрим способ достижения этого в Java 8, а затем посмотрим, как можно получить тот же результат в Java 7.

2. Получение списка зон

Чтобы начнем с того, что нам нужно получить список всех определенных часовых поясов.

Для этой цели в классе ZoneId есть удобный статический метод:

Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();

Затем мы можем использовать Set для создания отсортированного списка часовых поясов с соответствующими смещениями:

public List<String> getTimeZoneList(OffsetBase base) {
 
    LocalDateTime now = LocalDateTime.now();
    return ZoneId.getAvailableZoneIds().stream()
      .map(ZoneId::of)
      .sorted(new ZoneComparator())
      .map(id -> String.format(
        "(%s%s) %s", 
        base, getOffset(now, id), id.getId()))
      .collect(Collectors.toList());
}

Метод выше используется параметр перечисления, который представляет смещение, которое мы хотим увидеть:

public enum OffsetBase {
    GMT, UTC
}

Теперь давайте рассмотрим код более подробно.

После того, как мы получили все доступные идентификаторы зон, нам нужна фактическая ссылка на время, представленная LocalDateTime.now().

После этого мы используем Java Stream API для перебора каждой записи в нашем наборе строковых идентификаторов часовых поясов и преобразования их в список отформатированных часовых поясов с соответствующим смещением.

Для каждой из этих записей мы генерируем экземпляр ZoneId с помощью map(ZoneId::of).

3. Получение смещений

Нам также необходимо найти фактические смещения UTC. Например, в случае центральноевропейского времени смещение будет +01:00.

Чтобы получить смещение UTC для любой заданной зоны, мы можем использовать метод getOffset() класса LocalDateTime.

Также обратите внимание, что Java представляет смещение +00:00 как Z.

Итак, чтобы иметь последовательно выглядящую строку для часовых поясов с нулевым смещением, мы заменим Z на +00:00:

private String getOffset(LocalDateTime dateTime, ZoneId id) {
    return dateTime
      .atZone(id)
      .getOffset()
      .getId()
      .replace("Z", "+00:00");
}

~ ~~ 4. Делаем зоны сопоставимыми

Опционально мы можем отсортировать часовые пояса по смещению.

Для этого мы воспользуемся классом ZoneComparator:

private class ZoneComparator implements Comparator<ZoneId> {

    @Override
    public int compare(ZoneId zoneId1, ZoneId zoneId2) {
        LocalDateTime now = LocalDateTime.now();
        ZoneOffset offset1 = now.atZone(zoneId1).getOffset();
        ZoneOffset offset2 = now.atZone(zoneId2).getOffset();

        return offset1.compareTo(offset2);
    }
}

5. Отображение часовых поясов

Все, что осталось сделать, это соединить вышеуказанные части вместе, вызвав метод getTimeZoneList() для каждого перечисления OffsetBase. value и отображение списков:

public class TimezoneDisplayApp {

    public static void main(String... args) {
        TimezoneDisplay display = new TimezoneDisplay();

        System.out.println("Time zones in UTC:");
        List<String> utc = display.getTimeZoneList(
          TimezoneDisplay.OffsetBase.UTC);
        utc.forEach(System.out::println);

        System.out.println("Time zones in GMT:");
        List<String> gmt = display.getTimeZoneList(
          TimezoneDisplay.OffsetBase.GMT);
        gmt.forEach(System.out::println);
    }
}

Когда мы запустим приведенный выше код, он напечатает часовые пояса для UTC и GMT.

Вот фрагмент того, как будет выглядеть вывод:

Time zones in UTC:
(UTC+14:00) Pacific/Apia
(UTC+14:00) Pacific/Kiritimati
(UTC+14:00) Pacific/Tongatapu
(UTC+14:00) Etc/GMT-14

6. Java 7 и предыдущие версии

Java 8 упрощает эту задачу за счет использования API Stream и Date and Time.

Однако, если у нас есть Java 7 и более ранний проект, мы все равно можем добиться того же результата, полагаясь на класс java.util.TimeZone с его методом getAvailableIDs():

public List<String> getTimeZoneList(OffsetBase base) {
    String[] availableZoneIds = TimeZone.getAvailableIDs();
    List<String> result = new ArrayList<>(availableZoneIds.length);

    for (String zoneId : availableZoneIds) {
        TimeZone curTimeZone = TimeZone.getTimeZone(zoneId);
        String offset = calculateOffset(curTimeZone.getRawOffset());
        result.add(String.format("(%s%s) %s", base, offset, zoneId));
    }
    Collections.sort(result);
    return result;
}

Основное отличие от код Java 8 — это расчет смещения.

Значение rawOffset, которое мы получаем из метода getRawOffset() TimeZone(), выражает смещение часового пояса в миллисекундах.

Поэтому нам нужно преобразовать это в часы и минуты, используя класс TimeUnit:

private String calculateOffset(int rawOffset) {
    if (rawOffset == 0) {
        return "+00:00";
    }
    long hours = TimeUnit.MILLISECONDS.toHours(rawOffset);
    long minutes = TimeUnit.MILLISECONDS.toMinutes(rawOffset);
    minutes = Math.abs(minutes - TimeUnit.HOURS.toMinutes(hours));

    return String.format("%+03d:%02d", hours, Math.abs(minutes));
}

7. Заключение

В этом кратком руководстве мы увидели, как мы можем составить список всего доступного времени. зоны с их смещениями UTC и GMT.

И, как всегда, полный исходный код примеров доступен на GitHub.