この記事は エムスリー Advent Calendar 2018 12日目の記事です。
こんにちは。エムスリー エンジニアリンググループの三浦(@yuba)です。基盤開発チームというところで各サービスチーム共用のシステムの開発保守に携わっており、そこで見つけた面白い動作を掘っていったら意外な知識にたどり着いたという話をいたします。
化けた日付はどこから来た?
あるサービスの管理画面の動作を検証していたときのことです。バリデーションの振る舞いを確かめるためにいくつかテストケースを作りながら実際の動きを試していたところ、不思議な現象を見つけました。
次のように日時入力をするところで年の欄を空のままにして送信したところ⋯
次のようにおかしな日時が設定されたのです。
0013年? 18分59秒? 入力した覚えのない数字が3つも紛れ込んでいます。 これが C で書かれたプログラムなら何か不定値を拾ってしまうバグでもあったかと疑うところですが、このサービスはScala製です。案の定動作に再現性もあります。なんらかの出所があって生成されている日時データだということです。では一体どこから来たものなのでしょう?
Joda-Timeの寛容すぎる文字列パース
このサービスは Skinny Framework ベースで作られており、Skinny Framework は日時データを Joda-Time で扱うよう作られています。
このJoda-Time、文字列をパースして日時インスタンスを生成することができるのですがこのパース仕様が非常に寛容です。
どのくらい寛容かというと、"20181201"
という文字列をパースさせてもエラーにはせず「裸の整数値は月以下が省略された日時」と解釈して 20181201-01-01 00:00:00
という約2千万年後の時刻を生成してしまうほど。
一方、フレームワークはこの日時入力フィールドのように複数のHTML入力欄からなるフィールドの入力データをいったん一本の文字列に変換した上でバリデーションを通しインスタンス化するという流れで処理します。 数値の組から直接日付時刻インスタンスを生成するのでなく Joda-Time の例の寛容な文字列パースを経ることになります。
ここで0013年の方は解決です。
空文字列の年ごと文字列結合されて"-12-01"
となった日付をパースした結果、「日が省略された -12年(=紀元前13年)1月」と解釈されていたというもの。0013というのはAD/BC表示の省略された紀元前13年でした。
18分59秒はどこから来た
では分・秒の位の18と59はどこから来た数字でしょう。
実際にJoda-TimeのDateTimeクラスに "-12-01 13:00:00"
をパースさせてみます。するとこんなインスタンスが生成されました。
-0012-01-01T13:00:00.000+09:18:59
時分秒は意図通りにパースされているのにおかしなタイムゾーンオフセットが付いています。
PC環境のデフォルトタイムゾーンはAsia/Tokyoなのに+09:18:59
とは一体どこの都市のものでしょう、いや、こんな半端なタイムゾーンオフセット値などあるものでしょうか。どんなに細かくても15分単位だったのでは?
国際子午線会議以前の地方時
+09:18:59
という重要なキーワードを手に入れたのでここからウェブ検索が捗り、知らなかったタイムゾーンの歴史に触れていくことになります。
+09:18:59
というオフセット値は tz database に収録されていました。
# Zone NAME GMTOFF RULES FORMAT [UNTIL] Zone Asia/Tokyo 9:18:59 - LMT 1887 Dec 31 15:00u 9:00 Japan J%sT
https://github.com/eggert/tz/blob/2018g/asia#L1692
1887年の大晦日まではこの数字だったとなっており、なるほど、それなら紀元前13年だってオフセットは+09:18:59
であるわけです。
では、1888年に何があったのでしょう。
この年は、日本に標準時が施行された年でした。私達のよく知るUTC+9時間の日本標準時です。 それまでは日本標準時というものはなく、都市ごとに時刻を持っていました。日本に限らず世界中がそうで、米国では100以上の地方時があったと言います。現代人の感覚ではなぜそんな不便なことをと思ってしまいますが、考えてみれば電話のようなリアルタイムの通信手段がない時代には遠隔地間で時計を合わせる術もなければ必要性もなく、むしろ各都市で頭上の太陽と星に時計を合わせるのが最も自然なことだったのでしょうね。
オフセット+09:18:59
は東京の地方時でした。
地方時といっても東京の地方時は特別で、太陽暦の基準となるなど日本標準の地位を持っていたようですが。
地方時が乱立する時代を終わらせ標準時を打ち立てるきっかけとなったのは鉄道の発展だったそうです。駅ごとに時刻のオフセットが違うのではまともに時刻表を書けないというのが動機になったのだとか。
1884年に国際子午線会議が開催されて標準時の枠組みが決まり、日本では2年後の1886年に明石を通る東経135度を標準子午線と(=+09:00:00
を日本標準時と)決定、2年間の準備期間をおいて1888年にそれが施行されたのでした。
東京の地方時は +09:18:59 だったのか?
日本の暦の歴史については国立天文台がまとめている暦Wikiが大変詳しいです。
さてこのサイトで日本の本初子午線のページを読んでいくのですが、どこにも+09:18:59
という数字が出てきません。
東京の標準子午線は当初江戸城天守台上に設定され、一時期赤坂の内務省地理局測量課上に移されてからまた江戸城天守台上に戻されるという経緯を辿り、測量技術の進歩による変化も加わったもののだいたい+09:19:01
前後の数字だったとあります。
それでは+09:18:59
という数字はどこから来たのでしょう。
この数字の典拠はtz database内に注釈で示されていました。
# 'Tokyo' usually stands for the former location of Tokyo Astronomical # Observatory: 139° 44' 40.90" E (9h 18m 58.727s), 35° 39' 16.0" N. # This data is from 'Rika Nenpyou (Chronological Scientific Tables) 1996' # edited by National Astronomical Observatory of Japan....
139° 44' 40.90" E, 35° 39' 16.0" N
の東京天文台上だというのです。
暦Wikiと同じく国立天文台がまとめた理科年表が典拠とのことですがこれはどうしたことでしょう、「天守台」と「天文台」違いだったようです。
暦Wikiには法令文書へのリンクまであって間違いはなく、とすると間違っているのはこの1996年版理科年表の方のようです。 入手する機会があったら理科年表の当該記述が何を参考文献にしているのか確かめてみたいですね。
さて、この2秒の間違いについて tz database に修正を依頼して採用されたなどという後日談があればこの記事も大変ドラマチックにまとまっていたのですが、今回の話はここまでとなります。 tz database は歴史的なオフセット情報について収録こそしているもののやはり中心的な関心事とはしていないのです。 そういうわけで実は以前からこの件に気付いていた方もいたものの、修正の話は進まずにいるという状況なのでした。
これ、当時Hideyuki Suzukiさんに連絡とってみたところ、だいぶ前の話なのでーみたいになってそこから先の攻め方がわからなくて放置してたんですよねぇ
— きしだൠ(K8S(Kishidades)) (@kis) 2018年10月17日
実際tzdataの立場としても、子午線会議よりもかなり後の1970年より前のデータについてすら、あまり正確さを保証する気はなさそうだったと記憶しています。参考情報程度、という感じで
— Dai MIKURUBE (@dmikurube) 2018年10月18日
附記になりますが、Skinny Frameworkの方にはバリデーション処理を修正するプルリクエストを送りマージしてもらえましたので最新のバージョンでは年の省略で紀元前の東京地方時が生成される前にバリデーションに引っかかるようになっております。
まとめ
- 1888年に日本標準時が施行される前は都市ごとに地方時が刻まれており、当時の東京地方時も tz database に収録されています。天文台と天守台の違いで2秒ほどずれて。
- 普段は目にすることのない日本標準時施行前の日時もJoda-Timeの寛容すぎる文字列パースによって出現することがあります。油断なりません。
We are hiring!
発見のあるお仕事一緒にしませんか。 TechTalkやカジュアル面談でお会いしましょう。