LocalDate、LocalTime、LocalDateTimeは、java.timeパッケージの主要クラスであり、タイムゾーンに依存しない日付と時刻の操作を提供します。Oracleのドキュメント(Java 17、2024)によると、これらの型はimmutableおよびthread-safeとして設計されており、マルチスレッドアプリケーションでも安全に使用できます。AndroidではAPI 26以降でdesugaringを介して利用可能になり、古いバージョンではThreeTenABPライブラリを介して利用できます。
重要なポイント
LocalDate — 時刻やタイムゾーンの情報なしで年月日形式で日付を表すクラス。誕生日、イベント日、有効期限などのデータを格納するために使用されます。
LocalDateは-999999999から+999999999の範囲の年、1から12の月、およびうるう年を考慮した月の日を格納します。このクラスは完全にimmutableです — すべての操作は新しいオブジェクトを返します。
LocalTimeは時刻(時、分、秒、ナノ秒)を表します。最大精度はナノ秒までです。LocalTimeには日付やタイムゾーンの情報が含まれないため、店舗の営業時間やプロセスの所要時間を格納するのに便利です。
LocalDateTimeはLocalDateとLocalTimeを1つのオブジェクトに結合します。これは、日付と時刻の両方を格納する必要があるが、タイムゾーンへのバインドが不要な場合に最もよく使用される型です。たとえば、ローカル形式でのコンサートの日時などです。
Oracle Javaドキュメント(2024)によると、3つのクラスはすべてJoda-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クラスは、日付の作成、読み取り、変更のための多くのメソッドを提供します。現在の日付は静的メソッド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はナノ秒精度で時刻を表します。標準形式は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は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を使用して直接オブジェクトを作成することはできません。
主な作成メソッド:
ofメソッドには多くのオーバーロードがあります。LocalDateには年、月、日が必要です。LocalTimeには時と分(オプションで秒とナノ秒)が必要です。LocalDateTimeには年、月、日、時、分が必要です。月はint(1-12)またはMonth列挙型として渡せます。
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)を介して実行されます。
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です。
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つのオブジェクトは、異なるインスタンスであっても等しくなります。
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)メソッドはこれらのオブジェクトを受け入れます。
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)
実践的な例を見てみましょう:勤務シフト追跡アプリケーションです。シフトの期間を計算し、夜間時間帯に該当するかどうかを判断する必要があります。開始時間と終了時間にはLocalTime、日付にはLocalDate、深夜をまたぐシフトの計算にはLocalDateTimeを使用します。
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を使用し、誕生日の日と月を考慮して現在の日付と比較します。
fun calculateAge(birthDate: LocalDate): Int {
val today = LocalDate.now()
val period = Period.between(birthDate, today)
return period.years
}
3つ目の例 — 通知の操作。LocalDateTimeはリマインダーのスケジュールに使用されます。予定された時間が来たかどうかを確認します。
data class Reminder(
val id: Long,
val scheduledAt: LocalDateTime
) {
fun isDue(): Boolean =
LocalDateTime.now().isAfter(scheduledAt)
}
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です。
@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にはタイムゾーン情報が含まれていません。絶対的な瞬間を渡す必要がある場合は、ゾーン型を使用してください。ローカル時間で十分な場合は、ローカル型を使用してください。
よくある質問
Dateは1970-01-01 UTCからのミリ秒数を格納しますが、LocalDateはタイムゾーンのバインドなしで年、月、日を格納します。Dateは可変でスレッドセーフではなく、LocalDateは不変でスレッドセーフです。DateはJava 8以降非推奨です。
はい、LocalDateTimeはSQL型TIMESTAMP WITHOUT TIME ZONEに適切にマッピングされます。JPAとRoomはTypeConverterを介してサポートしています。TIMESTAMP WITH TIME ZONEの場合は、ZonedDateTimeまたはOffsetDateTimeを使用してください。
ChronoUnit.DAYS.between(startDate, endDate)を使用します。このメソッドはlong値を返します — 日数の差です。より詳細な計算にはPeriod.between()を使用します。これにより、年、月、日を含むPeriodが返されます。
LocalTimeはナノ秒精度(小数点以下9桁)をサポートします。ミリ秒精度で十分な場合は、保存前にtruncateTo(ChronoUnit.MILLIS)を使用してください。これにより、シリアル化時の丸めの問題を防げます。
now()メソッドはデバイスのシステムクロックとデフォルトのタイムゾーンを使用します。デバイスが異なるタイムゾーンにある場合、日付が異なる可能性があります。統一されたタイムスタンプにはInstant.now()を使用してください。これは常にUTC時刻を返します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。