2つの日時の間隔を求めたい場合に思いつく、なんとなく似ている響きの java.time.Period
と java.time.Duration
を比較してみました。
java.time.Period
こちらは「年月日」単位での間隔を表現できます。
例えば、2024-08-01 〜 2024-08-10の間であれば
import java.time.LocalDate
import java.time.Period
fun main() {
val start = LocalDate.of(2024, 8, 1)
val end = LocalDate.of(2024, 8, 10)
Period.between(start, end)
// P9D (9 days)
}
終了日 end
は含まれないので、1,2,3 ... 9 と数えていき、答えは9日間となります。
Periodの生成方法
between
2つの LocalDate
の差分から生成します。
SQLのように減算は使えないので、このメソッドを使います。
start > end
の場合、マイナス値になります。
Period.between(LocalDate.of(2024, 1, 1), LocalDate.of(2023, 11, 16))
// P-1M-15D (-1 month -15 days)
start == end
の場合、0です。
Period.between(LocalDate.of(2024, 1, 1), LocalDate.of(2024, 0, 0))
// P0D (0 days)
終了日を含めて「◯日間」と表現したい場合は、endに1日加算します。
もしくは、Period自体に加算します。
import java.time.LocalDate
import java.time.Period
fun main() {
val start = LocalDate.of(2024, 8, 1)
val end = LocalDate.of(2024, 8, 3)
Period.between(start, end.plusDays(1))
// P3D (3 days)
Period.between(start, end).plusDays(1)
// P3D (3 days)
}
between
を引き算で表現したい
演算子オーバーロードを使います。
operator fun LocalDate.minus(other: LocalDate): Period = Period.between(other, this)
val start = LocalDate.of(2024, 8, 1)
val end = LocalDate.of(2024, 8, 3)
end - start
// P2D (2 days)
start - end
// P-2D (-2 days)
必ず正の値を出したい場合
operator fun LocalDate.minus(other: LocalDate): Period = if (this > other) {
Period.between(other, this)
} else {
Period.between(this, other)
}
val start = LocalDate.of(2024, 8, 1)
val end = LocalDate.of(2024, 8, 3)
end - start
// P2D (2 days)
start - end
// P2D (2 days)
from
他の java.time.TemporalAmount
から生成します。
例えば java.time.Duration
や、他ライブラリが実装するパターン(例)もあります。
of(years: Int, months: Int, days: Int)
差分の「年月日」から生成します。
月が12を超えたり、日が31を超えても表現できます。
Period.of(1, 6, 0)
// P1Y6M (1 year 6 months)
Period.of(0, 18, 90)
// P18M90D (18 months 90 days)
ofYears
, ofMonths
, ofWeeks
, ofDays
of
の省力化バージョン。年のみ、月のみ、週のみ、日のみを指定できます。
月が12を超えたり、日が31を超えても表現できます。
Period.ofMonths(18)
// P18M (18 months)
Period.ofDays(90)
// P90D (90 days)
parse
ISO 8601形式の文字列を解釈できます。
LocalDate
と Period
で計算
指定期間の加減算
例えば 2024-01-01 の1年6️ヶ月後/前を計算したい場合
val date = LocalDate.of(2024, 1, 1)
val period = Period.of(1, 6, 0)
// -- 加算
date.plus(period)
date + period
// 2025-07-01
// 以下と同等
date.plusYears(1).plusMonths(6)
// -- 減算
date.miuns(period)
date - period
// 2022-07-01
// 以下と同等
date.minusYears(1).minusMonths(6)
java.time.Duration
こちらはPeriodより細かい、日〜ナノ秒単位での間隔を表現できます。
import java.time.Duration
import java.time.LocalTime
fun main() {
val start = LocalTime.of(0, 0)
val end = LocalTime.of(12, 34, 56, 789000000)
Duration.between(start, end)
// PT12H34M56.789S
}
秒単位での情報を持っていない LocalDate
などからは算出できません。
(Temporalインターフェイスが引数なので、コンパイルは通ってしまいます)
Duration.between(LocalDate.of(2024, 1, 1), LocalDate.now())
// java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds
また、 start
と end
で粒度が違う場合もエラーになります。
val start: LocalDateTime = LocalDate.now().atStartOfDay()
val end: LocalTime = LocalTime.of(12, 34, 56, 789000000)
Duration.between(start, end)
// java.time.DateTimeException: Unable to obtain LocalDateTime from TemporalAccessor: 12:34:56.789 of type java.time.LocalTime
Durationの生成方法
Periodとほぼ同じですが、引数が異なるものもあります。
of(amount: Long, unit: TemporalUnit)
Duration.of(2, ChronoUnit.HOURS)
// PT2H
ofSeconds
の2つのオーバーロード
nanoAdjustment
を指定すると、ナノ秒も指定できます。
Duration.ofSeconds(10)
// PT10S
Duration.ofSeconds(10, 200000000)
// PT10.2S
12:34:56.789 を表現したい
何か他にいい方法があるかもしれませんが…
Duration.ofHours(12).plusMinutes(34).plusSeconds(56).plusMillis(789)
// PT12H34M56.789S
時刻を持つ型と Duration
の加減算
LocalDateTime
を例にします。
val now = LocalDateTime.now()
val duration = Duration.ofDays(3).plusHours(12)
now + duration
// 現在時刻から3日半後
時刻を持たない型と Duration
の加減算
LocalDate
を例にします。
val duration = Duration.ofDays(3)
val date = LocalDate.of(2024, 1, 1)
date + duration
// java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds
PeriodとDurationをあわせたい
val duration = Duration.ofDays(3).plusHours(12)
val period = Period.ofYears(1)
duration + period
// コンパイルエラー
period + duration
// コンパイルは通るが、java.time.DateTimeException: Unit must be Years, Months or Days, but was Seconds
PeriodとDurationを一つのTemporal型変数に格納するのは難しそうなので、個別に利用するほうが良さそうです。
val period = Period.ofYears(1).plusMonths(6)
val duration = Duration.ofDays(3).plusHours(12)
val date = LocalDateTime.of(2024, 1, 1, 0, 0, 0)
date + period + duration
// 2025-07-04T12:00 (1年6ヶ月と、3日12時間を加算)