LocalDate、LocalTime、LocalDateTime:概要と日時の操作

著者: IT Sectr 公開日: 2026-07-13 読了時間: 12 分

LocalDate、LocalTime、LocalDateTimeは、java.timeパッケージの主要クラスであり、タイムゾーンに依存しない日付と時刻の操作を提供します。Oracleのドキュメント(Java 17、2024)によると、これらの型はimmutableおよびthread-safeとして設計されており、マルチスレッドアプリケーションでも安全に使用できます。AndroidではAPI 26以降でdesugaringを介して利用可能になり、古いバージョンではThreeTenABPライブラリを介して利用できます。

重要なポイント

  • LocalDate — 時刻とタイムゾーンなしで日付(年、月、日)を表すimmutableクラス。
  • LocalTime — 日付とタイムゾーンなしで時刻(時、分、秒、ナノ秒)を表すimmutableクラス。
  • LocalDateTime — LocalDateとLocalTimeを組み合わせ、タイムゾーンに依存せず日付と時刻の両方を格納するクラス。
  • 3つのクラスはすべて算術演算をサポート — plusおよびminusメソッドで日、月、時間の加算と減算が可能。
  • Androidでは、desugaring(API 26+)またはThreeTenABPライブラリ(API < 26)を介してこれらの型が利用可能。

LocalDate、LocalTime、LocalDateTimeとは?

LocalDate — 時刻やタイムゾーンの情報なしで年月日形式で日付を表すクラス。誕生日、イベント日、有効期限などのデータを格納するために使用されます。

LocalDateは-999999999から+999999999の範囲の年、1から12の月、およびうるう年を考慮した月の日を格納します。このクラスは完全にimmutableです — すべての操作は新しいオブジェクトを返します。

LocalTimeは時刻(時、分、秒、ナノ秒)を表します。最大精度はナノ秒までです。LocalTimeには日付やタイムゾーンの情報が含まれないため、店舗の営業時間やプロセスの所要時間を格納するのに便利です。

LocalDateTimeはLocalDateとLocalTimeを1つのオブジェクトに結合します。これは、日付と時刻の両方を格納する必要があるが、タイムゾーンへのバインドが不要な場合に最もよく使用される型です。たとえば、ローカル形式でのコンサートの日時などです。

Oracle Javaドキュメント(2024)によると、3つのクラスはすべてJoda-Timeライブラリのアイデアに基づいて設計されていますが、アーキテクチャが改善され、標準ライブラリに完全に統合されています。

java.timeパッケージの仕組み

java.timeパッケージはJava 8で、旧来のDate、Calendar、SimpleDateFormatクラスの置き換えとして導入されました。そのアーキテクチャは、不変オブジェクトとfluentインターフェースの原則に基づいて構築されています。

重要な特徴 — すべての主要クラスはvalue-basedです。つまり、インスタンスは参照ではなく値で比較され、継承できません。2つのオブジェクトを比較するには、==演算子ではなくequalsメソッドを使用します。

パッケージはいくつかのカテゴリに分かれています。タイムゾーンなしの型 — LocalDate、LocalTime、LocalDateTime — はローカルの日付と時刻に使用されます。タイムゾーンありの型 — ZonedDateTime、OffsetDateTime、OffsetTime — はオフセットまたはゾーン情報を追加します。インスタント型 — Instant — はUTCのタイムライン上のポイントを表します。

この分割により、従来のAPIに内在していた問題が解決されます。開発者はDateオブジェクトにタイムゾーン情報が含まれているかどうかを知る術がありませんでした。java.timeでは、各型がそのセマンティクスを明示的に宣言します。

LocalDate:日付の操作

LocalDateクラスは、日付の作成、読み取り、変更のための多くのメソッドを提供します。現在の日付は静的メソッドnow()で取得できます。特定の日付はof(int year, int month, int dayOfMonth)メソッドで取得できます。

日付のコンポーネントを読み取るにはゲッターを使用します:getYear()、getMonthValue()、getDayOfMonth()、getDayOfWeek()、getDayOfYear()。getMonth()メソッドはMonth列挙型を返し、getDayOfWeek()はDayOfWeek列挙型を返します。

LocalDateは日付チェックをサポートします。isBefore()、isAfter()、isEqual()メソッドで日付を比較できます。isLeapYear()メソッドはうるう年かどうかをチェックします。lengthOfMonth()メソッドは月の日数を返し、lengthOfYear()は年の日数を返します。

変更には、withYear()、withMonth()、withDayOfMonth()メソッドを使用します。これらは変更されたコンポーネントを持つ新しいオブジェクトを返します。plusDays()、minusMonths()などのメソッドは日付演算を実行します。

LocalTime:時刻の操作

