Date and Time API と ISO 8601 について

@khasunuma 氏による Date and Time API と ISO 8601 のツイートまとめ
0
HASUNUMA Kenji (Deprecated) @khasunuma_old

Date and Time APIを説明するにISO 8601にまで踏み込むのは蛇足だという方は非常に多く、過去にJava界隈の重鎮から叩かれたことがあります。しかし、過去のセッションやブログを見る限り、ISO 8601には触れずに解説したものは、ほぼ例外なく爆死しています。

2015-05-09 05:11:10
HASUNUMA Kenji (Deprecated) @khasunuma_old

Date and Time APIがISO 8601ベースになったのは、先行したJoda-Timeが同様にISO 8601ベースにしてうまくまとまったから。Stephenは暦が無数にあることを承知の上で、ISO 8601という国際規格を「人間向け」の基準と定めたのです。

2015-05-09 05:13:45
HASUNUMA Kenji (Deprecated) @khasunuma_old

コンピュータ内部では、Unix Timeに近い実装が使用されていますが、それをISO 8601準拠のAPIで隠蔽することで、プログラマはクロックの内部実装を意識せず使えるようになったのです。

2015-05-09 05:16:26
HASUNUMA Kenji (Deprecated) @khasunuma_old

ISO 8601は区切り符号を勝利訳することが可能で(むしろ区切り符号を入れないのが標準)、それ故フィールドの桁数には非常に敏感です。DateTimeFormatterでよく事故るのは、APIが不親切なわけではなく、あくまでISOの仕様にAPIが従っているためです。

2015-05-09 05:18:27
HASUNUMA Kenji (Deprecated) @khasunuma_old

DateTimeFormatterの一件不親切な仕様を含め、これはすべて「ISO 8601でそう決められているから」です。そこは「国際標準」による決めごとなので、外野がどうこう言える立場ではありません。それにISO 8601はその出自から「電文」内で使われることを想定しています。

2015-05-09 05:22:10
HASUNUMA Kenji (Deprecated) @khasunuma_old

MQ(あるいはそれに寄生するCOBOL)に触れた経験があれば分かりますが、現在主流の電文は固定長レコードで、フィールド桁数に厳格なISO 8601はそれに適合します。XML電文なら可変長フィールドも使えますが、フォーマット/パースの手間を考えると固定長の方が楽です。

2015-05-09 05:24:49
HASUNUMA Kenji (Deprecated) @khasunuma_old

一方Javaは、可変長フィールドが普通に使われているため、'2015/5/9'がパースエラーになるのは許容しがたいかもしれません。しかし、ISO 8601はJavaだけのものではなく、言語非依存の「標準規格」であるため、他システム連携を考えた時には素直に従った方が良いです。

2015-05-09 05:29:04
HASUNUMA Kenji (Deprecated) @khasunuma_old

他システム連携は大規模システム(特にメガバンクのシステムに多い)では必ずと言って良いほど発生するため、言語非依存の標準規格の存在は重要です。仮に自分がアサインされた案件がJava EEであっても、CICS経由でz/OS上のCOBOLと連携するとか、日常茶飯事ですから。

2015-05-09 05:31:32
HASUNUMA Kenji (Deprecated) @khasunuma_old

僕がSI案件やっていた頃、国際送金ではMT電文と呼ばれる固定長電文がシステム間連携で使われていました。現在、MT電文は同様の内容をXMLで表したMX電文に移行中ですが、XMLが採用した日付・時刻形式はISO 8601です。Javaで言うところのOffsetDateTime相当。

2015-05-09 05:35:12
HASUNUMA Kenji (Deprecated) @khasunuma_old

余談ですが、国際的にはMX電文への移行は概ね完了しています。国内は外為取り扱い最大手の三菱東京UFJ銀行のMX電文対応が後手に回っているため、いまだにMT電文が現役です。

2015-05-09 05:38:30
HASUNUMA Kenji (Deprecated) @khasunuma_old

話を戻しますが、java.util.Dateはスレッドセーフでない、フィールドの直接操作が面倒、日付と時刻が分離されていない等の他、ISO 8601による他システム連携が面倒という弱点もあります。もっともJava 7でISO 8601のフォーマッタは付きましたが。

2015-05-09 05:44:35
HASUNUMA Kenji (Deprecated) @khasunuma_old

ただし、java.util.Dateをすべて責めるわけにはいかないのも事実。Javaの初版が登場したとき、この世にISO 8601は存在せず、Cのtime_t構造体をベースにDateを作りました。ただ、ISO 8601が世に出ても従わなかったのは罪だよ。

2015-05-09 05:47:51
HASUNUMA Kenji (Deprecated) @khasunuma_old

