Scalaで日時の範囲を表すクラス群をつくる
きっかけ
Javaで開発をしていて、日時の範囲を表すクラスがなかったので少し不便に感じました。java.time.Period
とjava.time.Duration
は範囲というより2点間の距離に関心があるクラスなので違うんですよね。
よく使いそうなので私的メモとして残しておきます。
基底クラス
本筋とはそれますが、java.time.LocalXxx
のラッパーも用意しました。始端、終端をラッパークラスにするイメージです。
日時を表すTrait
継承先クラスは型引数で受け取り、内部で保持する値の型はtypeで受け取るというダブルスタンダードになりました。継承先クラスの情報を型引数で受け取っている理由は、Ordered
を継承する際に型引数を指定する必要があったからです。この型に大小関係がなければ、この型を始端と終端にした範囲という概念があり得ないと考えたのでTraitでOrderedを継承しました。
Chronology.scala
trait Chronology[ClassType <: Chronology[_]] extends Ordered[ClassType]{
type ValueType //LocalTimeとかが指定される想定
val value: ValueType
def isAfter(other: ClassType): Boolean
def isEquals(other: ClassType): Boolean
final def isAfterOrEqual(other: ClassType): Boolean = isAfter(other) || isEquals(other)
def isBefore(other: ClassType): Boolean
final def isBeforeOrEqual(other: ClassType): Boolean = isBefore(other) || isEquals(other)
}
範囲を表すTrait
日時の型を受け取り、受け取った型を始端と終端にもつTraitです。
始端と終端の大小関係を事前条件として指定しています。
ChronologyRange.scala
trait ChronologyRange {
type ChronologyType <: Chronology[_]
type ClassType <: ChronologyRange
val start: ChronologyType
val end: ChronologyType
require(start <= end)
def overlaps(other: ClassType): Boolean
}
使い方
例:Date
Date.scala
case class Date(value: LocalDate) extends Chronology[Date] {
override type ValueType = LocalDate
def toInstant: Instant = value.atStartOfDay(ZoneId.systemDefault()).toInstant
def isAfter(other: Date): Boolean = value.isAfter(other.value)
def isBefore(other: Date): Boolean = value.isBefore(other.value)
override def isEquals(other: Date): Boolean = value.isEqual(other.value)
override def compare(that: Date): Int = value.compareTo(that.value)
}
DateRange.scala
case class DateRange(start: Date, end: Date) extends ChronologyRange {
override type ChronologyType = Date
override type ClassType = DateRange
override def overlaps(other: DateRange): Boolean = ???
}
学び
- Traitにもフィールドを宣言できること
- Traitにもイニシャライザを書けること
- Orderedを継承したクラスのインスタンス同士を不等号で比較できること
あとで調べること
- typeとtype parameterってどう違うの
- TraitとAbstract Classってどう使い分けるの