Javaの日付・時刻で注意したいタイムゾーンの基本と実務での使い方
Javaで日付や時刻を扱っていると、タイムゾーンによって日付がずれることがあります。
例えば、日本時間では 2026年5月18日 00:30 でも、UTCでは 2026年5月17日 15:30 になります。
ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
ZonedDateTime tokyoTime = ZonedDateTime.of(
2026, 5, 18, 0, 30, 0, 0, tokyoZone);
ZonedDateTime utcTime = tokyoTime.withZoneSameInstant(ZoneId.of("UTC"));
System.out.println(tokyoTime);
System.out.println(utcTime);
出力例は以下のようになります。
2026-05-18T00:30+09:00[Asia/Tokyo]
2026-05-17T15:30Z[UTC]
このように、同じ瞬間の時刻でも、タイムゾーンが変わると日付が変わることがあります。
Javaで日付や時刻を扱う場合、特に以下のような点で迷いやすいです。
- LocalDateTime と ZonedDateTime の違いが分からない
- Instant は何に使うのか分からない
- ZoneId.of(“Asia/Tokyo") をいつ指定すべきか分からない
- UTCと日本時間で日付がずれる理由が分からない
- DB保存時や画面表示時にどの型を使えばよいか分からない
この記事では、Javaのタイムゾーンについて、基本的な考え方から実務で気を付けるポイントまでまとめます。
- 1. Javaのタイムゾーンとは
- 2. LocalDateTimeにはタイムゾーンがない
- 3. ZonedDateTimeはタイムゾーン付きの日時
- 4. InstantはUTC基準の瞬間を表す
- 5. Instantを日本時間に変換する
- 6. ZonedDateTimeをInstantに変換する
- 7. UTCと日本時間で日付がずれる例
- 8. LocalDate.now() はタイムゾーンに注意する
- 9. LocalDateTime.now() も注意が必要
- 10. withZoneSameInstant と withZoneSameLocal の違い
- 11. タイムゾーンを変換するときの考え方
- 12. DB保存時はUTCを基準にすると扱いやすい
- 13. 業務日付はLocalDateで扱うことも多い
- 14. 日付比較でタイムゾーンがずれる例
- 15. サマータイムがある地域にも注意する
- 16. OffsetDateTimeとの違い
- 17. タイムゾーンの指定は文字列で直接書きすぎない
- 18. Clockを使うとテストしやすい
- 19. 実務での使い分け
- 20. よくある注意点
- 21. まとめ
Javaのタイムゾーンとは
タイムゾーンとは、地域ごとの時刻の基準です。
例えば、日本では Asia/Tokyo というタイムゾーンを使います。
日本時間は UTC より9時間進んでいます。
- UTC:世界標準時
- Asia/Tokyo:日本時間
- America/Los_Angeles:ロサンゼルス時間
- America/New_York:ニューヨーク時間
Javaでは、タイムゾーンを表すために ZoneId を使います。
ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
ZoneId utcZone = ZoneId.of("UTC");
ZoneId losAngelesZone = ZoneId.of("America/Los_Angeles");
日本時間で現在日時を取得したい場合は、以下のように書きます。
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
System.out.println(now);
LocalDateTimeにはタイムゾーンがない
Javaの日付時刻で特に注意したいのが、LocalDateTime です。
LocalDateTime は、日付と時刻を持っていますが、タイムゾーン情報は持っていません。
LocalDateTime dateTime = LocalDateTime.of(2026, 5, 18, 10, 0);
System.out.println(dateTime);
出力例は以下です。
2026-05-18T10:00
この値だけを見ると、2026年5月18日 10:00 であることは分かります。
しかし、以下のどの時刻なのかは分かりません。
- 日本時間の 10:00
- UTCの 10:00
- ロサンゼルス時間の 10:00
つまり、LocalDateTime は「どの地域の時刻なのか」という情報を持たない日時です。
そのため、タイムゾーンを意識する必要がある処理では、LocalDateTime を安易に使うと日付ずれの原因になります。
ZonedDateTimeはタイムゾーン付きの日時
ZonedDateTime は、日付、時刻、タイムゾーンをまとめて扱うクラスです。
例えば、日本時間の 2026年5月18日 10:00 を表したい場合は、以下のように書きます。
ZonedDateTime tokyoTime = ZonedDateTime.of(
2026, 5, 18, 10, 0, 0, 0,
ZoneId.of("Asia/Tokyo"));
System.out.println(tokyoTime);
出力例は以下です。
2026-05-18T10:00+09:00[Asia/Tokyo]
+09:00 は、UTCより9時間進んでいることを意味します。
また、[Asia/Tokyo] はタイムゾーンIDです。
ZonedDateTime を使うと、その日時がどのタイムゾーンの時刻なのかを明確にできます。
InstantはUTC基準の瞬間を表す
Instant は、UTC基準の瞬間を表すクラスです。
DBに保存する日時や、システム間でやり取りする日時では、Instant が使われることがあります。
Instant now = Instant.now();
System.out.println(now);
出力例は以下です。
2026-05-18T01:00:00Z
末尾の Z は UTC を意味します。
Instant は「日本時間の10時」のような地域ごとの時刻ではなく、「世界共通のある瞬間」を表すイメージです。
Instantを日本時間に変換する
Instant は UTC基準の時刻なので、画面に表示するときは日本時間などに変換することがあります。
Instant を日本時間に変換するには、atZone を使います。
Instant instant = Instant.parse("2026-05-18T01:00:00Z");
ZonedDateTime tokyoTime = instant.atZone(ZoneId.of("Asia/Tokyo"));
System.out.println(tokyoTime);
出力例は以下です。
2026-05-18T10:00+09:00[Asia/Tokyo]
UTCの 01:00 は、日本時間では 10:00 になります。
ZonedDateTimeをInstantに変換する
逆に、タイムゾーン付きの日時を Instant に変換することもできます。
ZonedDateTime tokyoTime = ZonedDateTime.of(
2026, 5, 18, 10, 0, 0, 0,
ZoneId.of("Asia/Tokyo"));
Instant instant = tokyoTime.toInstant();
System.out.println(instant);
出力例は以下です。
2026-05-18T01:00:00Z
日本時間の 2026年5月18日 10:00 は、UTCでは 2026年5月18日 01:00 になります。
UTCと日本時間で日付がずれる例
タイムゾーンで特に問題になりやすいのが、日付のずれです。
例えば、UTCの 2026年5月17日 15:30 は、日本時間では 2026年5月18日 00:30 です。
Instant instant = Instant.parse("2026-05-17T15:30:00Z");
LocalDate utcDate = instant.atZone(ZoneId.of("UTC")).toLocalDate();
LocalDate tokyoDate = instant.atZone(ZoneId.of("Asia/Tokyo")).toLocalDate();
System.out.println(utcDate);
System.out.println(tokyoDate);
出力例は以下です。
2026-05-17
2026-05-18
同じ Instant から日付を取り出しているにもかかわらず、UTC基準では5月17日、日本時間基準では5月18日になります。
このような違いがあるため、業務処理で「今日の日付」を使う場合は、どのタイムゾーンを基準にするかを明確にする必要があります。
LocalDate.now() はタイムゾーンに注意する
今日の日付を取得するとき、以下のように書くことがあります。
LocalDate today = LocalDate.now();
System.out.println(today);
この書き方では、JVMのデフォルトタイムゾーンが使われます。
ローカル環境では日本時間でも、サーバー環境ではUTCになっている場合があります。
そのため、実行環境によって取得される日付が変わる可能性があります。
業務上、日本時間を基準にしたい場合は、以下のようにタイムゾーンを明示した方が安全です。
LocalDate today = LocalDate.now(ZoneId.of("Asia/Tokyo"));
System.out.println(today);
このように書くことで、実行環境のデフォルトタイムゾーンに依存せず、日本時間の日付を取得できます。
LocalDateTime.now() も注意が必要
LocalDateTime.now() も、デフォルトタイムゾーンに依存します。
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
このコードは、実行環境のデフォルトタイムゾーンを基準にした日時を返します。
日本時間を基準にしたい場合は、以下のように ZoneId を指定します。
LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Tokyo"));
System.out.println(now);
ただし、LocalDateTime 自体にはタイムゾーン情報は保持されません。
取得時に Asia/Tokyo を指定していても、作成された LocalDateTime の値には「Asia/Tokyoである」という情報は残りません。
タイムゾーン情報も含めて扱いたい場合は、ZonedDateTime を使います。
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
System.out.println(now);
withZoneSameInstant と withZoneSameLocal の違い
ZonedDateTime のタイムゾーンを変換するときには、withZoneSameInstant と withZoneSameLocal があります。
実務でよく使うのは withZoneSameInstant です。
withZoneSameInstant は、同じ瞬間を別のタイムゾーンで表します。
ZonedDateTime tokyoTime = ZonedDateTime.of(
2026, 5, 18, 10, 0, 0, 0,
ZoneId.of("Asia/Tokyo"));
ZonedDateTime utcTime = tokyoTime.withZoneSameInstant(ZoneId.of("UTC"));
System.out.println(tokyoTime);
System.out.println(utcTime);
出力例は以下です。
2026-05-18T10:00+09:00[Asia/Tokyo]
2026-05-18T01:00Z[UTC]
日本時間の10:00とUTCの01:00は、同じ瞬間を表しています。
一方、withZoneSameLocal は、日付時刻の見た目を変えずにタイムゾーンだけを変えます。
ZonedDateTime tokyoTime = ZonedDateTime.of(
2026, 5, 18, 10, 0, 0, 0,
ZoneId.of("Asia/Tokyo"));
ZonedDateTime utcTime = tokyoTime.withZoneSameLocal(ZoneId.of("UTC"));
System.out.println(tokyoTime);
System.out.println(utcTime);
出力例は以下です。
2026-05-18T10:00+09:00[Asia/Tokyo]
2026-05-18T10:00Z[UTC]
この場合、日本時間の10:00とUTCの10:00は、同じ瞬間ではありません。
通常、タイムゾーン変換で使いたいのは withZoneSameInstant です。
タイムゾーンを変換するときの考え方
タイムゾーンを変換するときは、以下の2つを分けて考えると分かりやすいです。
- 同じ瞬間を、別の地域の時刻として表示したい
- 日付時刻の文字だけを別のタイムゾーン扱いにしたい
多くの業務システムでは、前者の「同じ瞬間を、別の地域の時刻として表示したい」というケースが多いです。
その場合は、withZoneSameInstant を使います。
ZonedDateTime displayTime = storedInstant.atZone(ZoneId.of("Asia/Tokyo"));
DBにはUTC基準で保存し、画面表示時に日本時間へ変換する、という考え方です。
DB保存時はUTCを基準にすると扱いやすい
実務では、DBに日時を保存するときはUTC基準にしておくと扱いやすいです。
例えば、登録日時や更新日時などは、Instant として扱うと分かりやすいです。
Instant createdAt = Instant.now();
Instant updatedAt = Instant.now();
UTC基準で保存しておけば、システムの実行環境や利用者の地域が変わっても、同じ瞬間として扱いやすくなります。
画面に表示するときは、必要に応じて日本時間などに変換します。
Instant createdAt = Instant.parse("2026-05-18T01:00:00Z");
ZonedDateTime displayTime = createdAt.atZone(ZoneId.of("Asia/Tokyo"));
System.out.println(displayTime);
出力例は以下です。
2026-05-18T10:00+09:00[Asia/Tokyo]
業務日付はLocalDateで扱うことも多い
一方で、すべての日時を Instant にすればよいわけではありません。
例えば、以下のような項目は、時刻ではなく日付そのものが重要です。
- 生年月日
- 適用開始日
- 適用終了日
- 申込日
- 請求対象日
このような項目は、LocalDate で扱うことが多いです。
LocalDate startDate = LocalDate.of(2026, 5, 1);
LocalDate endDate = LocalDate.of(2026, 5, 31);
System.out.println(startDate);
System.out.println(endDate);
ただし、「今日」と比較する場合は、どのタイムゾーンの今日なのかを決める必要があります。
日本時間の今日を基準にする場合は、以下のように書きます。
LocalDate today = LocalDate.now(ZoneId.of("Asia/Tokyo"));
if (endDate.isBefore(today)) {
System.out.println("適用終了日が過去日です");
}
日付比較でタイムゾーンがずれる例
タイムゾーンの考慮漏れは、日付比較の不具合につながりやすいです。
例えば、画面上は日本時間の日付を基準にしているのに、サーバー側ではUTCの日付を使っている場合です。
Instant instant = Instant.parse("2026-05-17T15:30:00Z");
LocalDate utcToday = instant.atZone(ZoneId.of("UTC")).toLocalDate();
LocalDate tokyoToday = instant.atZone(ZoneId.of("Asia/Tokyo")).toLocalDate();
System.out.println(utcToday);
System.out.println(tokyoToday);
出力例は以下です。
2026-05-17
2026-05-18
この状態で、画面では5月18日として扱っているのに、サーバー側では5月17日として判定してしまうと、想定と異なるチェック結果になる可能性があります。
そのため、日付チェックでは以下をそろえることが重要です。
- 画面表示の基準タイムゾーン
- サーバー側の日付判定の基準タイムゾーン
- DB保存時の日時の基準
サマータイムがある地域にも注意する
日本にはサマータイムがありません。
しかし、America/Los_Angeles や America/New_York など、一部の地域にはサマータイムがあります。
サマータイムがある地域では、時期によってUTCとの時差が変わります。
そのため、固定で「UTCから何時間ずらす」と考えるのは危険です。
例えば、以下のように ZoneId を使って変換するのが安全です。
Instant instant = Instant.parse("2026-07-01T12:00:00Z");
ZonedDateTime losAngelesTime = instant.atZone(ZoneId.of("America/Los_Angeles"));
System.out.println(losAngelesTime);
タイムゾーンを扱う場合は、+09:00 や -08:00 のような固定の時差だけで考えるのではなく、Asia/Tokyo や America/Los_Angeles のようなタイムゾーンIDを使うのが分かりやすいです。
OffsetDateTimeとの違い
Javaには OffsetDateTime というクラスもあります。
OffsetDateTime は、UTCからの時差を持つ日時です。
OffsetDateTime dateTime = OffsetDateTime.parse("2026-05-18T10:00:00+09:00");
System.out.println(dateTime);
出力例は以下です。
2026-05-18T10:00+09:00
ZonedDateTime との違いは、タイムゾーンIDを持つかどうかです。
- ZonedDateTime:Asia/Tokyo のようなタイムゾーンIDを持つ
- OffsetDateTime:+09:00 のような時差を持つ
日本時間だけを扱う場合は大きな違いを感じにくいですが、サマータイムがある地域では ZoneId を持つ ZonedDateTime の方が扱いやすい場面があります。
タイムゾーンの指定は文字列で直接書きすぎない
ZoneId.of(“Asia/Tokyo") を何度も直接書くと、修正漏れやタイプミスの原因になります。
業務システムで基準タイムゾーンが決まっている場合は、定数として定義しておくと分かりやすいです。
private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Tokyo");
public LocalDate getToday() {
return LocalDate.now(BUSINESS_ZONE);
}
このようにしておくと、システム全体でどのタイムゾーンを基準にしているのかが分かりやすくなります。
Clockを使うとテストしやすい
現在日時を直接取得する処理は、テストしづらくなることがあります。
例えば、以下のように LocalDate.now() を直接書いている場合です。
public boolean isExpired(LocalDate endDate) {
LocalDate today = LocalDate.now(ZoneId.of("Asia/Tokyo"));
return endDate.isBefore(today);
}
このコードは、実行した日によって結果が変わります。
テストしやすくするには、Clock を使う方法があります。
public class DateService {
private final Clock clock;
public DateService(Clock clock) {
this.clock = clock;
}
public boolean isExpired(LocalDate endDate) {
LocalDate today = LocalDate.now(clock);
return endDate.isBefore(today);
}
}
テストでは、固定の日時を持つ Clock を渡せます。
Clock fixedClock = Clock.fixed(
Instant.parse("2026-05-18T00:00:00Z"),
ZoneId.of("Asia/Tokyo"));
DateService service = new DateService(fixedClock);
boolean result = service.isExpired(LocalDate.of(2026, 5, 17));
System.out.println(result);
このようにすると、現在日時に依存する処理でも安定してテストできます。
実務での使い分け
Javaでタイムゾーンを扱う場合、用途によって使うクラスを分けると分かりやすいです。
- 日付だけを扱うなら LocalDate
- 時刻だけを扱うなら LocalTime
- タイムゾーン不要の日時なら LocalDateTime
- タイムゾーン込みの日時なら ZonedDateTime
- UTC基準の瞬間を扱うなら Instant
- UTCからの時差付き日時なら OffsetDateTime
特に、DB保存やAPI連携では Instant、画面表示では ZonedDateTime や LocalDateTime、業務日付では LocalDate を使うことが多いです。
ただし、システムの設計によって最適な型は変わります。
大切なのは、日時を扱うときに「これはどのタイムゾーン基準の値なのか」を明確にすることです。
よくある注意点
Javaのタイムゾーンでよくある注意点をまとめると、以下のようになります。
- LocalDateTime にはタイムゾーン情報がない
- LocalDate.now() は実行環境のデフォルトタイムゾーンに依存する
- UTCと日本時間では日付が変わる時間帯がある
- DB保存時と画面表示時で基準タイムゾーンを混同しない
- サマータイムがある地域では固定の時差で考えない
- タイムゾーン変換では withZoneSameInstant を使う場面が多い
特に、日付チェックや期限判定では、タイムゾーンの考慮漏れが不具合につながりやすいです。
まとめ
Javaでタイムゾーンを扱うときは、LocalDateTime、ZonedDateTime、Instant の違いを理解することが大切です。
LocalDateTime は日付と時刻を持ちますが、タイムゾーン情報は持っていません。
ZonedDateTime は、日付、時刻、タイムゾーンをまとめて扱えます。
Instant は、UTC基準の瞬間を表します。
実務では、DBにはUTC基準で保存し、画面表示時に日本時間などへ変換する設計がよく使われます。
また、業務日付を比較する場合は、どのタイムゾーンの「今日」を使うのかを明確にする必要があります。
タイムゾーンの扱いを曖昧にすると、日付が1日ずれる不具合につながることがあります。
そのため、Javaで日付・時刻を扱うときは、ZoneId.of(“Asia/Tokyo") のように基準となるタイムゾーンを明示することを意識しましょう。


