OffsetDateTime を一言でいうと
OffsetDateTime は
「日付+時刻 + UTC からのズレ量(オフセット)」
をセットで表すクラスです。
ZonedDateTime が「タイムゾーン(Asia/Tokyo など)」を持つのに対して、OffsetDateTime は「+09:00」「-05:00」といった“数値としてのズレ”だけを持ちます。
「タイムゾーンの名前まではいらないけど、“UTC から何時間ずれているか”はちゃんと持ちたい」
という場面で使うのが、OffsetDateTime です。
OffsetDateTime が表しているもの
「カレンダー上の日時」+「UTC からのオフセット」
まず、コードで形を見てみます。
OffsetDateTime odt =
OffsetDateTime.of(2025, 1, 18, 10, 0, 0, 0, ZoneOffset.ofHours(9));
System.out.println(odt); // 2025-01-18T10:00+09:00
Javaここには、次の情報が入っています。
年・月・日・時・分・秒:2025-01-18 10:00
オフセット:+09:00(UTC より 9 時間進んでいる)
この 2 つがそろうと、「世界共通の瞬間(Instant)」に一意に変換できます。
Instant instant = odt.toInstant();
System.out.println(instant); // 2025-01-18T01:00:00Z など
JavaZ は「UTC」を表します。
「+09:00 の 10:00」は「UTC の 1:00」として表現される、という対応関係です。
ZonedDateTime との違い
ZonedDateTime は「場所のルール」まで含む
ZonedDateTime は、こういう形でした。
ZonedDateTime zdt =
ZonedDateTime.of(2025, 1, 18, 10, 0, 0, 0, ZoneId.of("Asia/Tokyo"));
System.out.println(zdt); // 2025-01-18T10:00+09:00[Asia/Tokyo]
JavaOffsetDateTime との違いは、末尾の [Asia/Tokyo] の部分です。
ZoneId(Asia/Tokyo, America/New_York など)は、
「夏時間があるか」「いつオフセットが変わるか」といった“場所ごとのルール”を持っています。
一方 OffsetDateTime は、単に「+09:00」「-05:00」といった“数値としてのズレ”だけを持ちます。
「どこの国・どの都市か」は分かりません。
いつ OffsetDateTime を選ぶか
ざっくり言うと、こういう感覚です。
「ビジネス的に“どこのタイムゾーンか”が意味を持つ」
→ ZonedDateTime(例:ユーザーの居住地、会議の場所など)
「とにかく“UTC からのズレ”だけ分かればよい」
→ OffsetDateTime(例:API のタイムスタンプ、ログの時刻など)
特に、外部システムとのやり取りで ISO-8601 形式の日時文字列2025-01-18T10:00:00+09:00
をそのまま扱いたいとき、OffsetDateTime は相性が良いです。
典型的な使いどころ:API・ログ・メッセージ
ISO-8601 の文字列との相性がいい
例えば、REST API でこんな JSON が飛んでくるとします。
{
"createdAt": "2025-01-18T10:00:00+09:00"
}
Javaこの createdAt を Java で受けるとき、OffsetDateTime にマッピングすると自然です。
OffsetDateTime createdAt = OffsetDateTime.parse("2025-01-18T10:00:00+09:00");
System.out.println(createdAt); // 2025-01-18T10:00+09:00
Javaこの時点で、
「カレンダー上の日時」
「UTC からのオフセット」
の両方がきちんと保持されます。
DB に保存するときは Instant に変換してもいいし、
文字列のまま保存してもいい、という柔軟さがあります。
ログのタイムスタンプとして
ログに時刻を出すときも、OffsetDateTime はよく使われます。
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
System.out.println(now); // 2025-01-18T01:23:45Z
JavaZ は +00:00(UTC)を意味します。
「常に UTC でログを出す」と決めておけば、
サーバーのタイムゾーン設定に左右されず、解析もしやすくなります。
OffsetDateTime と Instant の関係
Instant は「純粋な瞬間」、OffsetDateTime は「瞬間+見え方」
Instant は「1970-01-01T00:00:00Z からの経過時間」という、純粋に機械的な時刻です。
Instant now = Instant.now();
JavaOffsetDateTime は、その瞬間を「あるオフセットで見たときのカレンダー上の日時」です。
OffsetDateTime odt = now.atOffset(ZoneOffset.ofHours(9));
System.out.println(odt); // 2025-01-18T10:00+09:00 など
Java逆に、OffsetDateTime から Instant に戻すこともできます。
Instant instant = odt.toInstant();
Java設計としては、
保存・内部処理:Instant(+必要ならオフセットやタイムゾーン)
外部とのやり取り・表示:OffsetDateTime / ZonedDateTime
という役割分担を意識しておくと、コードが整理しやすくなります。
OffsetDateTime を使うときの設計ポイント
「場所の意味」が必要かどうかを自分に問う
OffsetDateTime を選ぶ前に、必ず自分にこう聞いてみてください。
「この日時に、“どこのタイムゾーンか”という意味はあるか?」
あるなら ZonedDateTime。
ないなら OffsetDateTime か Instant。
例えば、
ユーザーの「居住地の現在時刻」 → ZonedDateTime(Asia/Tokyo などが意味を持つ)
API の「処理開始時刻」 → OffsetDateTime か Instant(+09:00 かどうかだけ分かれば十分)
という感じです。
アプリ内部ではできるだけシンプルに
アプリ内部のロジックでは、
「全部 Instant にそろえる」
「ユーザーごとの ZoneId を持っておき、必要なときに ZonedDateTime に変換する」
といった方針を決めておくと、タイムゾーン・オフセットの混乱が減ります。
OffsetDateTime は主に「外との境界」で使う。
中では Instant / ZonedDateTime / LocalDateTime に寄せる。
こういう“役割の分担”を意識しておくと、設計がかなりスッキリします。
まとめ:OffsetDateTime を自分の言葉で説明するなら
あなたの言葉で OffsetDateTime を説明するなら、こうです。
「OffsetDateTime は、“日付+時刻+UTC からのズレ量(+09:00 など)”をセットで持つクラス。
タイムゾーン名まではいらないけれど、“世界のどの瞬間か”を一意に決めたいときに使う。
API やログで ISO-8601 形式の 2025-01-18T10:00:00+09:00 をそのまま扱うのに向いていて、
内部では Instant や ZonedDateTime と相互変換しながら、“場所の意味が必要かどうか”で使い分けるのが設計のポイント。」
