Java Tips | 日付・時間:タイムゾーン一覧

Java Java
スポンサーリンク

タイムゾーン一覧ユーティリティは何のために必要か

グローバルなサービスや、海外拠点を持つ会社のシステムでは、「どのタイムゾーンで日時を扱うか」をユーザーに選ばせる場面がよく出てきます。
ユーザー設定画面の「タイムゾーン選択プルダウン」、レポートの「どのタイムゾーンで表示するか」、バッチ処理の「どの国時間の深夜に動かすか」などです。

ここで毎回、手書きの「Asia/Tokyo」「America/New_York」などをベタ書きしていると、抜け漏れや誤記、将来の変更に弱くなります。
そこで役に立つのが「タイムゾーン一覧」を動的に取得・整形するユーティリティです。


Java のタイムゾーンの基本クラスを押さえる

ZoneId と ZoneOffset の役割

Java では、タイムゾーンを表すクラスとして ZoneIdZoneOffset が用意されています。

ZoneId は「Asia/Tokyo」「Europe/London」のような“地域名ベース”のタイムゾーンを表します。
ZoneOffset は「+09:00」「-05:00」のような“UTC からのオフセット”だけを表します。

通常、ユーザーに選ばせるのは ZoneId です。
なぜなら、サマータイム(夏時間)などのルールを含んでおり、「その地域の正しい時刻」を自動で計算してくれるからです。

簡単な例です。

import java.time.ZoneId;
import java.time.ZonedDateTime;

public class ZoneIdBasic {

    public static void main(String[] args) {
        ZoneId tokyo = ZoneId.of("Asia/Tokyo");
        ZonedDateTime nowInTokyo = ZonedDateTime.now(tokyo);
        System.out.println(nowInTokyo);
    }
}
Java

タイムゾーン一覧を取得する基本ユーティリティ

ZoneId.getAvailableZoneIds を使う

Java は、利用可能なタイムゾーン ID をすべて教えてくれるメソッドを持っています。
それが ZoneId.getAvailableZoneIds() です。

import java.time.ZoneId;
import java.util.Set;

public class TimeZoneList {

    public static Set<String> allZoneIds() {
        return ZoneId.getAvailableZoneIds();
    }
}
Java

使い方の例です。

import java.util.Set;

public class TimeZoneListExample {

    public static void main(String[] args) {
        Set<String> ids = TimeZoneList.allZoneIds();
        ids.stream()
           .sorted()
           .forEach(System.out::println);
    }
}
Java

これで、"Africa/Cairo""America/New_York" など、Java が知っているすべてのタイムゾーン ID を取得できます。
ここから「画面に出す候補」「システムで許可するタイムゾーン」を選んでいくイメージです。


画面向けに「見やすいタイムゾーン一覧」を作る

オフセット付きの表示用文字列を作る

そのまま ID を並べるだけだと、ユーザーには少し不親切です。
「(UTC+09:00) Asia/Tokyo」のように、オフセット付きで表示すると選びやすくなります。

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.zone.ZoneRules;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class TimeZoneDisplay {

    public static class TimeZoneEntry {
        private final String id;
        private final String display;

        public TimeZoneEntry(String id, String display) {
            this.id = id;
            this.display = display;
        }

        public String getId() {
            return id;
        }

        public String getDisplay() {
            return display;
        }
    }

    public static List<TimeZoneEntry> buildDisplayList() {
        Set<String> ids = ZoneId.getAvailableZoneIds();
        ZonedDateTime now = ZonedDateTime.now();

        return ids.stream()
                  .map(ZoneId::of)
                  .map(zoneId -> {
                      ZonedDateTime zdt = now.withZoneSameInstant(zoneId);
                      ZoneRules rules = zoneId.getRules();
                      int seconds = rules.getOffset(now.toInstant()).getTotalSeconds();
                      int hours = seconds / 3600;
                      int minutes = Math.abs((seconds % 3600) / 60);
                      String sign = hours >= 0 ? "+" : "-";
                      String offset = String.format("UTC%s%02d:%02d", sign, Math.abs(hours), minutes);
                      String display = String.format("(%s) %s", offset, zoneId.getId());
                      return new TimeZoneEntry(zoneId.getId(), display);
                  })
                  .sorted((a, b) -> a.getDisplay().compareTo(b.getDisplay()))
                  .collect(Collectors.toList());
    }
}
Java