Cのtime_t構造体をご存じの方なら分かるはずですが、アレはEpoch(=1970-01-01T00:00:00Z)からの秒数をカウントしたlong変数が内部実装で、そこから年月日を算出するもの。まず年月日ありきで仕様を決めたISO 8601とは根本的に違います。

2015-05-09 05:52:07
HASUNUMA Kenji (Deprecated) @khasunuma_old

JSR 310の場合、システムクロックとの橋渡しとしてInstantクラスを用意していて、こいつがEpochからのナノ秒単位の経過時間を保持しています。ThreeTen Extraではうるう秒込みのUTCや厳格なTAIとの相互変換も可能です。

2015-05-09 05:54:26
HASUNUMA Kenji (Deprecated) @khasunuma_old

個人的に今まで使ってきた感じでは、現在のAPI仕様ではInstantはデータ型間(特にJSR 310とDateの相互変換)に使うのが中心で、後は単体テスト用のFixedClockを生成する際に作る程度。業務コードで使うことはまずありません。

2015-05-09 05:56:38
HASUNUMA Kenji (Deprecated) @khasunuma_old

業務コードで使用するのは多くがLocalDateで、特に国内向けシステムではそれしか使わないと言っても良いくらい。Date相当の情報を持つからとZonedDateTimeに統一する派閥があるけど、あれはやめましょう。ZonedDateTimeは取り回しが効かないです。

2015-05-09 05:59:47
HASUNUMA Kenji (Deprecated) @khasunuma_old

ZonedDateTimeの取り回しの悪さは、OpenJDK 8入り直前のリファクタリングでユーティリティメソッドをLocalDateに集中させた結果です。結局ZonedDateTime.toLocalDate() の使いどころが増え、だったらはじめから…というわけ。

2015-05-09 06:01:34
HASUNUMA Kenji (Deprecated) @khasunuma_old

時差が発生するとき、だいたいのシステムではZonedDateTimeを採用するけど、ちょっと考えよう。ZonedDateTimeが参照するtzdbは頻繁に更新されていて、Oracle JDKでは四半期に一度tzdbの最新版を取り込んでいるのが現状(JSR 310以前もそう)。

2015-05-09 06:05:32
HASUNUMA Kenji (Deprecated) @khasunuma_old

ここで問題となるのは、担当するシステムが四半期に一度Javaとtzdbをきちんとアップデートするようスケジューリングできるか?またそのスケジューリングでシステムの挙動に支障はないか?ということ。このいずれかが「否」であればZonedDateTimeは使用すべきではないです。

2015-05-09 06:07:30
HASUNUMA Kenji (Deprecated) @khasunuma_old

対案としては、OffsetDateTimeを採用した上で、外部から夏時間等のシフト情報を供給されるTemporalAdjuster実装を作成し、それを適用することになるでしょう。面倒だけれども、要求仕様によってはそうせざるを得ないことも頭の片隅に置いておいた方がよさそうです。

2015-05-09 06:10:42
HASUNUMA Kenji (Deprecated) @khasunuma_old

ちなみにLocal-系とOffset-系はISO 8601の表現をそのままモデリングしたもので、Zoned-系はJSR 310の独自拡張です。他システム連携でZoned-系をそのまま使用したら事故る可能性大なので、気をつけましょう。

2015-05-09 06:13:00
HASUNUMA Kenji (Deprecated) @khasunuma_old

あ、そうそう、低水準APIの役割について。 TemporalAdjuster - plus-系/minus-系ではカバーできない高度な日付演算を行う TemporalQuery - get-系よりも細かい&自由な粒度でフィールド値を取得する

2015-05-09 06:19:06
HASUNUMA Kenji (Deprecated) @khasunuma_old

去年の3月のJava 8ローンチ・イベントで僕がTemporalAdjusterとTemporalQueryをスルーしたのは、JIS X 0301読んでて準備時間が不足したのもあるけど、この2つは知らなくても最悪生きていけることを知っていたから。

2015-05-09 06:21:10
HASUNUMA Kenji (Deprecated) @khasunuma_old

TemporalAdjustersユーティリティを全く紹介しなかったのは問題だったかもしれないけど、実業務ではTemporalAdjustersだけでは足りないケースがたくさん出てくるので、あれだけ紹介してもあまり意味はないと思ってます。まあ、知らないよりはマシだけど。

2015-05-09 06:23:37
HASUNUMA Kenji (Deprecated) @khasunuma_old

Date and Time APIのさわりを知りたい場合は、手前味噌ですがこの辺がオススメ。一応、Java 8の日付・時刻の取り扱い全般に関する変更点も解説(ただし古い!) slideshare.net/btnrouge/jsr31…

2015-05-09 06:33:30