Javaプログラミングにおいて、日時の操作は避けて通れない重要な要素です。

Java 8で導入された「Date and Time API」 (JSR-310) は、それ以前の java.util.Datejava.util.Calendar が抱えていた設計上の問題を解決し、より直感的で安全な日時操作を実現しました。

その中でも中心的な役割を担うのが LocalDateTime クラスです。

本記事では、Javaエンジニアが実務で直面する「日時の取得」「計算」「フォーマット変換」「型変換」といった操作を網羅的に解説します。

最新のJava開発において、なぜこのクラスが推奨されるのか、その理由と具体的なコード例を交えて詳しく見ていきましょう。

LocalDateTimeとは

java.time.LocalDateTime は、タイムゾーンの概念を持たない日付と時刻を表すクラスです。

「2024年12月25日 10時30分」といった、特定の地域に依存しない日時情報を扱う際に使用されます。

LocalDateTimeの主な特徴

LocalDateTimeには、従来のAPIと比較して以下のような優れた特徴があります。

イミュータブル(不変性)

一度生成されたインスタンスの状態を変更することはできません。

日時の計算を行うと、必ず新しいインスタンスが返されます。

スレッドセーフ

不変であるため、マルチスレッド環境でも安全に共有できます。

直感的なAPI

メソッド名が plusDaysgetMonth など、目的が明確で分かりやすくなっています。

厳格な型定義

日付のみ、時刻のみ、タイムゾーン付き、といった用途に応じてクラスが明確に分かれています。

実務においては、タイムゾーンを考慮する必要がある場合は ZonedDateTime を使用し、システム内部のログや特定のローカルな日時を扱う場合には LocalDateTime を使用するという使い分けが一般的です。

LocalDateTimeのインスタンス生成

LocalDateTimeのインスタンスを生成するには、コンストラクタを直接呼び出すのではなく、静的ファクトリメソッドを使用します。

主に「現在日時の取得」「特定日時の指定」「文字列からの変換」の3パターンがあります。

現在日時の取得:now()

実行時のシステムクロックに基づいて現在の日時を取得するには、now() メソッドを使用します。

Java
import java.time.LocalDateTime;

public class Main {
    public static void main(String[] args) {
        // 現在の日時を取得
        LocalDateTime now = LocalDateTime.now();
        
        System.out.println("現在日時: " + now);
    }
}
実行結果
現在日時: 2024-05-20T15:30:45.123456789

特定の日時を指定:of()

年、月、日、時、分などを個別に指定して生成する場合は、of() メソッドを使用します。

引数のバリエーションは豊富ですが、最も一般的なのは秒まで指定する形式です。

Java
import java.time.LocalDateTime;
import java.time.Month;

public class Main {
    public static void main(String[] args) {
        // 数値で指定 (年, 月, 日, 時, 分, 秒)
        LocalDateTime dateTime1 = LocalDateTime.of(2024, 12, 25, 10, 30, 0);
        
        // Month列挙型を使用して指定
        LocalDateTime dateTime2 = LocalDateTime.of(2024, Month.DECEMBER, 25, 10, 30);
        
        System.out.println("指定日時1: " + dateTime1);
        System.out.println("指定日時2: " + dateTime2);
    }
}
実行結果
指定日時1: 2024-12-25T10:30
指定日時2: 2024-12-25T10:30

文字列から生成:parse()

ISO 8601 形式(yyyy-MM-ddTHH:mm:ss)の文字列からインスタンスを生成するには parse() を使用します。

独自のフォーマットを使用する場合は、後述する DateTimeFormatter を併用します。

Java
import java.time.LocalDateTime;

public class Main {
    public static void main(String[] args) {
        // 標準形式の文字列を解析
        LocalDateTime parsed = LocalDateTime.parse("2024-08-15T18:00:00");
        
        System.out.println("解析結果: " + parsed);
    }
}
実行結果
解析結果: 2024-08-15T18:00

日時情報の取得(Getter)

生成した LocalDateTime インスタンスから、特定の要素(年、月、日、曜日など)を取り出すメソッドが多数用意されています。

メソッド名戻り値の型説明
getYear()int年を取得
getMonth()Month月を列挙型で取得
getMonthValue()int月を数値(1-12)で取得
getDayOfMonth()int月の日を取得
getDayOfWeek()DayOfWeek曜日を列挙型で取得
getHour()int時(0-23)を取得
getMinute()int分(0-59)を取得
getSecond()int秒(0-59)を取得
Java
import java.time.LocalDateTime;
import java.time.DayOfWeek;

