0
0

Java 8のPeriodとDurationメモ

Posted at

2つの日時の間隔を求めたい場合に思いつく、なんとなく似ている響きの java.time.Periodjava.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形式の文字列を解釈できます。

LocalDatePeriod で計算

指定期間の加減算

例えば 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

また、 startend で粒度が違う場合もエラーになります。

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時間を加算)
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0