LocalTimeはナノ秒精度で時刻を表します。標準形式はISO-8601(HH:mm:ss.nnnnnnnnn)です。最小値は00:00、最大値は23:59:59.999999999です。

LocalTimeオブジェクトは、now()(現在時刻)、of(int hour, int minute)、of(int hour, int minute, int second)、またはof(int hour, int minute, int second, int nanoOfSecond)で作成できます。parse(CharSequence text)メソッドはISO-8601形式の文字列をパースします。

ゲッターにはgetHour()、getMinute()、getSecond()、getNano()があります。toSecondOfDay()メソッドはその日の開始からの秒数を返し、toNanoOfDay()はナノ秒を返します。これは1日以内の期間の計算に便利です。

LocalTimeはLocalDateと同じ比較および変更操作をサポートします:plusHours()、minusMinutes()、withHour()、withMinute()。isBefore()とisAfter()メソッドは、時刻が1日の中で循環することを考慮して動作します。

LocalDateTime:日付と時刻の組み合わせ

LocalDateTimeはLocalDateとLocalTimeの機能を1つのクラスに統合します。日付と時刻の両方を格納しますが、タイムゾーンはありません。これは最も柔軟なローカル型ですが、分散システムで使用する場合は注意が必要です。

LocalDateTimeは、静的メソッドnow()、of(LocalDate date, LocalTime time)、of(int year, Month month, int dayOfMonth, int hour, int minute)およびそのオーバーロードで作成できます。また、atTime()メソッドでLocalDateとLocalTimeを組み合わせることもできます。

LocalDateTimeは、対応するゲッターを介してすべての日付と時刻のフィールドにアクセスできます:toLocalDate()とtoLocalTime()は個別のコンポーネントを返します。truncatedTo(TemporalUnit unit)メソッドは、時刻を指定された精度(たとえば分)に切り捨てることができます。

タイムゾーンに変換するには、atZone(ZoneId zone)メソッドを使用します。これによりZonedDateTimeが返されます。これがLocalDateTimeにタイムゾーンを追加する唯一の方法です。

日付と時刻のオブジェクトの作成方法

3つのクラスはすべて、静的ファクトリメソッドによる統一された作成パターンを使用します。クラスコンストラクターはprivateとして宣言されています — newを使用して直接オブジェクトを作成することはできません。

主な作成メソッド:

  • now() — システムクロックから現在の日付/時刻を取得
  • of(...) — コンポーネント(年、月、日など)から作成
  • parse(String) — ISO-8601形式の文字列から作成
  • from(TemporalAccessor) — 別のテンポラルオブジェクトから作成

ofメソッドには多くのオーバーロードがあります。LocalDateには年、月、日が必要です。LocalTimeには時と分(オプションで秒とナノ秒)が必要です。LocalDateTimeには年、月、日、時、分が必要です。月はint(1-12)またはMonth列挙型として渡せます。

kotlin
val today = LocalDate.now()
val specificDate = LocalDate.of(2026, Month.JULY, 21)
val parsedDate = LocalDate.parse("2026-07-21")

val currentTime = LocalTime.now()
val lunchTime = LocalTime.of(13, 30, 0)
val parsedTime = LocalTime.parse("13:30:00")

val now = LocalDateTime.now()
val meeting = LocalDateTime.of(2026, 7, 21, 15, 0)

型間の変換

java.timeクラスは相互に便利に変換できるように設計されています。LocalDateはatTime(LocalTime)またはatStartOfDay()メソッドでLocalDateTimeに変換できます。LocalTimeはatDate(LocalDate)で変換できます。

LocalDateTimeはtoLocalDate()でLocalDateに、toLocalTime()でLocalTimeに戻せます。ZonedDateTimeに変換するにはatZone(ZoneId)メソッドを使用します。

java.util.Dateへの変換(レガシーコードとの互換性用)には、Instantとタイムゾーンを介した中間ステップが必要です。Baeldung(2024)によると、この操作はDate.from(instant)を介して実行されます。

kotlin
val date = LocalDate.of(2026, 7, 21)
val dateTime = date.atTime(LocalTime.of(10, 30))

val time = LocalTime.of(14, 0)
val dateTimeFromTime = time.atDate(date)

val extractedDate = dateTime.toLocalDate()
val extractedTime = dateTime.toLocalTime()

val zoned = dateTime.atZone(ZoneId.of("Europe/Moscow"))

フォーマットとパース

フォーマットとパースにはDateTimeFormatterクラスを使用します。これは定数(ISO_LOCAL_DATE、ISO_LOCAL_TIME、ISO_LOCAL_DATE_TIME)を介した定義済みフォーマットと、パターン文字列を使用したカスタムフォーマット作成機能を提供します。

