Scalaでは時刻操作のためにJavaライブラリであるjoda-time(と、そのScala用便利ラッパーであるnscala-time)を使うことが多い。JVM言語の良い所だ。今回は
withZone
でタイムゾーン表記を変更する
DateTime
に対して定義されているwithZone
を使うと、ある時点での時刻の瞬間を保ったままTZを変更してくれる(いずれもUTCで見れば同じ時刻を指している)。
言い換えると、時刻を保持しつつTZの表記だけ変更してくれるメソッドである。
// build.scala libraryDependencies += "com.github.nscala-time" %% "nscala-time" % "2.32.0"
// code import com.github.nscala_time.time.Imports._ import org.joda.time.DateTimeZone // 3月13日正午(日本標準時) val dt = DateTime.parse("2023-03-13T12:00:00+09:00") // 同じ瞬間を保ったままJSTからUTCに変換する // UTCは最初から特別な値を用意されているのでこれを使う val dtUtc = dt.withZone(DateTimeZone.UTC) // => 2023-03-13T03:00:00Z // UTC以外のTZはforIDを使ってコンストラクトする // e.g. モスクワ標準時 val tzMsk = DateTimeZone.forID("+03:00") val dtMsk = dt.withZone(tzMsk) // => 2023-03-13T06:00:00+03:00
見てのとおり、日本標準時の2023-03-13T12:00:00+09:00
が、UTCの2023-03-13T03:00:00Z
、モスクワ標準時の2023-03-13T06:00:00+03:00
に変換されたが、これらはいずれも地球上の同じ瞬間を指している。
LocalDateTime
を経由してタイムゾーンを移動する
逆に、表記上の時刻を保ったままTZを変更する操作も可能だ。この操作は表記上の時間だけを維持するので、TZの変更によって「その時刻が指している瞬間」は移動してしまう。
例えば、UTC表記の2023-03-13T03:00:00Z
を時刻表記を維持しつつ2023-03-13T03:00:00+09:00
へとTZをすりかえると、同じ地球上の瞬間を指さなくなってしまう(過去に移動してしまう)。
しかし、このような操作が時として必要になるのも事実である。なんらかの理由で強制的にUTC
扱いで日付時刻情報が返される場合がそれである。
このような「TZ情報を持たない日付時刻表現」のために用意されているのがLocalDateTime
である。まとめると以下の通り。
DateTime
からTZ情報を忘却したものがLocalDateTime
toLocalDateTime
で忘却させられる
LocalDateTime
にTZ情報を付け加えた構造がDateTime
toDateTime
で再構成できる
- つまり等式風に書くと
DateTime
=LocalDateTime
+DateTimeZone
したがって、DateTime.toLocalDateTime.toDateTime(tz)
すると無理矢理TZを書き換えられる。
import com.github.nscala_time.time.Imports._ import org.joda.time.DateTimeZone // 3月13日正午(日本標準時) val dt = DateTime.parse("2023-03-13T12:00:00+09:00") dt.toLocalDateTime.toDateTime(DateTimeZone.UTC) // => 2023-03-13T12:00:00Z // => 3月13日正午(UTC)になっている
TZを書き換えた新たな時刻が生成されることを確認できた。この二者は、地球上の同一の瞬間を指していない。時刻の表記が同じだけの別の時間になっている。
構造の忘却
LocalDateTime
の説明をするとき、DateTime
からTZ情報を忘却したもの、という表現を使った。データを扱うときはこの考え方(構造が失われていないか)が大事で、「この操作で何か構造が失われていないか」「失われるとしたらそれは何か」「失われた構造は適切な方法で復元できるか」といったことを考えるようにすると、「いつのまにか情報がおかしくなる」ことを防げるように思う。
数学にも似たような話があって、例えばモノイドと半群との構造がちょっと似ている。
モノイド(Monoid) という構造は単位元と結合律という2つの構造上の制約を保持しているけれど、モノイドから単位元を奪って忘れさせると、制約として結合律だけを持つ 半群(Semigroup) という構造に格下げされてしまう。半群は制約が緩い一方、表現力はそれほど強くない。
そして、半群に適切に単位元の構造を与えることでモノイドに格上げすることができる。モノイドは制約の数が2つある一方、「何もしない」という操作を単位元と対応させられるので表現力は高い。
このように、構造を忘却する・付与するという話題はけっこう普遍的。