Java Tips | 日付・時間:タイムゾーン変換

Java Java
スポンサーリンク

タイムゾーン変換のゴールイメージ

「サーバは UTC、ユーザーは日本時間」「海外拠点はロサンゼルス時間」「ログは全部 UTC で残したい」
業務システムで“時間”を扱うとき、ほぼ必ず出てくるのが「タイムゾーン変換」です。

ここでまず絶対に押さえてほしいのは、次の二つです。
「世界共通の時間軸(UTC)で“瞬間”を持つ」こと。
「表示するときだけ“どの国の時間か(タイムゾーン)”をかぶせる」こと。

Java では、InstantZoneIdZonedDateTime を使うと、この考え方をそのままコードにできます。


タイムゾーンの基本概念

Instant と ZoneId と ZonedDateTime の役割

ざっくり役割を分けると、こうなります。

Instant は「世界共通の一点(UTC 上の瞬間)」。
ZoneId は「タイムゾーン(Asia/Tokyo, Europe/London など)」。
ZonedDateTime は「あるタイムゾーンで見たときの年月日時分秒」。

同じ瞬間でも、タイムゾーンが違えば“見え方”が変わります。
2025-03-26T10:00:00+09:00(東京)と
2025-03-26T01:00:00+00:00(UTC)は、同じ Instant です。

タイムゾーン変換とは、
「同じ Instant を、別の ZoneId で見直す」ことだとイメージしてください。


日本時間と UTC の相互変換

日本時間(Asia/Tokyo)から UTC へ変換

まずは「日本時間での日時」を UTC に変換する例です。

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

public class TokyoToUtcExample {

    public static void main(String[] args) {
        ZoneId tokyo = ZoneId.of("Asia/Tokyo");
        ZonedDateTime tokyoTime =
                ZonedDateTime.of(2025, 3, 26, 10, 0, 0, 0, tokyo);

        Instant instant = tokyoTime.toInstant();

        ZonedDateTime utcTime = instant.atZone(ZoneId.of("UTC"));

        System.out.println("東京時間 : " + tokyoTime); // 2025-03-26T10:00+09:00[Asia/Tokyo]
        System.out.println("UTC     : " + utcTime);   // 2025-03-26T01:00Z[UTC]
    }
}
Java

ここで重要なのは、変換の流れです。

東京時間(ZonedDateTime)
toInstant() で世界共通の瞬間(Instant)にする
instant.atZone(別の ZoneId) で別タイムゾーンの時刻にする

「一度 Instant に落としてから、別の ZoneId をかぶせる」というパターンを、手に覚えさせてください。

UTC から日本時間(Asia/Tokyo)へ変換

逆方向も同じ考え方です。

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

public class UtcToTokyoExample {

    public static void main(String[] args) {
        Instant instant = Instant.parse("2025-03-26T01:00:00Z");

        ZonedDateTime tokyoTime = instant.atZone(ZoneId.of("Asia/Tokyo"));

        System.out.println("Instant : " + instant);    // 2025-03-26T01:00:00Z
        System.out.println("東京時間: " + tokyoTime); // 2025-03-26T10:00+09:00[Asia/Tokyo]
    }
}
Java

業務システムでは、
「DB には UTC(Instant)で保存」「画面表示はユーザーのタイムゾーンで表示」
という設計がとても相性が良いです。


LocalDateTime をそのまま変換してはいけない理由

LocalDateTime は「タイムゾーンなしの“見た目”」にすぎない

LocalDateTime は「2025-03-26T10:00」のような“見た目”だけで、
「それがどこの国の 10:00 なのか」を知りません。

もし、タイムゾーンを意識せずに LocalDateTime 同士をやり取りすると、
「サーバは UTC のつもり」「クライアントは日本時間のつもり」
といったすれ違いが簡単に起きます。

タイムゾーン変換をするときは、必ず

LocalDateTime + 「これはどのタイムゾーンの時刻か」
ZonedDateTime にする
toInstant() で世界共通の瞬間にする

というステップを踏むようにしてください。

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

public class LocalDateTimeToZoned {

    public static void main(String[] args) {
        LocalDateTime local = LocalDateTime.of(2025, 3, 26, 10, 0);

        ZonedDateTime tokyoTime = local.atZone(ZoneId.of("Asia/Tokyo"));

        System.out.println("LocalDateTime : " + local);      // 2025-03-26T10:00
        System.out.println("東京時間      : " + tokyoTime); // 2025-03-26T10:00+09:00[Asia/Tokyo]
    }
}
Java

ここで深掘りしたいのは、
「LocalDateTime は“どこかの国のカレンダー表示”でしかない」という感覚です。
“瞬間”として扱いたいなら、必ずタイムゾーンをかぶせて ZonedDateTime にする必要があります。


複数拠点間のタイムゾーン変換

東京とロサンゼルスの時刻を相互変換する

例えば「東京で 2025-03-26 10:00 に会議」「ロサンゼルスでは何時か?」というケースです。

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

public class TokyoLaConversion {