フォーマットパターンは記号を使用します:yyyy — 年、MM — 月(2桁)、dd — 日、HH — 時(0-23)、mm — 分、ss — 秒。format()メソッドは日時オブジェクトまたはDateTimeFormatterを介して呼び出します。

DateTimeFormatterは、静的メソッドofLocalizedDate(FormatStyle)、ofLocalizedTime(FormatStyle)、ofLocalizedDateTime(FormatStyle)を介したローカライズもサポートします。利用可能なスタイルはSHORT、MEDIUM、LONG、FULLです。

kotlin
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
val formatted = LocalDateTime.now().format(formatter)

val parsed = LocalDate.parse(
    "21.07.2026",
    DateTimeFormatter.ofPattern("dd.MM.yyyy")
)

日時オブジェクトの比較

3つのクラスはすべてComparableインターフェースを実装しており、自然な比較が可能です。compareTo()メソッドは順序に応じて負の数、ゼロ、または正の数を返します。isBefore()、isAfter()、isEqual()メソッドはブール値を返します。

LocalDateの場合、比較は時系列で行われます — より前の日付が小さいと見なされます。LocalTimeの場合は時刻で比較されます。LocalDateTimeの場合は、最初に日付で比較され、次に時刻で比較されます。すべての比較はうるう年と月の日数を正しく考慮します。

従来のAPIとの重要な違い:LocalDate、LocalTime、LocalDateTimeのequals()は参照ではなく値を比較します。つまり、同じフィールドを持つ2つのオブジェクトは、異なるインスタンスであっても等しくなります。

kotlin
val d1 = LocalDate.of(2026, 7, 21)
val d2 = LocalDate.of(2026, 12, 25)

if (d1.isBefore(d2)) {
    Log.d("日付", "d1はd2より前")
}

val sortedDates = listOf(d2, d1).sorted()

日時の算術演算

3つのクラスはすべてplusおよびminusメソッドを介した算術演算をサポートします。LocalDateではplusDays()、plusWeeks()、plusMonths()、plusYears()および対応するminusメソッドが利用可能です。LocalTimeはplusHours()、plusMinutes()、plusSeconds()、plusNanos()をサポートします。

LocalDateTimeは両方の型からすべての算術演算を継承します。LocalDateの注目すべき特徴:月を追加する場合、結果は異なる月の長さを正しく処理します。たとえば、1月31日 + 1ヶ月 = 2月28日(うるう年は29日)です。

より複雑な操作には、Periodクラス(日付用)とDurationクラス(時刻用)があります。plus(TemporalAmount)およびminus(TemporalAmount)メソッドはこれらのオブジェクトを受け入れます。

kotlin
val today = LocalDate.now()
val nextWeek = today.plusDays(7)
val nextMonth = today.plusMonths(1)
val lastYear = today.minusYears(1)

val now = LocalTime.now()
val inTwoHours = now.plusHours(2)
val halfHourAgo = now.minusMinutes(30)

Kotlinのコード例

実践的な例を見てみましょう:勤務シフト追跡アプリケーションです。シフトの期間を計算し、夜間時間帯に該当するかどうかを判断する必要があります。開始時間と終了時間にはLocalTime、日付にはLocalDate、深夜をまたぐシフトの計算にはLocalDateTimeを使用します。

kotlin
data class Shift(
    val startTime: LocalTime,
    val endTime: LocalTime,
    val date: LocalDate
) {
    fun isOvernight(): Boolean = endTime.isBefore(startTime)

    fun durationInMinutes(): Long {
        val start = LocalDateTime.of(date, startTime)
        val end = LocalDateTime.of(
            if (isOvernight()) date.plusDays(1) else date,
            endTime
        )
        return Duration.between(start, end).toMinutes()
    }
}

2つ目の例 — ユーザーの年齢計算。生年月日にLocalDateを使用し、誕生日の日と月を考慮して現在の日付と比較します。

kotlin
fun calculateAge(birthDate: LocalDate): Int {
    val today = LocalDate.now()
    val period = Period.between(birthDate, today)
    return period.years
}

3つ目の例 — 通知の操作。LocalDateTimeはリマインダーのスケジュールに使用されます。予定された時間が来たかどうかを確認します。

kotlin
data class Reminder(
    val id: Long,
    val scheduledAt: LocalDateTime
) {
    fun isDue(): Boolean =
        LocalDateTime.now().isAfter(scheduledAt)
}

Androidのサポート:APIレベルとDesugaring

java.timeの組み込みサポートはAndroid API 26(Android 8.0 Oreo)から利用可能になりました。より古いAndroidバージョンのデバイスでは、desugaring — 新しいJava APIのサポートを以前のバージョンに追加するメカニズム — を使用する必要があります。

