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のタイムゾーンについて、基本的な考え方から実務で気を付けるポイントまでまとめます。

スポンサーリンク

※このページにはプロモーションが含まれています。当サイトは各種アフィリエイトプログラムから一定の収益を得ています。

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") のように基準となるタイムゾーンを明示することを意識しましょう。

スポンサーリンク

Java