Java | Java 詳細・モダン文法:日付・時刻 API – TimeZone の扱い

Java Java
スポンサーリンク

まず「タイムゾーンって何者か」を整理する

タイムゾーンは、ざっくり言うと
「その場所の“ローカルな時刻”と、世界共通の時刻(UTC)をどう対応づけるかのルール」
です。

日本(Asia/Tokyo)は「UTC+9」で、夏時間もありません。
アメリカの多くの地域は「標準時」と「夏時間」でオフセットが変わります。

Java の java.time では、この「場所ごとのルール」を ZoneId / ZoneOffset というクラスで表現し、
「タイムゾーン付きの日時」を ZonedDateTime で扱います。


タイムゾーンなしの日時と、ありの日時を区別する

LocalDateTime は「タイムゾーンなしのカレンダー上の日時」

LocalDateTime は「年・月・日・時・分・秒」だけを持つクラスです。

LocalDateTime local = LocalDateTime.of(2025, 1, 18, 10, 0);
System.out.println(local); // 2025-01-18T10:00
Java

これは「どこの 10:00 か」は分かりません。
東京かもしれないし、ニューヨークかもしれない。

「カレンダー上の日時」だけを扱いたいとき(例:締切日、営業開始時刻など)は LocalDateTime で十分ですが、
「世界共通の瞬間」として扱いたいときにはタイムゾーンが必要になります。

ZonedDateTime は「タイムゾーン付きの日時」

ZonedDateTime は、「日時+タイムゾーン」をセットで持つクラスです。

ZonedDateTime tokyoTime =
        ZonedDateTime.of(2025, 1, 18, 10, 0, 0, 0, ZoneId.of("Asia/Tokyo"));

System.out.println(tokyoTime); // 2025-01-18T10:00+09:00[Asia/Tokyo]
Java

ここでは、

  • カレンダー上の日時:2025-01-18 10:00
  • オフセット:+09:00
  • タイムゾーン ID:Asia/Tokyo

がセットになっています。
これで初めて、「世界のどの瞬間を指しているか」が一意に決まります。


「世界共通の瞬間」と「各地のローカル時刻」を行き来する

Instant(UTC の瞬間)から各地の時刻へ

Instant は「UTC 基準の瞬間」です。

Instant now = Instant.now();
Java

これを「東京時間」で見たいなら、こう変換します。

ZonedDateTime tokyoNow = now.atZone(ZoneId.of("Asia/Tokyo"));
System.out.println(tokyoNow); // 2025-01-18T...+09:00[Asia/Tokyo]
Java

同じ瞬間を「ニューヨーク時間」で見るなら、こうです。

ZonedDateTime newYorkNow = now.atZone(ZoneId.of("America/New_York"));
System.out.println(newYorkNow); // 2025-01-17T...-05:00[America/New_York] など
Java

ポイントは、

  • Instant は「世界共通の一点」
  • ZonedDateTime は「その地点を、あるタイムゾーンのローカル時刻として見たもの」

という関係になっていることです。

ローカル時刻から「世界共通の瞬間」へ

逆に、「東京の 2025-01-18 10:00」を世界共通の瞬間にしたいなら、こうします。

ZonedDateTime tokyoTime =
        ZonedDateTime.of(2025, 1, 18, 10, 0, 0, 0, ZoneId.of("Asia/Tokyo"));

Instant instant = tokyoTime.toInstant();
System.out.println(instant); // 2025-01-18T01:00:00Z など
Java

ここで Z は「UTC」を意味します。
「東京の 10:00」は「UTC の 1:00」として表現される、という対応関係です。


タイムゾーン変換の基本パターン

ZonedDateTime のまま別タイムゾーンに変換する

「東京時間での会議日時」を、「ニューヨークの人にとってのローカル時刻」に変換したいケースを考えます。

ZonedDateTime tokyoMeeting =
        ZonedDateTime.of(2025, 1, 18, 10, 0, 0, 0, ZoneId.of("Asia/Tokyo"));
Java

これをニューヨーク時間に変換するには、withZoneSameInstant を使います。

ZonedDateTime newYorkMeeting =
        tokyoMeeting.withZoneSameInstant(ZoneId.of("America/New_York"));

System.out.println(tokyoMeeting);   // 2025-01-18T10:00+09:00[Asia/Tokyo]
System.out.println(newYorkMeeting); // 2025-01-17T20:00-05:00[America/New_York] など
Java

ここで大事なのは、

  • 「同じ瞬間(instant)」を、別のタイムゾーンのローカル時刻として見直している
  • 日付や時刻の数字は変わるが、「世界共通の瞬間」は変わらない

という点です。

「数字だけ変える」withZoneSameLocal との違い

withZoneSameLocal というメソッドもありますが、これは「ローカルな数字を変えずに、タイムゾーンだけ変える」危険なメソッドです。

ZonedDateTime z1 = tokyoMeeting.withZoneSameLocal(ZoneId.of("America/New_York"));
Java

これは「2025-01-18 10:00」という数字をそのままにして、タイムゾーンだけ Tokyo→New_York に変えます。
つまり、「別の瞬間」を指すことになります。

通常、「同じ瞬間を別のタイムゾーンで見たい」ケースが圧倒的に多いので、
基本的には withZoneSameInstant を使う、と覚えておくと安全です。


「アプリ内部ではどう持つか?」という設計の話

保存・通信は Instant(+必要なら ZoneId)

DB やメッセージで日時をやり取りするときは、

  • Instant(UTC)で保存する
  • もしくは InstantZoneId をセットで保存する

という設計がよく使われます。

例:

Instant createdAt = Instant.now(); // DB には epoch milli などで保存
Java

こうしておけば、

  • サーバーのタイムゾーンに依存しない
  • 別の国のユーザーにも、好きなタイムゾーンで表示できる

というメリットがあります。

表示やビジネスロジックは ZonedDateTime / LocalDateTime

ユーザーに見せるときや、ビジネスルールを評価するときは、

  • ユーザーのタイムゾーンが分かっているなら ZonedDateTime
  • タイムゾーンを意識しない「日付だけ」「時刻だけ」なら LocalDate / LocalTime / LocalDateTime

を使います。

例えば、「ユーザーのタイムゾーンで“今日”の注文だけを集計したい」なら、

ZoneId userZone = ZoneId.of("America/New_York");
ZonedDateTime nowUser = Instant.now().atZone(userZone);
LocalDate todayUser = nowUser.toLocalDate();
Java

のように、「ユーザーのタイムゾーンでの今日」をまず求めてから、
その日付を使って検索条件を組み立てる、という流れになります。


「サーバーのタイムゾーンに依存しない」ことの重要性

System.currentTimeMillis() とサーバー設定の罠

古いコードでは、

new Date();
System.currentTimeMillis();
Java

のように、サーバーのローカルタイムゾーンに依存した形で時刻を扱っていることがよくあります。

サーバーのタイムゾーン設定が変わると、ログの時刻や計算結果が変わってしまう、という事故も起きがちです。

java.time では、

  • 「瞬間」は Instant(UTC)で扱う
  • 「ローカル時刻」は明示的に ZoneId を指定して計算する

というスタイルにすることで、
「サーバーのタイムゾーン設定に振り回されない」設計にできます。

now(ZoneId) を明示的に使う

LocalDateTime.now()ZonedDateTime.now() は、デフォルトで「システムのデフォルトタイムゾーン」を使います。

本当にそれでいいのか?
ユーザーごとのタイムゾーンを使うべきでは?

と一度立ち止まるクセをつけると、タイムゾーン設計の質が上がります。

ZonedDateTime nowInTokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
Java

のように、「どこの時間なのか」をコードに刻むのが理想です。


まとめ:TimeZone の扱いを自分の言葉で言うなら

あなたの言葉でまとめると、こうなります。

「タイムゾーンは、“ローカルな時刻”と“世界共通の時刻(UTC)”を結びつけるルール。
java.time では、
・タイムゾーンなしの日時は LocalDateTime
・タイムゾーン付きの日時は ZonedDateTime
・世界共通の瞬間は Instant
・タイムゾーン自体は ZoneId
で表現する。

保存や通信では Instant(+必要なら ZoneId)を使い、
表示やビジネスロジックではユーザーの ZoneId を使って ZonedDateTime / LocalDateTime に変換する。
タイムゾーン変換は withZoneSameInstant を基本にして、“どこの時間なのか”を常に意識してコードに書く。」

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