Android Gradle PluginでのDesugaringは、build.gradleのcompileOptionsを介して構成します。isCoreLibraryDesugaringEnabled = trueを設定し、desugar_jdk_libsライブラリを追加するだけです。その後、java.timeは14以降のすべてのAPIレベルで利用可能になります。

desugaringを使用できないプロジェクト(AGP 4.0未満のレガシープロジェクトなど)向けに、ThreeTenABPライブラリがあります — java.timeのバックポートです。同じクラス(LocalDate、LocalTime、LocalDateTime)を提供しますが、パッケージはorg.threeten.bpです。

groovy
@Suppress("UnstableApiUsage")
android {
    compileOptions {
        isCoreLibraryDesugaringEnabled = true
    }
}

dependencies {
    "coreLibraryDesugaring"("com.android.tools:desugar_jdk_libs:2.1.4")
}

よくある間違いとその回避方法

1つ目のよくある間違い — タイムゾーンを考慮せずに分散システムでLocalDateTimeを使用すること。サーバーがEurope/Moscowにあり、クライアントがAsia/Tokyoにある場合、LocalDateTimeの解釈が異なります。解決策:グローバルデータにはInstantまたはZonedDateTimeを使用してください。

2つ目の間違い — 誤った文字列パース。デフォルトでは、LocalDate.parse()はISO-8601形式(yyyy-MM-dd)を期待します。文字列が別の形式の場合は、明示的にDateTimeFormatterを渡す必要があります。また、アプリケーションが無効な入力でクラッシュしないようにDateTimeParseExceptionを処理する必要があります。

3つ目の間違い — null安全性の無視。LocalDate、LocalTime、LocalDateTimeはnullになる可能性のあるオブジェクトです。Kotlinでは、明示的なチェックまたはElvis演算子を使用してnull許容型を使用することをお勧めします。Javaでは、メソッドを呼び出す前にnullチェックを行ってください。

4つ目の間違い — LocalDateTimeとZonedDateTimeの混同。LocalDateTimeにはタイムゾーン情報が含まれていません。絶対的な瞬間を渡す必要がある場合は、ゾーン型を使用してください。ローカル時間で十分な場合は、ローカル型を使用してください。

よくある質問

JavaのLocalDateとDateの違いは何ですか?

Dateは1970-01-01 UTCからのミリ秒数を格納しますが、LocalDateはタイムゾーンのバインドなしで年、月、日を格納します。Dateは可変でスレッドセーフではなく、LocalDateは不変でスレッドセーフです。DateはJava 8以降非推奨です。

データベースでLocalDateTimeを使用できますか?

はい、LocalDateTimeはSQL型TIMESTAMP WITHOUT TIME ZONEに適切にマッピングされます。JPAとRoomはTypeConverterを介してサポートしています。TIMESTAMP WITH TIME ZONEの場合は、ZonedDateTimeまたはOffsetDateTimeを使用してください。

2つの日付間の日数を取得するには?

ChronoUnit.DAYS.between(startDate, endDate)を使用します。このメソッドはlong値を返します — 日数の差です。より詳細な計算にはPeriod.between()を使用します。これにより、年、月、日を含むPeriodが返されます。

ミリ秒単位の時刻精度を保持する必要がある場合は?

LocalTimeはナノ秒精度(小数点以下9桁)をサポートします。ミリ秒精度で十分な場合は、保存前にtruncateTo(ChronoUnit.MILLIS)を使用してください。これにより、シリアル化時の丸めの問題を防げます。

LocalDate.now()がデバイスによって異なる日付を返すのはなぜですか?

now()メソッドはデバイスのシステムクロックとデフォルトのタイムゾーンを使用します。デバイスが異なるタイムゾーンにある場合、日付が異なる可能性があります。統一されたタイムスタンプにはInstant.now()を使用してください。これは常にUTC時刻を返します。

まとめ

  • LocalDate — 時刻とタイムゾーンなしの日付用のimmutableクラス。誕生日、期限、イベント日などの保存に使用。
  • LocalTime — ナノ秒精度の時刻用immutableクラス。営業時間、プロセス期間の保存に適切。
  • LocalDateTime — タイムゾーンバインドなしの日付と時刻の組み合わせ。最も柔軟なローカル型だが、分散システムには不適切。
  • 3つのクラスはすべてDateTimeFormatterに基づく統一APIを介して算術、比較、フォーマット、パースをサポート。
  • Androidでは、java.timeはAPI 26からの組み込みサポートまたは古いバージョン向けのdesugaringを介して利用可能。
  • グローバルタイムスタンプとタイムゾーン対応データには、ローカル型の代わりにZonedDateTimeまたはInstantを使用。
  • 文字列をパースする際は、非標準フォーマットには常にDateTimeFormatterを渡し、DateTimeParseExceptionを処理すること。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください