Java | Java 標準ライブラリ:ZonedDateTime

Java Java
スポンサーリンク

ZonedDateTime をざっくり一言でいうと

ZonedDateTime は、

「日付(年・月・日)+時刻(時・分・秒)+タイムゾーン」

をまとめて表現するクラスです。

LocalDateTime が「カレンダー上の日時(どこの国かは知らない)」なのに対して、
ZonedDateTime は「東京の 2025-01-10 09:30」「ニューヨークの 2025-01-09 19:30」のように、
“場所まで含めた本当の日時”を扱えます。

ここをしっかり押さえておくと、時差や夏時間にまつわるバグをかなり防げます。


基本イメージ:LocalDateTime + ZoneId = ZonedDateTime

まず ZoneId の感覚から

タイムゾーンは ZoneId というクラスで表します。

import java.time.ZoneId;

ZoneId tokyo = ZoneId.of("Asia/Tokyo");
ZoneId newYork = ZoneId.of("America/New_York");
ZoneId system = ZoneId.systemDefault(); // 実行環境のデフォルトタイムゾーン
Java

“Asia/Tokyo” や “Europe/London” のような ID は、
IANA タイムゾーンデータベースに基づいた「ちゃんとした名前」です。

「GMT+9」みたいな単純なオフセットではなく、
夏時間(サマータイム)や歴史的な変更も含めて管理されます。

LocalDateTime と ZoneId から ZonedDateTime を作る

import java.time.*;

public class ZonedBasic {
    public static void main(String[] args) {
        LocalDateTime local = LocalDateTime.of(2025, 1, 10, 9, 30);
        ZoneId tokyo = ZoneId.of("Asia/Tokyo");

        ZonedDateTime zdt = ZonedDateTime.of(local, tokyo);

        System.out.println(zdt); // 2025-01-10T09:30+09:00[Asia/Tokyo]
    }
}
Java

LocalDateTime だけだと「2025-01-10T09:30」でしたが、
ZonedDateTime になると

日付と時刻
UTC とのオフセット(+09:00)
タイムゾーン ID(Asia/Tokyo)

まで一体になっています。

この「+09:00」や Asia/Tokyo の情報が、時差計算や世界共通の瞬間との変換で重要になります。


ZonedDateTime の「今」を扱う

システムデフォルトタイムゾーンでの現在日時

import java.time.ZonedDateTime;

ZonedDateTime now = ZonedDateTime.now();
System.out.println(now); // 例: 2025-01-10T09:30:15.123456789+09:00[Asia/Tokyo]
Java

LocalDateTime.now() との違いは、
タイムゾーンとオフセットが含まれていることです。

同じ瞬間でも、
東京で now を取ると +09:00[Asia/Tokyo]
ニューヨークで now を取ると -05:00[America/New_York](夏時間中は -04:00)

という違いが出ます。

指定したタイムゾーンでの現在日時

import java.time.*;

ZoneId newYork = ZoneId.of("America/New_York");
ZonedDateTime nowInNewYork = ZonedDateTime.now(newYork);

System.out.println(nowInNewYork);
Java

東京のマシンで実行していても、
「ニューヨークの今」を簡単に取得できます。


時差変換(タイムゾーン変換)が ZonedDateTime の真骨頂

東京時間をニューヨーク時間に変換する

ある会議が「東京時間で 2025-01-10 9:30」だとします。
それを「ニューヨークでは何日の何時か」に変換してみましょう。

import java.time.*;

public class ZonedConversion {
    public static void main(String[] args) {
        ZoneId tokyo   = ZoneId.of("Asia/Tokyo");
        ZoneId newYork = ZoneId.of("America/New_York");

        ZonedDateTime tokyoMeeting =
                ZonedDateTime.of(2025, 1, 10, 9, 30, 0, 0, tokyo);

        ZonedDateTime nyMeeting =
                tokyoMeeting.withZoneSameInstant(newYork);

        System.out.println("Tokyo: " + tokyoMeeting);
        System.out.println("NY   : " + nyMeeting);
    }
}
Java

withZoneSameInstant(newYork) は、

「同じ瞬間を、別のタイムゾーンとして見たらどうなるか」

という変換です。

例えば、出力イメージとしては:

Tokyo: 2025-01-10T09:30+09:00[Asia/Tokyo]
NY : 2025-01-09T19:30-05:00[America/New_York]

のようになります。

日付まで変わっていることに注目してください。
「東京で 10 日の朝 9:30 は、ニューヨークでは前日の 19:30」
ということを、ZonedDateTime が自動で計算してくれています。

「瞬間を変えずにタイムゾーンだけ変える」ことの意味

ここでのキモは、
「世界共通の瞬間(Instant)は変えず、見方(タイムゾーン)だけ変える」
ということです。

内部的には、

tokyoMeeting.toInstant()
→ 世界標準時(UTC)での瞬間
→ newYork のタイムゾーンで LocalDateTime に換算する

という流れが隠れています。

ZonedDateTime を使うことで、
この一連の変換を安全に、簡単に書けるわけです。


Instant / Date との関係(サーバー保存・外部システム連携)

ZonedDateTime ↔ Instant

Instant は「UTC の瞬間」を表すクラスです。

ZonedDateTime から Instant に変換するのは簡単です。

import java.time.*;

ZonedDateTime zdt = ZonedDateTime.of(
        2025, 1, 10, 9, 30, 0, 0,
        ZoneId.of("Asia/Tokyo"));

Instant instant = zdt.toInstant();
System.out.println(instant); // 2025-01-10T00:30:00Z(UTC)
Java

逆に、Instant を特定タイムゾーンの ZonedDateTime にするには ofInstant を使います。

Instant instant = Instant.now();
ZoneId london = ZoneId.of("Europe/London");

ZonedDateTime zdtInLondon = ZonedDateTime.ofInstant(instant, london);
System.out.println(zdtInLondon);
Java

「ストレージや通信では Instant(UTC)で持ち、
ユーザー表示やロジックでは ZonedDateTime(各ユーザーのタイムゾーン)で扱う」

という設計は、時刻周りのバグを減らす定番パターンです。

Date とも簡単に相互変換できる

古い API が java.util.Date を使っている場合でも、
Instant を経由すれば安全に変換できます。

import java.time.*;
import java.util.Date;

// Date → ZonedDateTime(東京時間で解釈)
Date legacy = new Date();
Instant instant = legacy.toInstant();
ZoneId tokyo = ZoneId.of("Asia/Tokyo");

ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, tokyo);

// ZonedDateTime → Date(タイムゾーンを考慮した瞬間に変換)
Instant instant2 = zdt.toInstant();
Date date = Date.from(instant2);
Java

大事なのは、「Date にはタイムゾーン情報がない」ので、
どのタイムゾーンとして解釈するか(東京なのかニューヨークなのか)を必ず決めてから ZonedDateTime にすることです。


夏時間(サマータイム)と「ありえない時刻」「重なって見える時刻」

ここはゾーン付き日時を扱ううえで、とても重要なポイントです。

夏時間で「存在しない時刻」がある

例えば、ある国で「春に 2 時から 3 時の間の 1 時間を飛ばす」ルールがあるとします。

2025 年のある日、
01:59 の次が、いきなり 03:00 になる世界です。

その場合、その日の「02:30」という LocalDateTime は、
そのタイムゾーンでは「存在しない時刻」になります。

ZonedDateTime は、そういうケースにも対応します。

import java.time.*;

ZoneId zone = ZoneId.of("Europe/Berlin"); // 夏時間がある例
LocalDateTime nonexistent = LocalDateTime.of(2025, 3, 30, 2, 30);

ZonedDateTime zdt = ZonedDateTime.of(nonexistent, zone);
System.out.println(zdt);
Java

実際の出力は環境や年によって違いますが、多くの場合、

「そのタイムゾーンで存在する、最も近い妥当な時刻」

に補正されます。

つまり、「夏時間の境界で LocalDateTime を直接 ZonedDateTime にするときには、
想像と違う時刻になる可能性がある」ということです。

夏時間終了時の「重なって見える時刻」

逆に、「秋に 2 時台を 2 回繰り返す」ケースもあります。

01:59 → 02:00 → … → 02:59 → 02:00(オフセットが変わる)→ 02:59 → 03:00

のように、「同じ時計表示の 2:30 が 2 回」存在する状況になります。

この場合、
LocalDateTime.of(2025, X, Y, 2, 30) を ZonedDateTime にするとき、
その 2:30 のどちらを選ぶか、という問題が出てきます。

ZonedDateTime は、その辺りも含めて「ゾーン付きの一意な瞬間」に決着をつけてくれるクラスであり、
だからこそ「世界共通の Instant との橋渡し役」として非常に重要です。

初心者の段階では、
「夏時間のある国では、LocalDateTime 単体での計算には気をつける」
「ZonedDateTime か Instant をベースに考えたほうが安全」

くらいの理解で十分です。


フォーマット/パースと表示

ZonedDateTime → 文字列

DateTimeFormatter を使えば、タイムゾーン込みの表示も簡単です。

import java.time.*;
import java.time.format.DateTimeFormatter;

public class ZonedFormat {
    public static void main(String[] args) {
        ZoneId tokyo = ZoneId.of("Asia/Tokyo");
        ZonedDateTime zdt = ZonedDateTime.of(2025, 1, 10, 9, 30, 0, 0, tokyo);

        DateTimeFormatter fmt =
                DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss O (VV)");

        String s = zdt.format(fmt);
        System.out.println(s); // 2025-01-10 09:30:00 GMT+9 (Asia/Tokyo) のような表示
    }
}
Java

O はオフセットのテキスト表現(GMT+9 など)、
VV はタイムゾーン ID(Asia/Tokyo)です。

シンプルに "yyyy-MM-dd HH:mm:ss Z" などもよく使います。

文字列 → ZonedDateTime

フォーマットが分かっていれば、parse で ZonedDateTime にできます。

String text = "2025-01-10 09:30:00 +0900 Asia/Tokyo";

DateTimeFormatter fmt =
        DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss Z VV");

ZonedDateTime zdt = ZonedDateTime.parse(text, fmt);
System.out.println(zdt);
Java

ログや設定ファイルで「タイムゾーン付き日時」を扱うときに便利です。


ZonedDateTime をいつ使うべきか(感覚の整理)

最後に、他の日時クラスとの棲み分けを整理しておきます。

日付だけなら LocalDate
時刻だけなら LocalTime
日付+時刻(どの国かは関係ない)なら LocalDateTime
タイムゾーンまで含めた「その国の日時」として扱いたいなら ZonedDateTime
世界共通の「瞬間」として保存・比較したいなら Instant(古い API 相手なら Date

ZonedDateTime は特に、

ユーザーごとにタイムゾーンが違うシステム
国際会議のスケジュール
世界中のユーザーが触るサービスの UI 表示(各自の現地時間で表示したい)

のように、「どこのタイムゾーンの日時なのか」が本質的に重要な場面で真価を発揮します。


まとめ:ZonedDateTime を自分の中でこう位置づける

ZonedDateTime を初心者向けに一言でまとめると、

「日付+時刻+タイムゾーンをセットで持ち、“世界共通の瞬間”と安全に行き来できるクラス」

です。

意識しておきたいポイントは、

  • LocalDateTime に ZoneId を足したものが ZonedDateTime
  • withZoneSameInstant で、瞬間は変えずに別タイムゾーンの日時に変換できる
  • toInstant / ofInstant で、Instant(UTC)と相互変換できる
  • 夏時間など「存在しない時刻」「重なって見える時刻」がある国に対しても、タイムゾーンルールを考慮してくれる

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