Java Tips | 日付・時間:UTC変換

Java Java
スポンサーリンク

UTC変換のゴールイメージ

「アプリは日本時間で動いているけど、DBには UTC で保存したい」
「ログは全部 UTC に統一したい」
「外部APIは UTC で返してくる」

業務システムで“時間”をちゃんと扱おうとすると、必ず出てくるのが UTC変換 です。

ここでまず押さえてほしいのは次の2つです。

  1. UTC は「世界共通の基準時刻(協定世界時)」であり、“どこの国でもない時間軸”だということ。
  2. Java では、UTC の“瞬間”を表すのに Instant を使う、ということ。

UTC変換とは、ざっくり言うと
「ローカルな日時(日本時間など)⇔ 世界共通の Instant(UTC)」
を行き来することだとイメージしてください。


Java における UTC の“本体”:Instant

Instant は「UTC 上の一点」

Instant は、「2025-03-26T01:00:00Z」のような形で表現される、“UTC 上の瞬間”です。

import java.time.Instant;

public class InstantBasic {

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

        System.out.println("現在のUTC時刻(Instant): " + now);
    }
}
Java

Instant.now() は、「今この瞬間」を UTC で表現したものです。
ここには「日本時間」や「ロサンゼルス時間」といった概念は一切ありません。

重要なのは、「システム内部・DB・ログの“基準”としては Instant を使う」という発想です。
表示や入力のときだけ、「どのタイムゾーンで見るか」をかぶせてあげます。


日本時間(JST)から UTC(Instant)への変換

ZonedDateTime(Asia/Tokyo)→ Instant

まずは「日本時間での日時」を UTC に変換するパターンです。

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

public class TokyoToUtc {

    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 utcInstant = tokyoTime.toInstant();

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

ここでの流れはシンプルです。

  1. 「これは日本時間だ」と分かる形で ZonedDateTime を作る
  2. toInstant() で UTC 上の瞬間(Instant)に変換する

「ローカルな日時にタイムゾーンをかぶせてから Instant にする」
このパターンを手に覚えさせてください。

LocalDateTime から直接 UTC にしてはいけない理由

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

なので、UTC に変換したいときは必ず「これはどのタイムゾーンの時間か」を明示してから Instant にします。

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

public class LocalToUtc {

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

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

        Instant utcInstant = tokyoTime.toInstant();

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

ここで深掘りしたいのは、
「LocalDateTime 単体では“瞬間”を表せない」という感覚です。
“瞬間”にしたいなら、必ずタイムゾーンをかぶせてから Instant に変換します。


UTC(Instant)からローカル時間への変換

Instant → 日本時間(Asia/Tokyo)の日時

今度は逆方向です。
DB やログに保存されている Instant を、日本時間で表示したいケースです。

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

public class UtcToTokyo {

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

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

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

流れはこうです。

  1. Instant(UTC 上の瞬間)を持っている
  2. instant.atZone(ZoneId.of("Asia/Tokyo")) で、日本時間としての年月日時分秒に変換

「保存は Instant、表示は ZonedDateTime」
この役割分担を意識すると、設計がかなりスッキリします。


UTC変換と情報セキュリティ(ログ・監査)

ログは UTC で統一する、という強いパターン

セキュリティインシデント調査では、
複数システム・複数国のログを突き合わせることがよくあります。

このとき、各システムがバラバラのローカル時間でログを書いていると、
「どのイベントが先か後か」「どのシステムのログがどのタイミングか」が非常に分かりにくくなります。

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

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

という方針です。

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

public class SecurityLogUtc {

    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)だが、見る人のタイムゾーンごとに“見え方”を変えられる」ことです。

これにより、

・世界中のログを“同じ時間軸”で比較できる
・ユーザーには自分のローカル時間で分かりやすく見せられる

という、セキュリティとユーザビリティの両立がしやすくなります。


UTC変換ユーティリティとしてまとめる

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

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

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

public class UtcUtils {

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

    public static ZonedDateTime fromUtc(Instant utcInstant, ZoneId zone) {
        return utcInstant.atZone(zone);
    }

    public static Instant nowUtc() {
        return Instant.now();
    }
}
Java

使い方のイメージはこんな感じです。

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

public class UtcUtilsUsage {

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

        Instant utc = UtcUtils.toUtc(localTokyo, ZoneId.of("Asia/Tokyo"));

        ZonedDateTime laTime = UtcUtils.fromUtc(utc, ZoneId.of("America/Los_Angeles"));

        System.out.println("東京ローカル : " + localTokyo);
        System.out.println("UTC(Instant): " + utc);
        System.out.println("LA時間      : " + laTime);
    }
}
Java

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


まとめ:UTC変換で絶対に覚えてほしいこと

UTC変換は、「ローカルな日時」と「世界共通の瞬間(Instant)」を行き来することです。

UTC 上の瞬間は Instant で表現する。
ローカルな日時は ZonedDateTime(LocalDateTime+ZoneId)として扱い、toInstant() で UTC に変換する。
DB・ログ・監査の“真実の時間”は UTC(Instant)で持ち、表示時にユーザーのタイムゾーンへ変換する。
LocalDateTime 単体で UTC を意識せずに扱うと、タイムゾーンのすれ違いが起きやすい。

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

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

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