概要

タイムゾーン処理の基本は timezone-conversion で整理しましたが、実務では更に踏み込んだ判断が求められます。サマータイム(DST)のある地域と連携するとき、冬と夏でオフセットが変わる影響をどう吸収するか。DB に保存する時刻を UTC に統一する具体的なパターン。ZonedDateTime と OffsetDateTime をどう使い分けるか。この記事では、これらの実務的なテーマを掘り下げ、DB保存→表示のラウンドトリップや、DST 切り替え時のエッジケースを含めたコード例を紹介します。

使いどころ

海外拠点のシステムとデータ連携する際に、DST の影響でログの時刻がずれる問題を解消する

DB にはすべて UTC で保存し、画面表示時にユーザーのタイムゾーンで変換して表示する

外部 API のレスポンスに含まれる ISO 8601 タイムスタンプを JST に変換して帳票に出力する

コード例

DB保存のUTC統一とDST対応のラウンドトリップ
import java.time.LocalDateTime;

public class TimezoneAdvanced {

    private static final ZoneId JST = ZoneId.of("Asia/Tokyo");
    private static final ZoneId UTC = ZoneId.of("UTC");

    public static String toDbValue(ZonedDateTime jstTime) {
        return jstTime.withZoneSameInstant(UTC)
            .format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
    }

    public static String toDisplay(String dbValue) {
        return ZonedDateTime.parse(dbValue)
            .withZoneSameInstant(JST)
            .format(DateTimeFormatter.ofPattern(
                "yyyy/MM/dd HH:mm"));
    }

    public static void main(String[] args) {

        var now = ZonedDateTime.now(JST);
        var saved = toDbValue(now);
        var displayed = toDisplay(saved);
        System.out.println("保存値(UTC): " + saved);
        System.out.println("表示(JST):   " + displayed);

        var nyWinter = ZonedDateTime.of(
            LocalDateTime.of(2025, 1, 15, 12, 0),
            ZoneId.of("America/New_York"));
        var nySummer = ZonedDateTime.of(
            LocalDateTime.of(2025, 7, 15, 12, 0),
            ZoneId.of("America/New_York"));
        System.out.println("NY冬: " + nyWinter.getOffset());
        System.out.println("NY夏: " + nySummer.getOffset());

        var offset = OffsetDateTime.now(ZoneOffset.of("+09:00"));
        System.out.println("OffsetDateTime: " + offset);
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

API に大きな変化はない。var 宣言とテキストブロックで記述が若干簡潔になる程度。

Java 17
// Java 17: var で簡潔に記述
var jst = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
var utc = jst.withZoneSameInstant(ZoneId.of("UTC"));
var dbValue = utc.format(
    DateTimeFormatter.ISO_OFFSET_DATE_TIME);

Library Comparison

Pure Java (java.time)タイムゾーン変換・DST 処理は標準 API で十分にカバーできる。タイムゾーンデータ(tzdata)の更新は JDK のアップデートに依存する。
Joda-TimeJava 7 以前の保守案件で DST 対応が必要な場合。Java 8 以降は java.time に移行すべき。Joda-Time のメンテナンスは縮小傾向。
ThreeTen-Extrajava.time にない追加クラス(Interval など)が必要な場合。タイムゾーン処理だけなら標準 API で十分。

注意点

ZonedDateTime は DST ルールを持つため、将来の予定を扱うのに適している。OffsetDateTime は固定オフセットなので DB 保存や API 連携向き

サマータイム切り替え時に「存在しない時刻」(2:00〜3:00 がスキップ)や「重複する時刻」が生じる。ZonedDateTime はこれを自動調整するが、意図通りか確認が必要

DB には UTC で保存するのが鉄則。ローカルタイムで保存すると、DST やタイムゾーン変更時にデータが破綻する

ZoneId.of() に不正な文字列を渡すと DateTimeException が発生する。外部入力の場合はバリデーションが必要

FAQ

ZonedDateTime と OffsetDateTime のどちらを DB に保存すべきですか?

DB には OffsetDateTime(UTC 固定)で保存するのが安全です。ZonedDateTime のタイムゾーンルールは DB に保存しにくいためです。

サマータイム切り替え時に時刻が重複する場合はどうなりますか?

ZonedDateTime.of() は自動的に「先に来る方」のオフセットを選びます。明示的に制御するには withEarlierOffsetAtOverlap / withLaterOffsetAtOverlap を使います。

タイムゾーンデータ(tzdata)はどのタイミングで更新されますか?

JDK のマイナーアップデートに含まれます。TZUpdater ツールで個別に更新することも可能です。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます