まず「タイムゾーンって何者か」を整理する
タイムゾーンは、ざっくり言うと
「その場所の“ローカルな時刻”と、世界共通の時刻(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)で保存する- もしくは
Instant+ZoneIdをセットで保存する
という設計がよく使われます。
例:
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 を基本にして、“どこの時間なのか”を常に意識してコードに書く。」
