はじめに

日付と時刻の処理は、あらゆるアプリケーションで必要となる基本的な機能です。予約システムの日付管理、ログのタイムスタンプ、有効期限の計算など、日常的に日付・時刻を扱う場面は数多くあります。

Java 8で導入された**Date and Time API(java.timeパッケージ)**は、従来のjava.util.Datejava.util.Calendarの問題点を解消し、直感的で安全な日付・時刻操作を実現します。

この記事では、java.timeパッケージの主要クラスであるLocalDateLocalTimeLocalDateTimeの基本的な使い方から、日付計算、フォーマット、タイムゾーン対応、そしてPeriodDurationによる期間表現まで、実践的なコード例とともに解説します。

旧APIの問題点とjava.timeパッケージ

Java 8より前は、java.util.Datejava.util.Calendarを使って日付・時刻を扱っていました。しかし、これらのAPIには多くの設計上の問題がありました。

旧APIの問題点

問題点 説明
ミュータブル(可変) Dateオブジェクトの値を後から変更でき、バグの温床になる
月が0始まり 1月が0、12月が11と直感に反する設計
スレッドセーフでない マルチスレッド環境でSimpleDateFormatが安全に使えない
APIの一貫性がない DateCalendarで役割が重複し、使い分けが困難
タイムゾーン処理が複雑 タイムゾーンを考慮した計算が非常に煩雑

旧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によるフォーマット

日付・時刻を任意の形式の文字列に変換するには、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で期間を表現

PeriodDurationは、時間の量(期間)を表現するクラスです。

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
単位 年、月、日 秒、ナノ秒
用途 カレンダー上の日付差 時刻差、処理時間
適用対象 LocalDateLocalDateTime LocalTimeLocalDateTimeInstant
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パッケージでは解消されている
  • LocalDateLocalTimeLocalDateTimeは、タイムゾーンを持たないローカルな日付・時刻を表現する
  • plusminuswithメソッドはイミュータブルな操作で、新しいオブジェクトを返す
  • DateTimeFormatterでカスタムフォーマットを定義し、フォーマットとパースを行う
  • ZonedDateTimeでタイムゾーンを考慮した日時処理が可能になる
  • Periodは日付ベース、Durationは時間ベースの期間を表現する

タイムゾーンを考慮する必要がない場合はLocalDateTimeを、グローバルなアプリケーションではZonedDateTimeInstantを適切に使い分けることで、正確な日時処理を実現できます。

参考リンク