    public static void main(String[] args) {
        ZoneId tokyo = ZoneId.of("Asia/Tokyo");
        ZoneId la    = ZoneId.of("America/Los_Angeles");

        ZonedDateTime tokyoTime =
                ZonedDateTime.of(2025, 3, 26, 10, 0, 0, 0, tokyo);

        ZonedDateTime laTime = tokyoTime.withZoneSameInstant(la);

        System.out.println("東京時間 : " + tokyoTime);
        System.out.println("LA時間  : " + laTime);
    }
}
Java

ここで使っている withZoneSameInstant がポイントです。
「同じ Instant(瞬間)のまま、別のタイムゾーンでの“見え方”に変換する」メソッドです。

これを使えば、
サマータイム(夏時間)なども含めて、Java が正しく変換してくれます。


サマータイム(DST)とタイムゾーン変換の落とし穴

「時差は常に 9 時間」ではない世界

日本(Asia/Tokyo)はサマータイムがないので、
UTC との時差は常に +9 時間です。

しかし、アメリカやヨーロッパの多くの地域では、
夏だけ時計を 1 時間進める「サマータイム(DST)」があります。

つまり、「ロサンゼルスは常に UTC-8」ではなく、
時期によって UTC-8 だったり UTC-7 だったりします。

このため、「時差を自分で足し引きする」のは非常に危険です。
必ず ZoneIdZonedDateTime を使い、
withZoneSameInstantInstant 経由の変換に任せるべきです。


セキュリティ・ログ設計とタイムゾーン

ログは UTC で、表示はローカルで

情報セキュリティの観点から見ると、
ログのタイムゾーン設計はとても重要です。

インシデント調査では、
複数システム・複数国のログを突き合わせることがよくあります。
このとき、各システムがバラバラのローカル時間でログを書いていると、
「どのイベントが先か後か」が非常に分かりにくくなります。

そこでよく採用されるのが、

「ログの保存は UTC(Instant ベース)」
「画面やレポートで見るときだけ、ユーザーのタイムゾーンに変換」

という方針です。

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

public class SecurityLogExample {

    public static void main(String[] args) {
        Instant eventTime = Instant.now();

        System.out.println("ログ保存用(UTC): " + eventTime);

        ZonedDateTime tokyoView = eventTime.atZone(ZoneId.of("Asia/Tokyo"));
        System.out.println("東京での表示  : " + tokyoView);

        ZonedDateTime laView = eventTime.atZone(ZoneId.of("America/Los_Angeles"));
        System.out.println("LAでの表示    : " + laView);
    }
}
Java

こうしておけば、
「世界共通の時間軸(Instant)」でログを突き合わせつつ、
ユーザーには自分のタイムゾーンで分かりやすく見せることができます。


タイムゾーン変換ユーティリティとしてまとめる

「Instant を中心に据える」ユーティリティ設計

タイムゾーン変換も、あちこちで同じようなコードを書きがちなので、
ユーティリティにまとめておくと安全で読みやすくなります。

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class TimeZoneUtils {

    public static Instant toInstant(LocalDateTime local, ZoneId zone) {
        return local.atZone(zone).toInstant();
    }

    public static ZonedDateTime toZonedDateTime(Instant instant, ZoneId zone) {
        return instant.atZone(zone);
    }

    public static ZonedDateTime convertZone(ZonedDateTime from, ZoneId toZone) {
        return from.withZoneSameInstant(toZone);
    }
}
Java

呼び出し側は、例えばこう書けます。

LocalDateTime localTokyo = LocalDateTime.of(2025, 3, 26, 10, 0);
Instant instant = TimeZoneUtils.toInstant(localTokyo, ZoneId.of("Asia/Tokyo"));
ZonedDateTime laTime = TimeZoneUtils.toZonedDateTime(instant, ZoneId.of("America/Los_Angeles"));
Java

ここで深掘りしたいのは、
「Instant を“真ん中”に置いて、周りに各タイムゾーンの見え方をぶら下げる」という設計です。
これを徹底すると、タイムゾーン絡みのバグが一気に減ります。


まとめ:タイムゾーン変換で絶対に覚えてほしいこと

タイムゾーン変換は、「同じ瞬間を、別のタイムゾーンで見直す」ことです。

世界共通の時間軸として Instant を使う。
タイムゾーンは ZoneId、タイムゾーン付きの日時は ZonedDateTime で表現する。
変換は「ZonedDateTime → Instant → 別 ZoneId で ZonedDateTime」という流れに統一する。
サマータイムや時差を自分で計算せず、必ず ZoneIdZonedDateTime に任せる。
ログは UTC で保存し、表示時にユーザーのタイムゾーンへ変換する設計が、セキュリティ的にも運用的にも強い。

もしあなたのコードのどこかに、
「+9 時間して日本時間にする」「-8 時間してロサンゼルス時間にする」といった手書きの時差計算があれば、
そこを一度 InstantZonedDateTime ベースに置き換えられないか眺めてみてください。

それが、時間にもセキュリティにも強いエンジニアへの、大きな一歩になります。

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