Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

DateTimeのwithZoneについてのメモ / 構造の忘却という普遍的な話題

Scalaでは時刻操作のためにJavaライブラリであるjoda-time(と、そのScala用便利ラッパーであるnscala-time)を使うことが多い。JVM言語の良い所だ。今回はタイムゾーンTZ操作にまつわるメモ。

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つある一方、「何もしない」という操作を単位元と対応させられるので表現力は高い。

このように、構造を忘却する・付与するという話題はけっこう普遍的。

参考文献

https://developers.cyberagent.co.jp/blog/archives/24735/

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?