日付時間API(java.time: LocalDate / LocalDateTime / Duration) — 日時処理
日時は「正しく扱えるか」で品質が決まります。java.time(Java 8以降)は不変で安全、直感的なクラス設計で、旧来の Date/Calendar の弱点を解決します。まずは LocalDate/LocalDateTime/Duration に絞って、実務で使う定番をかみ砕いて解説します。
基本の考え方(不変・役割分担)
- 不変(immutable): 値は変更されず、加算・減算は新しいインスタンスとして返る。スレッドセーフで扱いやすい。
- 役割分担:
- LocalDate: 年月日(タイムゾーンなし)
- LocalDateTime: 年月日+時分秒(タイムゾーンなし)
- Duration: 時間の差(秒・ナノ秒単位)。日付差は Period を使うが、今回は Duration に集中。
- 旧APIとの違い: Date/Calendar は可変で直感的でない。今は java.time を基本にするのが推奨。
LocalDate(年月日)
生成・取得・加減算
import java.time.*;
var today = LocalDate.now(); // 今日
var d1 = LocalDate.of(2025, 12, 7); // 指定日
var nextWeek = today.plusWeeks(1); // 1週間後
var lastMonth = today.minusMonths(1); // 1ヶ月前
int year = today.getYear();
int month = today.getMonthValue();
int day = today.getDayOfMonth();
Java- ポイント: 可変ではなく、新しい値が返る。祝日計算などは別ライブラリかルール実装で対応。
比較・判定
boolean after = d1.isAfter(today);
boolean before = d1.isBefore(today);
DayOfWeek dow = today.getDayOfWeek(); // 月曜〜日曜
JavaLocalDateTime(年月日+時分秒)
生成・取得・加減算
import java.time.*;
var now = LocalDateTime.now(); // 現在の日時
var dt = LocalDateTime.of(2025, 12, 7, 16, 30, 0); // 指定日時
var plus = dt.plusHours(2).minusMinutes(15); // 2時間加算→15分減算
int hour = now.getHour();
int minute = now.getMinute();
int second = now.getSecond();
Java- タイムゾーン注意: LocalDateTime はタイムゾーンを持たない。ゾーン付きが必要なら ZonedDateTime を使う。
ゾーン適用とInstant変換
var tokyo = ZoneId.of("Asia/Tokyo");
var zoned = dt.atZone(tokyo); // LocalDateTime にゾーン適用
var instant = zoned.toInstant(); // UTC系の瞬間に変換
Java- 用途: DBや外部APIとやり取りする場合は Instant や ZonedDateTime を適切に使い分ける。
Duration(時間差:秒・ナノ秒)
生成・差分・加減算
import java.time.*;
var start = LocalDateTime.of(2025, 12, 7, 9, 0);
var end = LocalDateTime.of(2025, 12, 7, 17, 30);
var dur = Duration.between(start, end); // 差分(8時間30分)
long seconds = dur.getSeconds(); // トータル秒
var plus30m = dur.plusMinutes(30); // 30分追加
Java- 注意: 日月年の差は Period。時間量やタイマーには Duration が適切。
フォーマットとパース(DateTimeFormatter)
文字列に整形
import java.time.format.DateTimeFormatter;
var fmtDate = DateTimeFormatter.ofPattern("yyyy-MM-dd");
var fmtDateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String s1 = today.format(fmtDate); // 2025-12-07
String s2 = now.format(fmtDateTime); // 2025-12-07 16:30:00(例)
Java文字列から読み込み
var parsedDate = LocalDate.parse("2025-12-07", fmtDate);
var parsedDt = LocalDateTime.parse("2025-12-07 16:30:00", fmtDateTime);
Java- 定番フォーマット: ISO系(toString 既定)を基本に、表示だけ独自フォーマット。パース例外への対処も忘れずに。
例題で身につける
例題1: 締切日までの日数(LocalDate + Period)
import java.time.*;
var deadline = LocalDate.of(2025, 12, 31);
var today = LocalDate.now();
var daysLeft = deadline.toEpochDay() - today.toEpochDay(); // シンプルに日数差
System.out.println("残り日数: " + daysLeft);
Java- 解説: 日数差なら EpochDay の差分が簡単。月年をまたぐ「暦の差」計算は Period でも可。
例題2: 稼働時間の集計(LocalDateTime + Duration)
import java.time.*;
record Shift(LocalDateTime start, LocalDateTime end) {}
var s1 = new Shift(LocalDateTime.of(2025,12,7,9,0), LocalDateTime.of(2025,12,7,12,0));
var s2 = new Shift(LocalDateTime.of(2025,12,7,13,0), LocalDateTime.of(2025,12,7,17,30));
var total = Duration.between(s1.start(), s1.end())
.plus(Duration.between(s2.start(), s2.end()));
System.out.println("合計分: " + total.toMinutes()); // 450分
Java- 解説: 区間ごとに Duration を取り、合計するのが定石。ナノ秒精度で安全に扱える。
例題3: タイムゾーン付き表示(ZonedDateTime)
import java.time.*;
import java.time.format.*;
var utc = Instant.parse("2025-12-07T07:30:00Z"); // UTCの瞬間
var tokyoTime = utc.atZone(ZoneId.of("Asia/Tokyo"));
var fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss VV");
System.out.println(tokyoTime.format(fmt)); // 2025-12-07 16:30:00 Asia/Tokyo
Java- 解説: 表示はゾーンを適用してからフォーマット。夏時間の存在する地域では ZonedDateTime が不可欠。
すぐ使えるテンプレート
- 今日・今・加減算
var today = LocalDate.now();
var now = LocalDateTime.now();
var nextMonth = today.plusMonths(1);
var in2h = now.plusHours(2);
Java- 差分(Duration)
var dur = Duration.between(start, end);
long minutes = dur.toMinutes();
Java- フォーマット/パース
var fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
String s = now.format(fmt);
var parsed = LocalDateTime.parse(s, fmt);
Java- ゾーン適用
var zoned = LocalDateTime.now().atZone(ZoneId.of("Asia/Tokyo"));
Java実務のコツと落とし穴
- タイムゾーンを意識: 保存は UTC(Instant)、表示はユーザのゾーン。LocalDateTime は「ゾーンなしの壁時計」。
- 不変を前提に組み立てる: 加減算は新しい値。副作用を避けた処理が書ける。
- フォーマットは集中管理: DateTimeFormatter を定数化して再利用、パース例外に備える。
- 旧APIの混在を避ける: Date/Calendar はラップや変換に限定し、java.time を中心に。
まとめ
- LocalDate / LocalDateTime / Duration を押さえれば、ほとんどの業務日時は安全に扱える。
- 不変・直感的なAPIで、加減算・差分・フォーマット・ゾーン適用がシンプル。
- 保存と表示を分ける設計(UTC保存+ゾーン表示)で、国際化や夏時間にも対応しやすい。
