はじめに#
日付と時刻の処理は、あらゆるアプリケーションで必要となる基本的な機能です。予約システムの日付管理、ログのタイムスタンプ、有効期限の計算など、日常的に日付・時刻を扱う場面は数多くあります。
Java 8で導入された**Date and Time API(java.timeパッケージ)**は、従来のjava.util.Dateやjava.util.Calendarの問題点を解消し、直感的で安全な日付・時刻操作を実現します。
この記事では、java.timeパッケージの主要クラスであるLocalDate・LocalTime・LocalDateTimeの基本的な使い方から、日付計算、フォーマット、タイムゾーン対応、そしてPeriod・Durationによる期間表現まで、実践的なコード例とともに解説します。
旧APIの問題点とjava.timeパッケージ#
Java 8より前は、java.util.Dateとjava.util.Calendarを使って日付・時刻を扱っていました。しかし、これらのAPIには多くの設計上の問題がありました。
旧APIの問題点#
| 問題点 |
説明 |
| ミュータブル(可変) |
Dateオブジェクトの値を後から変更でき、バグの温床になる |
| 月が0始まり |
1月が0、12月が11と直感に反する設計 |
| スレッドセーフでない |
マルチスレッド環境でSimpleDateFormatが安全に使えない |
| APIの一貫性がない |
DateとCalendarで役割が重複し、使い分けが困難 |
| タイムゾーン処理が複雑 |
タイムゾーンを考慮した計算が非常に煩雑 |
旧APIの問題を示す典型的な例を見てみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import java.util.Date;
import java.util.Calendar;
public class OldApiProblems {
public static void main(String[] args) {
// 問題1: ミュータブルなDateオブジェクト
Date date = new Date();
System.out.println("変更前: " + date);
date.setTime(0); // 値を変更できてしまう
System.out.println("変更後: " + date); // 1970-01-01になる
// 問題2: 月が0始まり(直感に反する)
Calendar cal = Calendar.getInstance();
cal.set(2026, 0, 15); // 0は1月を意味する(紛らわしい)
System.out.println("設定した月: " + cal.get(Calendar.MONTH)); // 0
// 問題3: Dateは日付と時刻の両方を持つ(日付だけを扱えない)
Date today = new Date(); // 常に時刻情報も含まれる
}
}
|
java.timeパッケージの登場#
Java 8で導入されたjava.timeパッケージは、Joda-Timeライブラリの設計を参考に、JSR 310として標準化されました。主な特徴は以下のとおりです。
| 特徴 |
説明 |
| イミュータブル(不変) |
すべてのクラスがイミュータブルで、スレッドセーフ |
| 明確な責務分離 |
日付のみ、時刻のみ、日付時刻など目的に応じたクラスが存在 |
| 直感的なAPI |
月は1から始まり、メソッド名も分かりやすい |
| タイムゾーン対応 |
ZonedDateTimeでタイムゾーンを明示的に扱える |
| null安全 |
nullを渡すとNullPointerExceptionが発生し、早期に問題を検出 |
java.timeパッケージの主要クラス#
classDiagram
class LocalDate {
+int year
+int month
+int dayOfMonth
+now() LocalDate
+of(year, month, day) LocalDate
+plusDays(days) LocalDate
}
class LocalTime {
+int hour
+int minute
+int second
+now() LocalTime
+of(hour, minute) LocalTime
}
class LocalDateTime {
+LocalDate date
+LocalTime time
+now() LocalDateTime
+of(date, time) LocalDateTime
}
class ZonedDateTime {
+LocalDateTime dateTime
+ZoneId zone
+now(zone) ZonedDateTime
+withZoneSameInstant(zone) ZonedDateTime
}
LocalDate --> LocalDateTime : 結合
LocalTime --> LocalDateTime : 結合
LocalDateTime --> ZonedDateTime : タイムゾーン付加
| クラス |
用途 |
例 |
LocalDate |
日付のみ(時刻なし) |
誕生日、祝日 |
LocalTime |
時刻のみ(日付なし) |
営業開始時間 |
LocalDateTime |
日付と時刻 |
イベント開始日時 |
ZonedDateTime |
日付・時刻・タイムゾーン |
国際会議の日時 |
Instant |
タイムスタンプ(エポック秒) |
ログ記録、DB保存 |
Period |
日付ベースの期間 |
「2年3ヶ月」 |
Duration |
時間ベースの期間 |
「2時間30分」 |
LocalDate・LocalTime・LocalDateTimeの使い方#
最も頻繁に使用する3つのクラスについて、生成方法と基本操作を解説します。
LocalDate - 日付のみを扱う#
LocalDateはタイムゾーンを持たない日付を表現します。誕生日、祝日、期限日など、時刻情報が不要な場面で使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
import java.time.LocalDate;
import java.time.Month;
import java.time.DayOfWeek;
public class LocalDateExample {
public static void main(String[] args) {
// 現在の日付を取得
LocalDate today = LocalDate.now();
System.out.println("今日: " + today); // 2026-01-03
// 特定の日付を生成
LocalDate date1 = LocalDate.of(2026, 1, 15);
LocalDate date2 = LocalDate.of(2026, Month.JANUARY, 15); // Monthを使用
System.out.println("指定日: " + date1); // 2026-01-15
// 文字列からパース
LocalDate parsed = LocalDate.parse("2026-03-20");
System.out.println("パース結果: " + parsed);
// 日付の各要素を取得
System.out.println("年: " + today.getYear());
System.out.println("月: " + today.getMonthValue()); // 1〜12
System.out.println("月(Enum): " + today.getMonth()); // JANUARY
System.out.println("日: " + today.getDayOfMonth());
System.out.println("曜日: " + today.getDayOfWeek()); // FRIDAY
System.out.println("年の何日目: " + today.getDayOfYear());
// うるう年の判定
System.out.println("2024年はうるう年: " + LocalDate.of(2024, 1, 1).isLeapYear()); // true
System.out.println("2025年はうるう年: " + LocalDate.of(2025, 1, 1).isLeapYear()); // false
// 日付の比較
LocalDate future = LocalDate.of(2026, 12, 31);
System.out.println("未来の日付か: " + future.isAfter(today)); // true
System.out.println("過去の日付か: " + future.isBefore(today)); // false
System.out.println("同じ日付か: " + today.isEqual(LocalDate.now())); // true
}
}
|
LocalTime - 時刻のみを扱う#
LocalTimeはタイムゾーンを持たない時刻を表現します。営業時間、アラーム設定など、日付情報が不要な場面で使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
import java.time.LocalTime;
public class LocalTimeExample {
public static void main(String[] args) {
// 現在の時刻を取得
LocalTime now = LocalTime.now();
System.out.println("現在時刻: " + now); // 14:30:45.123456789
// 特定の時刻を生成
LocalTime time1 = LocalTime.of(9, 30); // 09:30
LocalTime time2 = LocalTime.of(14, 30, 45); // 14:30:45
LocalTime time3 = LocalTime.of(14, 30, 45, 123456789); // ナノ秒まで指定
// 文字列からパース
LocalTime parsed = LocalTime.parse("10:15:30");
System.out.println("パース結果: " + parsed);
// 時刻の各要素を取得
System.out.println("時: " + now.getHour());
System.out.println("分: " + now.getMinute());
System.out.println("秒: " + now.getSecond());
System.out.println("ナノ秒: " + now.getNano());
// 定数
System.out.println("真夜中: " + LocalTime.MIDNIGHT); // 00:00
System.out.println("正午: " + LocalTime.NOON); // 12:00
System.out.println("最小値: " + LocalTime.MIN); // 00:00
System.out.println("最大値: " + LocalTime.MAX); // 23:59:59.999999999
// 時刻の比較
LocalTime morning = LocalTime.of(9, 0);
LocalTime evening = LocalTime.of(18, 0);
System.out.println("営業時間内か: " +
(now.isAfter(morning) && now.isBefore(evening)));
}
}
|
LocalDateTime - 日付と時刻を扱う#
LocalDateTimeは日付と時刻の両方を持ちますが、タイムゾーン情報は持ちません。イベントの開始日時、予約日時など、ローカルな日時を扱う場面で使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
public class LocalDateTimeExample {
public static void main(String[] args) {
// 現在の日時を取得
LocalDateTime now = LocalDateTime.now();
System.out.println("現在日時: " + now); // 2026-01-03T14:30:45.123
// 特定の日時を生成
LocalDateTime dt1 = LocalDateTime.of(2026, 1, 15, 10, 30);
LocalDateTime dt2 = LocalDateTime.of(2026, 1, 15, 10, 30, 45);
System.out.println("指定日時: " + dt1); // 2026-01-15T10:30
// LocalDateとLocalTimeから生成
LocalDate date = LocalDate.of(2026, 3, 20);
LocalTime time = LocalTime.of(14, 0);
LocalDateTime dt3 = LocalDateTime.of(date, time);
LocalDateTime dt4 = date.atTime(time); // atTimeメソッドでも可
LocalDateTime dt5 = time.atDate(date); // atDateメソッドでも可
// 文字列からパース(ISO 8601形式)
LocalDateTime parsed = LocalDateTime.parse("2026-06-15T09:30:00");
System.out.println("パース結果: " + parsed);
// 日付部分・時刻部分の取得
LocalDate datePart = now.toLocalDate();
LocalTime timePart = now.toLocalTime();
System.out.println("日付部分: " + datePart);
System.out.println("時刻部分: " + timePart);
// 個別の要素取得
System.out.println("年: " + now.getYear());
System.out.println("月: " + now.getMonthValue());
System.out.println("日: " + now.getDayOfMonth());
System.out.println("時: " + now.getHour());
System.out.println("分: " + now.getMinute());
}
}
|
日付の計算(plus、minus、with)#
java.timeの日付・時刻クラスはイミュータブルです。計算を行うと、元のオブジェクトは変更されず、新しいオブジェクトが返されます。
plusとminusによる加減算#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
public class DateCalculation {
public static void main(String[] args) {
LocalDate today = LocalDate.of(2026, 1, 15);
// 日付の加算
LocalDate tomorrow = today.plusDays(1);
LocalDate nextWeek = today.plusWeeks(1);
LocalDate nextMonth = today.plusMonths(1);
LocalDate nextYear = today.plusYears(1);
System.out.println("明日: " + tomorrow); // 2026-01-16
System.out.println("1週間後: " + nextWeek); // 2026-01-22
System.out.println("1ヶ月後: " + nextMonth); // 2026-02-15
System.out.println("1年後: " + nextYear); // 2027-01-15
// 日付の減算
LocalDate yesterday = today.minusDays(1);
LocalDate lastMonth = today.minusMonths(1);
System.out.println("昨日: " + yesterday); // 2026-01-14
System.out.println("1ヶ月前: " + lastMonth); // 2025-12-15
// ChronoUnitを使用した加減算
LocalDate future = today.plus(100, ChronoUnit.DAYS);
System.out.println("100日後: " + future); // 2026-04-25
// LocalDateTimeでの時刻計算
LocalDateTime now = LocalDateTime.of(2026, 1, 15, 10, 30);
LocalDateTime later = now.plusHours(3).plusMinutes(45);
System.out.println("3時間45分後: " + later); // 2026-01-15T14:15
}
}
|
withによる特定フィールドの変更#
withメソッドを使うと、特定のフィールドだけを変更した新しいオブジェクトを取得できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.DayOfWeek;
import java.time.temporal.TemporalAdjusters;
public class DateWithMethod {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2026, 1, 15);
// 特定のフィールドを変更
LocalDate newYear = date.withYear(2027);
LocalDate newMonth = date.withMonth(6);
LocalDate newDay = date.withDayOfMonth(1);
System.out.println("年を変更: " + newYear); // 2027-01-15
System.out.println("月を変更: " + newMonth); // 2026-06-15
System.out.println("日を変更: " + newDay); // 2026-01-01
// TemporalAdjustersによる高度な調整
LocalDate firstDayOfMonth = date.with(TemporalAdjusters.firstDayOfMonth());
LocalDate lastDayOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());
LocalDate firstDayOfYear = date.with(TemporalAdjusters.firstDayOfYear());
LocalDate lastDayOfYear = date.with(TemporalAdjusters.lastDayOfYear());
System.out.println("月初: " + firstDayOfMonth); // 2026-01-01
System.out.println("月末: " + lastDayOfMonth); // 2026-01-31
System.out.println("年初: " + firstDayOfYear); // 2026-01-01
System.out.println("年末: " + lastDayOfYear); // 2026-12-31
// 次の特定曜日
LocalDate nextFriday = date.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
LocalDate nextOrSameFriday = date.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY));
System.out.println("次の金曜日: " + nextFriday); // 2026-01-16
// 月の最初・最後の特定曜日
LocalDate firstMonday = date.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
LocalDate lastFriday = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY));
System.out.println("月の最初の月曜日: " + firstMonday); // 2026-01-05
System.out.println("月の最後の金曜日: " + lastFriday); // 2026-01-30
}
}
|
日付間の計算#
2つの日付間の差を計算することもできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class DateBetween {
public static void main(String[] args) {
LocalDate startDate = LocalDate.of(2026, 1, 1);
LocalDate endDate = LocalDate.of(2026, 12, 31);
// 日数の差を計算
long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);
long monthsBetween = ChronoUnit.MONTHS.between(startDate, endDate);
long yearsBetween = ChronoUnit.YEARS.between(startDate, endDate);
System.out.println("日数差: " + daysBetween); // 364
System.out.println("月数差: " + monthsBetween); // 11
System.out.println("年数差: " + yearsBetween); // 0
// 年齢計算の例
LocalDate birthDate = LocalDate.of(1990, 5, 15);
LocalDate today = LocalDate.of(2026, 1, 3);
long age = ChronoUnit.YEARS.between(birthDate, today);
System.out.println("年齢: " + age + "歳"); // 35歳
}
}
|
日付・時刻を任意の形式の文字列に変換するには、DateTimeFormatterを使用します。
定義済みフォーマッタ#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DateTimeFormatterExample {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.of(2026, 1, 15, 14, 30, 45);
// 定義済みフォーマッタ
System.out.println("ISO_LOCAL_DATE: " +
now.format(DateTimeFormatter.ISO_LOCAL_DATE)); // 2026-01-15
System.out.println("ISO_LOCAL_TIME: " +
now.format(DateTimeFormatter.ISO_LOCAL_TIME)); // 14:30:45
System.out.println("ISO_LOCAL_DATE_TIME: " +
now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); // 2026-01-15T14:30:45
}
}
|
カスタムパターン#
DateTimeFormatter.ofPattern()を使用して、独自のフォーマットを定義できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class CustomFormatExample {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.of(2026, 1, 15, 14, 30, 45);
// カスタムパターン
DateTimeFormatter f1 = DateTimeFormatter.ofPattern("yyyy/MM/dd");
DateTimeFormatter f2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
DateTimeFormatter f3 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DateTimeFormatter f4 = DateTimeFormatter.ofPattern("yy/M/d H:m");
System.out.println(now.format(f1)); // 2026/01/15
System.out.println(now.format(f2)); // 2026年01月15日
System.out.println(now.format(f3)); // 2026-01-15 14:30:45
System.out.println(now.format(f4)); // 26/1/15 14:30
// ロケールを指定したフォーマット
DateTimeFormatter f5 = DateTimeFormatter.ofPattern("yyyy年M月d日(E)", Locale.JAPANESE);
DateTimeFormatter f6 = DateTimeFormatter.ofPattern("EEEE, MMMM d, yyyy", Locale.US);
System.out.println(now.format(f5)); // 2026年1月15日(木)
System.out.println(now.format(f6)); // Thursday, January 15, 2026
}
}
|
主要なパターン文字#
| パターン |
意味 |
例 |
| yyyy |
年(4桁) |
2026 |
| yy |
年(2桁) |
26 |
| MM |
月(2桁、ゼロ埋め) |
01 |
| M |
月(ゼロ埋めなし) |
1 |
| dd |
日(2桁、ゼロ埋め) |
05 |
| d |
日(ゼロ埋めなし) |
5 |
| HH |
時(24時間制、2桁) |
14 |
| hh |
時(12時間制、2桁) |
02 |
| mm |
分(2桁) |
30 |
| ss |
秒(2桁) |
45 |
| E |
曜日(短縮形) |
木 |
| EEEE |
曜日(完全形) |
木曜日 |
| a |
午前/午後 |
午後 |
文字列からのパース#
文字列を日付・時刻オブジェクトに変換(パース)する方法を解説します。
基本的なパース#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
public class BasicParseExample {
public static void main(String[] args) {
// ISO 8601形式は直接パース可能
LocalDate date = LocalDate.parse("2026-01-15");
LocalTime time = LocalTime.parse("14:30:45");
LocalDateTime dateTime = LocalDateTime.parse("2026-01-15T14:30:45");
System.out.println("日付: " + date); // 2026-01-15
System.out.println("時刻: " + time); // 14:30:45
System.out.println("日時: " + dateTime); // 2026-01-15T14:30:45
}
}
|
カスタムパターンでのパース#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class CustomParseExample {
public static void main(String[] args) {
// カスタムパターンでパース
DateTimeFormatter f1 = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate date1 = LocalDate.parse("2026/01/15", f1);
System.out.println("パース結果: " + date1); // 2026-01-15
DateTimeFormatter f2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
LocalDate date2 = LocalDate.parse("2026年01月15日", f2);
System.out.println("パース結果: " + date2); // 2026-01-15
DateTimeFormatter f3 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dateTime = LocalDateTime.parse("2026-01-15 14:30:45", f3);
System.out.println("パース結果: " + dateTime); // 2026-01-15T14:30:45
}
}
|
パースエラーの処理#
無効な日付文字列をパースするとDateTimeParseExceptionが発生します。適切にエラーハンドリングを行いましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Optional;
public class ParseErrorHandling {
public static void main(String[] args) {
// 例外処理でエラーをハンドリング
String input = "2026/13/45"; // 無効な日付
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
try {
LocalDate date = LocalDate.parse(input, formatter);
System.out.println("パース成功: " + date);
} catch (DateTimeParseException e) {
System.out.println("パースエラー: " + e.getMessage());
}
// Optionalを使ったエラーハンドリング
Optional<LocalDate> result = safeParse("2026/01/15", formatter);
result.ifPresentOrElse(
date -> System.out.println("パース成功: " + date),
() -> System.out.println("パース失敗")
);
}
public static Optional<LocalDate> safeParse(String text, DateTimeFormatter formatter) {
try {
return Optional.of(LocalDate.parse(text, formatter));
} catch (DateTimeParseException e) {
return Optional.empty();
}
}
}
|
厳密なパースとレニエントなパース#
デフォルトではスマートモードでパースが行われますが、より厳密またはレニエント(寛容)なパースも設定できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.ResolverStyle;
public class ResolverStyleExample {
public static void main(String[] args) {
// 2月30日という無効な日付
String input = "2026-02-30";
// SMARTモード(デフォルト): 妥当な値に調整
DateTimeFormatter smart = DateTimeFormatter.ofPattern("yyyy-MM-dd")
.withResolverStyle(ResolverStyle.SMART);
// STRICTモード: 厳密にチェック
DateTimeFormatter strict = DateTimeFormatter.ofPattern("uuuu-MM-dd")
.withResolverStyle(ResolverStyle.STRICT);
try {
LocalDate smartDate = LocalDate.parse(input, smart);
System.out.println("SMARTモード: " + smartDate); // 2026-02-28
} catch (Exception e) {
System.out.println("SMARTモードでエラー");
}
try {
LocalDate strictDate = LocalDate.parse(input, strict);
System.out.println("STRICTモード: " + strictDate);
} catch (Exception e) {
System.out.println("STRICTモードでエラー: " + e.getMessage()); // エラー発生
}
}
}
|
ZonedDateTimeとタイムゾーン#
グローバルなアプリケーションでは、タイムゾーンを考慮した日時処理が必要です。ZonedDateTimeはタイムゾーン情報を含む完全な日時を表現します。
ZoneIdの取得と一覧#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Set;
public class ZoneIdExample {
public static void main(String[] args) {
// システムのデフォルトタイムゾーン
ZoneId defaultZone = ZoneId.systemDefault();
System.out.println("デフォルト: " + defaultZone); // Asia/Tokyo
// タイムゾーンIDから取得
ZoneId tokyo = ZoneId.of("Asia/Tokyo");
ZoneId newYork = ZoneId.of("America/New_York");
ZoneId london = ZoneId.of("Europe/London");
ZoneId utc = ZoneId.of("UTC");
// オフセットから取得
ZoneId plus9 = ZoneId.of("+09:00");
ZoneId offset = ZoneOffset.ofHours(9);
// 利用可能なタイムゾーンID一覧
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
System.out.println("タイムゾーン数: " + zoneIds.size());
// 日本関連のタイムゾーンを表示
zoneIds.stream()
.filter(id -> id.contains("Tokyo") || id.contains("Japan"))
.forEach(System.out::println);
}
}
|
ZonedDateTimeの生成と操作#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
public class ZonedDateTimeExample {
public static void main(String[] args) {
// 現在のタイムゾーンでの現在日時
ZonedDateTime nowDefault = ZonedDateTime.now();
System.out.println("現在(デフォルト): " + nowDefault);
// 特定のタイムゾーンでの現在日時
ZonedDateTime nowTokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
ZonedDateTime nowNewYork = ZonedDateTime.now(ZoneId.of("America/New_York"));
ZonedDateTime nowUtc = ZonedDateTime.now(ZoneOffset.UTC);
System.out.println("東京: " + nowTokyo);
System.out.println("ニューヨーク: " + nowNewYork);
System.out.println("UTC: " + nowUtc);
// 特定の日時とタイムゾーンで生成
ZonedDateTime meeting = ZonedDateTime.of(
2026, 1, 15, 10, 0, 0, 0,
ZoneId.of("Asia/Tokyo")
);
System.out.println("会議: " + meeting);
// LocalDateTimeにタイムゾーンを付与
LocalDateTime local = LocalDateTime.of(2026, 1, 15, 10, 0);
ZonedDateTime zoned = local.atZone(ZoneId.of("Asia/Tokyo"));
System.out.println("タイムゾーン付与: " + zoned);
}
}
|
タイムゾーン間の変換#
国際的なアプリケーションでは、異なるタイムゾーン間で日時を変換する必要があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
public class TimeZoneConversion {
public static void main(String[] args) {
// 東京で2026年1月15日10:00の会議
ZonedDateTime tokyoMeeting = ZonedDateTime.of(
2026, 1, 15, 10, 0, 0, 0,
ZoneId.of("Asia/Tokyo")
);
// 同じ瞬間を異なるタイムゾーンで表現
ZonedDateTime newYorkTime = tokyoMeeting.withZoneSameInstant(ZoneId.of("America/New_York"));
ZonedDateTime londonTime = tokyoMeeting.withZoneSameInstant(ZoneId.of("Europe/London"));
ZonedDateTime utcTime = tokyoMeeting.withZoneSameInstant(ZoneId.of("UTC"));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm z");
System.out.println("東京: " + tokyoMeeting.format(formatter));
// 2026-01-15 10:00 JST
System.out.println("ニューヨーク: " + newYorkTime.format(formatter));
// 2026-01-14 20:00 EST
System.out.println("ロンドン: " + londonTime.format(formatter));
// 2026-01-15 01:00 GMT
System.out.println("UTC: " + utcTime.format(formatter));
// 2026-01-15 01:00 UTC
// withZoneSameLocalとの違い
// withZoneSameInstant: 同じ瞬間を維持(時刻が変わる)
// withZoneSameLocal: 同じローカル時刻を維持(瞬間が変わる)
ZonedDateTime sameLocal = tokyoMeeting.withZoneSameLocal(ZoneId.of("America/New_York"));
System.out.println("ローカル時刻維持: " + sameLocal.format(formatter));
// 2026-01-15 10:00 EST(時刻は同じだが、異なる瞬間を指す)
}
}
|
InstantとZonedDateTimeの相互変換#
Instantはタイムラインの一点を表すタイムスタンプで、タイムゾーンに依存しません。データベースへの保存やシステム間の通信に適しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.LocalDateTime;
public class InstantConversion {
public static void main(String[] args) {
// 現在のInstant
Instant now = Instant.now();
System.out.println("Instant: " + now); // 2026-01-03T05:30:45.123Z
// エポック秒からInstantを生成
Instant fromEpoch = Instant.ofEpochSecond(1735884645L);
Instant fromMillis = Instant.ofEpochMilli(1735884645000L);
// InstantからZonedDateTimeへ変換
ZonedDateTime tokyoTime = now.atZone(ZoneId.of("Asia/Tokyo"));
ZonedDateTime newYorkTime = now.atZone(ZoneId.of("America/New_York"));
System.out.println("東京: " + tokyoTime);
System.out.println("ニューヨーク: " + newYorkTime);
// ZonedDateTimeからInstantへ変換
Instant instant = tokyoTime.toInstant();
System.out.println("Instantに戻す: " + instant);
// エポック秒の取得
long epochSecond = now.getEpochSecond();
long epochMilli = now.toEpochMilli();
System.out.println("エポック秒: " + epochSecond);
System.out.println("エポックミリ秒: " + epochMilli);
}
}
|
夏時間(DST)の考慮#
タイムゾーンによっては夏時間(Daylight Saving Time)が適用されます。ZonedDateTimeはこれを自動的に処理します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.Month;
public class DaylightSavingTime {
public static void main(String[] args) {
ZoneId newYork = ZoneId.of("America/New_York");
// 冬時間(EST: UTC-5)
ZonedDateTime winter = ZonedDateTime.of(2026, 1, 15, 12, 0, 0, 0, newYork);
System.out.println("冬: " + winter); // 2026-01-15T12:00-05:00[America/New_York]
// 夏時間(EDT: UTC-4)
ZonedDateTime summer = ZonedDateTime.of(2026, 7, 15, 12, 0, 0, 0, newYork);
System.out.println("夏: " + summer); // 2026-07-15T12:00-04:00[America/New_York]
// オフセットの違いを確認
System.out.println("冬のオフセット: " + winter.getOffset()); // -05:00
System.out.println("夏のオフセット: " + summer.getOffset()); // -04:00
// 夏時間開始時の動作(時計が1時間進む)
// 2026年3月8日 2:00 AMに夏時間開始(アメリカ)
ZonedDateTime beforeDST = ZonedDateTime.of(2026, 3, 8, 1, 30, 0, 0, newYork);
ZonedDateTime afterDST = beforeDST.plusHours(1);
System.out.println("DST前: " + beforeDST); // 2026-03-08T01:30-05:00
System.out.println("DST後: " + afterDST); // 2026-03-08T03:30-04:00(2:30はスキップ)
}
}
|
Period・Durationで期間を表現#
PeriodとDurationは、時間の量(期間)を表現するクラスです。
Period - 日付ベースの期間#
Periodは年、月、日を単位とした期間を表現します。カレンダー上の日付の差を扱う場合に使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
import java.time.LocalDate;
import java.time.Period;
public class PeriodExample {
public static void main(String[] args) {
// Periodの生成
Period oneYear = Period.ofYears(1);
Period twoMonths = Period.ofMonths(2);
Period tenDays = Period.ofDays(10);
Period complex = Period.of(1, 2, 15); // 1年2ヶ月15日
System.out.println("1年: " + oneYear); // P1Y
System.out.println("2ヶ月: " + twoMonths); // P2M
System.out.println("10日: " + tenDays); // P10D
System.out.println("複合: " + complex); // P1Y2M15D
// 文字列からパース(ISO 8601形式)
Period parsed = Period.parse("P1Y2M15D");
System.out.println("パース: " + parsed);
// 2つの日付間のPeriodを計算
LocalDate startDate = LocalDate.of(2024, 3, 15);
LocalDate endDate = LocalDate.of(2026, 6, 20);
Period between = Period.between(startDate, endDate);
System.out.println("期間: " + between); // P2Y3M5D
System.out.println("年: " + between.getYears()); // 2
System.out.println("月: " + between.getMonths()); // 3
System.out.println("日: " + between.getDays()); // 5
// Periodを日付に適用
LocalDate today = LocalDate.of(2026, 1, 15);
LocalDate future = today.plus(Period.of(1, 2, 10));
System.out.println("1年2ヶ月10日後: " + future); // 2027-03-25
// 年齢計算の応用
LocalDate birthDate = LocalDate.of(1990, 5, 15);
LocalDate currentDate = LocalDate.of(2026, 1, 3);
Period age = Period.between(birthDate, currentDate);
System.out.printf("年齢: %d歳%dヶ月%n", age.getYears(), age.getMonths());
}
}
|
Duration - 時間ベースの期間#
Durationは秒とナノ秒を単位とした期間を表現します。時刻の差や処理時間の計測に使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
import java.time.Duration;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.Instant;
public class DurationExample {
public static void main(String[] args) {
// Durationの生成
Duration oneHour = Duration.ofHours(1);
Duration thirtyMinutes = Duration.ofMinutes(30);
Duration tenSeconds = Duration.ofSeconds(10);
Duration complex = Duration.ofHours(2).plusMinutes(30).plusSeconds(45);
System.out.println("1時間: " + oneHour); // PT1H
System.out.println("30分: " + thirtyMinutes); // PT30M
System.out.println("10秒: " + tenSeconds); // PT10S
System.out.println("複合: " + complex); // PT2H30M45S
// 文字列からパース(ISO 8601形式)
Duration parsed = Duration.parse("PT2H30M45S");
System.out.println("パース: " + parsed);
// 2つの時刻間のDurationを計算
LocalTime start = LocalTime.of(9, 0);
LocalTime end = LocalTime.of(17, 30);
Duration workHours = Duration.between(start, end);
System.out.println("勤務時間: " + workHours); // PT8H30M
System.out.println("時間: " + workHours.toHours()); // 8
System.out.println("分(合計): " + workHours.toMinutes()); // 510
System.out.println("秒(合計): " + workHours.getSeconds()); // 30600
// Durationを時刻に適用
LocalDateTime now = LocalDateTime.of(2026, 1, 15, 10, 0);
LocalDateTime later = now.plus(Duration.ofHours(3).plusMinutes(45));
System.out.println("3時間45分後: " + later); // 2026-01-15T13:45
// 処理時間の計測
Instant startInstant = Instant.now();
// 何らかの処理
for (int i = 0; i < 1000000; i++) {
Math.sqrt(i);
}
Instant endInstant = Instant.now();
Duration elapsed = Duration.between(startInstant, endInstant);
System.out.println("処理時間: " + elapsed.toMillis() + "ms");
}
}
|
PeriodとDurationの違い#
| 特徴 |
Period |
Duration |
| 単位 |
年、月、日 |
秒、ナノ秒 |
| 用途 |
カレンダー上の日付差 |
時刻差、処理時間 |
| 適用対象 |
LocalDate、LocalDateTime |
LocalTime、LocalDateTime、Instant |
| ISO表記 |
P1Y2M3D |
PT1H2M3S |
| 日数変換 |
月により変動 |
正確な秒数で計算 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
import java.time.LocalDate;
import java.time.Period;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
public class PeriodVsDuration {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2026, 1, 31);
// Periodで1ヶ月加算(月末調整が行われる)
LocalDate plusOneMonth = date.plus(Period.ofMonths(1));
System.out.println("1ヶ月後: " + plusOneMonth); // 2026-02-28
// Durationで30日加算
LocalDate plus30Days = date.plus(Duration.ofDays(30));
System.out.println("30日後: " + plus30Days); // 2026-03-02
// ChronoUnitで正確な日数計算
long exactDays = ChronoUnit.DAYS.between(
LocalDate.of(2026, 1, 1),
LocalDate.of(2026, 2, 1)
);
System.out.println("1月の日数: " + exactDays); // 31
}
}
|
実践的な活用例#
これまで学んだ内容を組み合わせた実践的な例を紹介します。
営業日計算#
週末を除いた営業日を計算する例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
import java.time.LocalDate;
import java.time.DayOfWeek;
import java.util.stream.Stream;
public class BusinessDayCalculator {
public static LocalDate addBusinessDays(LocalDate date, int days) {
if (days <= 0) {
return date;
}
LocalDate result = date;
int addedDays = 0;
while (addedDays < days) {
result = result.plusDays(1);
if (!isWeekend(result)) {
addedDays++;
}
}
return result;
}
public static long countBusinessDays(LocalDate start, LocalDate end) {
return Stream.iterate(start, d -> d.plusDays(1))
.limit(java.time.temporal.ChronoUnit.DAYS.between(start, end))
.filter(d -> !isWeekend(d))
.count();
}
private static boolean isWeekend(LocalDate date) {
DayOfWeek dow = date.getDayOfWeek();
return dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY;
}
public static void main(String[] args) {
LocalDate today = LocalDate.of(2026, 1, 15); // 木曜日
// 5営業日後
LocalDate fiveDaysLater = addBusinessDays(today, 5);
System.out.println("5営業日後: " + fiveDaysLater); // 2026-01-22(木曜日)
// 営業日数のカウント
LocalDate start = LocalDate.of(2026, 1, 1);
LocalDate end = LocalDate.of(2026, 1, 31);
long businessDays = countBusinessDays(start, end);
System.out.println("1月の営業日数: " + businessDays);
}
}
|
予約システムの時間枠チェック#
予約可能な時間枠をチェックする例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Duration;
public class ReservationChecker {
private static final LocalTime OPENING_TIME = LocalTime.of(9, 0);
private static final LocalTime CLOSING_TIME = LocalTime.of(18, 0);
private static final Duration MIN_DURATION = Duration.ofMinutes(30);
public static boolean isValidReservation(LocalDateTime start, LocalDateTime end) {
// 開始が終了より前かチェック
if (!start.isBefore(end)) {
return false;
}
// 営業時間内かチェック
LocalTime startTime = start.toLocalTime();
LocalTime endTime = end.toLocalTime();
if (startTime.isBefore(OPENING_TIME) || endTime.isAfter(CLOSING_TIME)) {
return false;
}
// 最小予約時間を満たしているかチェック
Duration duration = Duration.between(start, end);
if (duration.compareTo(MIN_DURATION) < 0) {
return false;
}
// 過去の日時でないかチェック
if (start.isBefore(LocalDateTime.now())) {
return false;
}
return true;
}
public static void main(String[] args) {
LocalDateTime validStart = LocalDateTime.of(2026, 2, 1, 10, 0);
LocalDateTime validEnd = LocalDateTime.of(2026, 2, 1, 11, 0);
System.out.println("有効な予約: " + isValidReservation(validStart, validEnd)); // true
LocalDateTime earlyStart = LocalDateTime.of(2026, 2, 1, 8, 0);
LocalDateTime earlyEnd = LocalDateTime.of(2026, 2, 1, 9, 30);
System.out.println("早すぎる予約: " + isValidReservation(earlyStart, earlyEnd)); // false
}
}
|
国際会議のスケジューリング#
複数のタイムゾーンにまたがる参加者向けに会議時間を表示する例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
public class MeetingScheduler {
private static final List<ZoneId> PARTICIPANT_ZONES = List.of(
ZoneId.of("Asia/Tokyo"),
ZoneId.of("America/New_York"),
ZoneId.of("Europe/London"),
ZoneId.of("Asia/Singapore")
);
public static void displayMeetingTimes(ZonedDateTime meetingTime) {
DateTimeFormatter formatter = DateTimeFormatter
.ofPattern("yyyy-MM-dd HH:mm (z)", Locale.US);
System.out.println("=== 会議時間 ===");
for (ZoneId zone : PARTICIPANT_ZONES) {
ZonedDateTime localTime = meetingTime.withZoneSameInstant(zone);
System.out.printf("%-20s: %s%n",
zone.getId(),
localTime.format(formatter));
}
}
public static void main(String[] args) {
// 東京時間で2026年1月20日 10:00に会議を設定
ZonedDateTime meeting = ZonedDateTime.of(
2026, 1, 20, 10, 0, 0, 0,
ZoneId.of("Asia/Tokyo")
);
displayMeetingTimes(meeting);
// === 会議時間 ===
// Asia/Tokyo : 2026-01-20 10:00 (JST)
// America/New_York : 2026-01-19 20:00 (EST)
// Europe/London : 2026-01-20 01:00 (GMT)
// Asia/Singapore : 2026-01-20 09:00 (SGT)
}
}
|
まとめ#
この記事では、JavaのDate and Time API(java.timeパッケージ)について、基礎から実践的な活用方法まで解説しました。
主要なポイントを振り返ると、以下のとおりです。
- 旧APIの問題点である可変性・スレッド非安全・直感に反する設計が、java.timeパッケージでは解消されている
LocalDate・LocalTime・LocalDateTimeは、タイムゾーンを持たないローカルな日付・時刻を表現する
plus・minus・withメソッドはイミュータブルな操作で、新しいオブジェクトを返す
DateTimeFormatterでカスタムフォーマットを定義し、フォーマットとパースを行う
ZonedDateTimeでタイムゾーンを考慮した日時処理が可能になる
Periodは日付ベース、Durationは時間ベースの期間を表現する
タイムゾーンを考慮する必要がない場合はLocalDateTimeを、グローバルなアプリケーションではZonedDateTimeやInstantを適切に使い分けることで、正確な日時処理を実現できます。
参考リンク#