使い方の例です。

public class TimeZoneDisplayExample {

    public static void main(String[] args) {
        var list = TimeZoneDisplay.buildDisplayList();
        list.stream()
            .limit(20)
            .forEach(e -> System.out.println(e.getDisplay()));
    }
}
Java

ここで深掘りしたいポイントは、
「ZoneId から現在のオフセットを計算し、(UTC±hh:mm) を付けている」ことです。
サマータイムの有無なども含めて、今この瞬間のオフセットを表示できるので、ユーザーにとって直感的になります。


業務でよく使う「絞り込み」と「ホワイトリスト」

全世界ではなく、サポート対象地域だけに絞る

getAvailableZoneIds() は非常に多くの ID を返します。
業務システムでは、「サポート対象の地域だけ」に絞ることが多いです。

例えば、「日本・アメリカ・ヨーロッパ主要都市だけ」といったホワイトリストを用意しておきます。

import java.time.ZoneId;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class TimeZoneWhitelist {

    private static final Set<String> ALLOWED = Set.of(
            "Asia/Tokyo",
            "Asia/Seoul",
            "Asia/Shanghai",
            "Europe/London",
            "Europe/Berlin",
            "America/New_York",
            "America/Los_Angeles"
    );

    public static List<ZoneId> allowedZoneIds() {
        return ALLOWED.stream()
                      .map(ZoneId::of)
                      .collect(Collectors.toList());
    }
}
Java

このように「システムとして許可するタイムゾーン」を明示しておくと、
想定外のタイムゾーンによるバグや、テストの抜け漏れを減らせます。


セキュリティ・運用の観点から見たタイムゾーン一覧

「タイムゾーンの扱い」はそのまま監査・トラブルの原因になる

ログのタイムスタンプ、レポートの日時、契約の有効期間など、
タイムゾーンの扱いを間違えると、「いつ何が起きたか」が分からなくなります。

そのため、次のような方針をユーティリティとセットで決めておくことが重要です。

内部的には UTC で保存し、表示時にユーザーのタイムゾーンへ変換する。
ユーザーが選べるタイムゾーンはホワイトリストで制限し、テスト可能な範囲にする。
タイムゾーン一覧の生成ロジックを一か所に集約し、画面やバッチで同じ定義を使う。

こうしておくと、「あの画面だけ違うタイムゾーンで表示されていた」といった事故を防ぎやすくなります。

タイムゾーン ID を外部入力からそのまま信用しない

API などで「タイムゾーン ID」を外部から受け取る場合、
ZoneId.of(userInput) をそのまま呼ぶと、存在しない ID で例外が飛びます。

必ず「許可された ID の中に含まれているか」をチェックし、
不正な値はエラーとして扱うようにします。
これは、異常系の安定性だけでなく、攻撃的な入力に対する防御にもなります。


まとめ:タイムゾーン一覧ユーティリティで身につけてほしい感覚

タイムゾーン一覧ユーティリティは、「Java が知っているタイムゾーンを取得し、ユーザーに選びやすい形に整える」ための道具です。
しかし、その裏には次のような大事な考え方があります。

ZoneId を使って地域ベースのタイムゾーンを扱う。
getAvailableZoneIds() から一覧を取り、オフセット付きの表示用文字列を作る。
業務としてサポートするタイムゾーンをホワイトリストで明示する。
内部は UTC、表示はユーザータイムゾーン、という方針を徹底する。
タイムゾーン一覧の生成と検証ロジックを一か所に集約し、全画面・全機能で共通化する。

もしあなたのプロジェクトで、画面ごとにバラバラのタイムゾーン候補がベタ書きされているなら、
それを一度「タイムゾーン一覧ユーティリティ」にまとめられないか眺めてみてください。

それだけで、コードの意図がはっきりし、
グローバル対応や監査対応にも耐えられる、日付・時間設計に一歩近づきます。

タイトルとURLをコピーしました