概要
タイムゾーン起因の不具合は、開発環境では再現せず本番で初めて発覚することが多い、質の悪い部類の問題です。日本国内向けのシステムでも、クラウド上のサーバが UTC で動いていたために保存時刻が9時間ずれていた、という話は珍しくありません。Java 8 以降の ZonedDateTime と ZoneId を使えば時差の変換自体は難しくありませんが、本当に重要なのは「どの時点で、どのタイムゾーンとして扱うか」という設計の方です。この記事では、JST・UTC・海外拠点間の変換の基本操作を確認したうえで、UTC で保存して表示時に変換する定石パターン、サマータイムの切り替わりが絡むときの注意点までを整理します。
使いどころ
UTC で動くクラウドサーバと日本のクライアント間で、注文日時をずれなく保存・表示する
米国拠点とのファイル連携で、先方のタイムスタンプ(EST/EDT)を JST に変換して照合する
障害調査で、アプリログ(JST)とクラウド監視サービスのログ(UTC)の時刻を突き合わせる
コード例
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class TimezoneCompare {
public static void main(String[] args) {
var fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm z");
// 日本時間 2025-03-10 09:00 を各拠点の時刻へ変換
var jst = ZonedDateTime.of(2025, 3, 10, 9, 0, 0, 0,
ZoneId.of("Asia/Tokyo"));
for (var zone : java.util.List.of(
"Asia/Tokyo", "UTC", "America/New_York")) {
System.out.printf("%-18s %s%n", zone,
jst.withZoneSameInstant(ZoneId.of(zone)).format(fmt));
}
// Asia/Tokyo 2025-03-10 09:00 JST
// UTC 2025-03-10 00:00 UTC
// America/New_York 2025-03-09 20:00 EDT
// サマータイム境界: NY は 2025/3/9 に夏時間入りするため、
// 前々日の同時刻では JST との時差が 1 時間広い
var beforeDst = ZonedDateTime.of(2025, 3, 8, 9, 0, 0, 0,
ZoneId.of("Asia/Tokyo"));
System.out.println(beforeDst
.withZoneSameInstant(ZoneId.of("America/New_York"))
.format(fmt));
// → 2025-03-07 19:00 EST(時差 14 時間。夏時間中は 13 時間)
}
}Version Coverage
基本的な使い方は Java 8 と同じ。switch 式で出力フォーマットの切り替えが簡潔に書ける。
// Java 17: DB へは UTC で保存し、表示時に JST へ変換する
var now = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
// DB保存は UTC、表示は JST パターン
String dbValue = now.withZoneSameInstant(ZoneId.of("UTC"))
.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
// 取得時に表示用へ変換
var fromDb = ZonedDateTime.parse(dbValue);
String display = fromDb
.withZoneSameInstant(ZoneId.of("Asia/Tokyo"))
.format(DateTimeFormatter
.ofPattern("yyyy/MM/dd HH:mm"));Library Comparison
注意点
DB には UTC で保存し、表示時にユーザーのタイムゾーンに変換するのが安全。
サマータイムの切り替え時に時刻が重複・欠落する場合がある。
ZoneId.of() に不正な文字列を渡すと DateTimeException が発生する。
DB のタイムスタンプを UTC で保存しているつもりが、JDBC ドライバのデフォルト設定でサーバのローカルタイムに変換されていたケースがある。MySQL / MariaDB の接続 URL には serverTimezone=UTC を明示すること。
FAQ
あります。クラウドのサーバやコンテナは UTC がデフォルトのことが多く、JVM のデフォルトタイムゾーン頼みのコードは実行環境が変わった瞬間に9時間ずれます。
UTC に統一して保存し、表示時にユーザーのタイムゾーンへ変換するのが定石です。JDBC ドライバの接続設定でもタイムゾーンを明示しておくと安全です。
サマータイムの規則ごと扱いたいなら ZonedDateTime、+09:00 のような固定オフセットでの保存・データ交換には OffsetDateTime が向きます。