public class Main {
    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2024, 10, 1, 12, 0, 0);
        
        int year = dt.getYear();
        int month = dt.getMonthValue();
        DayOfWeek dow = dt.getDayOfWeek();
        
        System.out.println(year + "年" + month + "月、曜日は" + dow);
    }
}
実行結果
2024年10月、曜日はTUESDAY

日時の計算(加算・減算・変更)

LocalDateTimeは不変であるため、計算メソッドを呼び出しても元のインスタンスは変更されません。

計算結果を新しい変数で受け取る必要がある点に注意してください。

日時の加算・減算

plusXxx および minusXxx 系のメソッドを使用します。

Java
import java.time.LocalDateTime;

public class Main {
    public static void main(String[] args) {
        LocalDateTime base = LocalDateTime.of(2024, 1, 1, 10, 0);
        
        // 10日後
        LocalDateTime plusDays = base.plusDays(10);
        
        // 5時間前
        LocalDateTime minusHours = base.minusHours(5);
        
        // メソッドチェーンでの計算
        LocalDateTime complex = base.plusYears(1).minusMonths(2).plusWeeks(3);
        
        System.out.println("元の日時: " + base);
        System.out.println("10日後: " + plusDays);
        System.out.println("5時間前: " + minusHours);
        System.out.println("複合計算: " + complex);
    }
}
実行結果
元の日時: 2024-01-01T10:00
10日後: 2024-01-11T10:00
5時間前: 2024-01-01T05:00
複合計算: 2024-11-22T10:00

特定のフィールドを変更する

「年だけを2025年に変えたい」といった場合には、withXxx メソッドを使用します。

Java
import java.time.LocalDateTime;

public class Main {
    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2024, 5, 20, 10, 0);
        
        // 日を1日に変更(月初めにする)
        LocalDateTime firstDayOfMonth = dt.withDayOfMonth(1);
        
        System.out.println("元: " + dt);
        System.out.println("変更後: " + firstDayOfMonth);
    }
}
実行結果
元: 2024-05-20T10:00
変更後: 2024-05-01T10:00

日時の比較

2つの LocalDateTime インスタンスの前後関係を判定する方法を解説します。

比較メソッド

比較には主に以下のメソッドを使用します。

  • isBefore(other):指定した日時より前か
  • isAfter(other):指定した日時より後か
  • isEqual(other):指定した日時と同じか(LocalDateTime同士なら equals と同等)
  • compareTo(other):比較結果を数値(負、0、正)で返す
Java
import java.time.LocalDateTime;

public class Main {
    public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime future = now.plusDays(1);
        
        if (future.isAfter(now)) {
            System.out.println("futureはnowより後の日時です。");
        }
    }
}

期間の差分を取得する

2つの日時の間にどれくらいの時間差があるかを計算するには、Duration クラスや ChronoUnit を使用します。

Java
import java.time.LocalDateTime;
import java.time.Duration;
import java.time.temporal.ChronoUnit;

public class Main {
    public static void main(String[] args) {
        LocalDateTime start = LocalDateTime.of(2024, 1, 1, 10, 0);
        LocalDateTime end = LocalDateTime.of(2024, 1, 2, 13, 30);
        
        // ChronoUnitを使って特定の単位で差を出す
        long hoursDiff = ChronoUnit.HOURS.between(start, end);
        long daysDiff = ChronoUnit.DAYS.between(start, end);
        
        // Durationを使って詳細な差を出す
        Duration duration = Duration.between(start, end);
        
        System.out.println("時間差: " + hoursDiff + "時間");
        System.out.println("日数差: " + daysDiff + "日");
        System.out.println("Durationによる分: " + duration.toMinutes() + "分");
    }
}
実行結果
時間差: 27時間
日数差: 1日
Durationによる分: 1650分

ChronoUnit.DAYS は「24時間経過しているか」で日数をカウントするため、日付だけの単純な差を求めたい場合は、後述する LocalDate に変換してから比較するのが安全です。

フォーマット変換(String ⇔ LocalDateTime)

画面表示やAPI連携のために、LocalDateTimeを特定の形式の文字列に変換したり、その逆を行ったりする操作は非常に頻繁に行われます。

これには java.time.format.DateTimeFormatter を使用します。

LocalDateTimeから文字列へ (Format)

format() メソッドにフォーマッタを渡します。

Java
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class Main {
    public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.now();
        
        // 任意のフォーマットを定義
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        
        String formatted = now.format(formatter);
        System.out.println("整形済み文字列: " + formatted);
    }
}

