この記事は、Java Advent Calendar 2019 22日目の記事になりました。
Javaとか言いつつJavaScriptやKotlinの話が出てきますが、分量的にはDateTime APIの話が主なので大体Javaです。
解決したい問題
以下の記事の通り、JavaScriptのDate型には多くの罠があります。
日本人であれば「日時を送信したら1日巻き戻って届いた」的な状況は経験された方も多いのではないでしょうか?
そんな罠だらけのDate型を安全に送信/解釈します。
解決方法
ものすごくざっくり言うと、規格に則って文字列として送信し、規格に則って文字列から解釈すれば安全に取り扱えます。
今回は例として、ISO 8601規格に則って処理を行います。
具体的には以下を用います。
-
Date.prototype.toISOString()メソッド -
DateTime APIのZoned系クラス
補足
Date.prototype.toISOString()メソッドは大体の環境で動きますが、DateTime APIはJava8以降のサポートです。
やり方
送信側
以下で生成したdateStrを送信すればOKです
const date = Date.now();
// ISO 8601で、タイムゾーンがUTCの文字列
const dateStr = date.toISOString();
受信側
まずZonedDateTimeとして解釈して、そこからLocal系の内容に変換するのがオススメです。
ZonedDateTime.parseメソッドはDate.prototype.toISOString()で生成した文字列を直に解釈できます。
val dateStr = /* toISOString()した文字列 */
// タイムゾーン付き日時型
val zonedDateTime = ZonedDateTime.parse(dateStr)
タイムゾーンを設定する
型としてはZonedDateTimeに解釈した時点で安全に取り扱えますが、この方法では受け取った時点でのタイムゾーンがUTC扱いになるため、そこから更に何か扱う前にローカルのタイムゾーンに変換した方が良いでしょう。
タイムゾーンのセットは、ZonedDateTime::withZoneSameInstantメソッドで行えます。
このメソッドの引数はZoneId型で、ZoneId.systemDefault()を呼び出すことでシステムのタイムゾーンのZoneIdを得ることができます。
val original: ZonedDateTime
val zoned = original.withZoneSameInstant(ZoneId.systemDefault())
この処理を終えれば、後は自由に扱うことができます。
補足として、日時を厳密に扱いたいのであれば、JVM内では日時全てをZonedDateTimeとして扱い、ゲッター等で必要に応じて変換するというのがオススメです。
終わりに
今回は日時(Date)を安全に送信する/解釈する方法についてまとめました。
日時に限らず相対的な値は扱いがとても難しいですが、規格に則ることで安全に扱えるようになります。
この記事が相対的な値の扱いのお役に立てば幸いです。