文字列からLocalDateTimeへ (Parse)

parse() メソッドに文字列とフォーマッタを渡します。

Java
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class Main {
    public static void main(String[] args) {
        String input = "2024-12-31 23:59:59";
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        
        LocalDateTime dateTime = LocalDateTime.parse(input, formatter);
        System.out.println("復元された日時: " + dateTime);
    }
}

よく使われるフォーマットパターン

パターン説明
yyyy西暦4桁2024
MM月(2桁)05
dd日(2桁)20
HH24時間表記の時15
mm09
ss45
EEEE曜日(フルネーム)月曜日

注意点:パターンの大文字・小文字は厳格に区別されます。

例えば、「mm」は分ですが「MM」は月です。

間違えると意図しない値が解析されるため、必ずドキュメントを確認しましょう。

他の型との相互変換

実際のプロジェクトでは、データベースの型や古いライブラリとの互換性のために、LocalDateTimeを他の型に変換しなければならない場面が多くあります。

LocalDate / LocalTime への変換

LocalDateTimeから日付のみ、あるいは時刻のみを抽出するのは非常に簡単です。

Java
import java.time.LocalDateTime;
import java.time.LocalDate;
import java.time.LocalTime;

public class Main {
    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.now();
        
        LocalDate date = dt.toLocalDate();
        LocalTime time = dt.toLocalTime();
        
        System.out.println("日付のみ: " + date);
        System.out.println("時刻のみ: " + time);
    }
}

java.util.Date との相互変換

古いAPIである java.util.Date との変換には、Instant クラスとタイムゾーン(ZoneId)を介在させる必要があります。

Java
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
        // LocalDateTime -> Date
        LocalDateTime ldt = LocalDateTime.now();
        Date date = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
        
        // Date -> LocalDateTime
        Date inputDate = new Date();
        LocalDateTime outputLdt = LocalDateTime.ofInstant(inputDate.toInstant(), ZoneId.systemDefault());
        
        System.out.println("Date型: " + date);
        System.out.println("LocalDateTime型: " + outputLdt);
    }
}

java.sql.Timestamp との相互変換

JDBCなどのデータベース操作で使われる java.sql.Timestamp は、直接変換するメソッドが用意されています。

Java
import java.time.LocalDateTime;
import java.sql.Timestamp;

public class Main {
    public static void main(String[] args) {
        LocalDateTime ldt = LocalDateTime.now();
        
        // LocalDateTime -> Timestamp
        Timestamp ts = Timestamp.valueOf(ldt);
        
        // Timestamp -> LocalDateTime
        LocalDateTime result = ts.toLocalDateTime();
        
        System.out.println("Timestamp: " + ts);
        System.out.println("Result: " + result);
    }
}

LocalDateTime使用時の注意点

高機能な LocalDateTime ですが、使用にあたって注意すべき点もいくつか存在します。

1. タイムゾーンの欠如

前述の通り、LocalDateTimeはタイムゾーンを持っていません。

そのため、グローバル展開するアプリケーションで、サーバーが異なる国に設置されている場合などは、LocalDateTimeだけで管理すると時刻の食い違いが発生します。

「絶対的な一時点」を記録したい場合は、Instant または ZonedDateTime を使用することを検討してください。

2. 不変オブジェクトの誤解

初心者が最も陥りやすいミスは、戻り値を受け取らないことです。

Java
LocalDateTime ldt = LocalDateTime.now();
ldt.plusDays(1); // 間違い!ldt自体は変わらない
System.out.println(ldt); // 現在時刻のまま

ldt = ldt.plusDays(1); // 正解:戻り値を代入する

3. 解析エラーのハンドリング

parse() メソッドは、文字列の形式が正しくない場合に DateTimeParseException をスローします。

外部入力(ユーザーの入力値やCSVデータなど)を扱う場合は、必ず例外処理を実装するか、バリデーションを行う必要があります。

まとめ

Java 8以降の標準となった LocalDateTime は、日付と時刻を安全かつ容易に操作するための強力なツールです。

  • now()of() でインスタンスを生成する。
  • plus / minus メソッドで日時の計算を行うが、結果は必ず新しいインスタンスとして受け取る。
  • DateTimeFormatter を使って文字列との相互変換を行う。
  • タイムゾーンが必要な場合は ZonedDateTime と使い分ける。

これらの基本を押さえるだけで、Javaにおける日時操作のトラブルは劇的に減少します。

従来の Calendar クラスなどに苦労していた方も、この機会にモダンな Date and Time API を使いこなし、保守性の高いコードを目指しましょう。