Help us understand the problem. What is going on with this article?

古い日付を扱うのはめんどくさいという話

概要

一般的に、プログラミングにおける日付の処理はけっこう面倒な部類の処理だと思いますが、古い日付を扱う場合はもっと面倒になるよという話をします。
サンプルコードはJavaで書いていますが、他の言語でも同様の問題は起こると思います。

動作環境

  • Java11
  • JShellを使用

本文

ケース1:タイムゾーンオフセットが「+09:18:59」になる

LocalDateTime.of(1887, 1, 1, 0, 0).atZone(ZoneId.of("Asia/Tokyo"));
// 1887-01-01T00:00+09:18:59[Asia/Tokyo]

日本標準時のタイムゾーンオフセットは基本的に「+09:00」なのですが、1887年を指定すると、なんとタイムゾーンオフセットが「+09:18:59」となっています。
秒も含まれていて一般的なフォーマットと違うため、たとえばこの値をサーバで生成してクライアントに返すなどした場合、フロントエンド側のJavaScriptのコードでパースした時に失敗するといった問題が起こり得ます。(起こりました)

原因

tz databaseで実際そうなっているためです。tz databaseとは、名前の通りタイムゾーン情報のデータベースです。JavaではJREに同梱されており、Javaで日時を扱うライブラリから参照されています。

現在の日本標準時は1888年1月1日から始まっており、その前は違ったので、tz databaseにそのことが反映されているということです。

ケース2:日付が10日間すっ飛ぶ

旧APIとJava8以降の新APIで結果が変わるという例です。

まずは旧APIから。

var cal = Calendar.getInstance();
cal.clear();
cal.set(1582, Calendar.OCTOBER, 4);

var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.format(cal.getTime());    // "1582-10-04 00:00:00"

1582/10/4を指定し、それがそのまま出力されました。では新APIではどうなるのでしょうか?

var cal = Calendar.getInstance();
cal.clear();
cal.set(1582, Calendar.OCTOBER, 4);

LocalDateTime.ofInstant(cal.toInstant(), ZoneId.of("Asia/Tokyo"));
// 1582-10-14T00:18:59

なんと、10日間もずれてしまいました。

原因

ライブラリによって、1582年10月15日より前の扱いが異なるためです。1582年10月15日は特別な日付で、現行のグレゴリオ暦が導入された日です。
それよりも前はユリウス暦という暦が使われていました。それで、1582年10月15日より前の日を実際通りにユリウス暦として扱うライブラリと、擬似的にグレゴリオ暦(先発グレゴリオ暦)として扱うライブラリに分かれていて、Javaにおいては旧APIが前者、新APIが後者と違いがあるため、結果が変わってしまったということです。

また、ユリウス暦からグレゴリオ暦への移行は、ユリウス暦における1582年10月4日の翌日を、グレゴリオ暦の1582年10月15日とする形で実施されました。つまり実際に10日間すっ飛んだので、その差が日付ライブラリの処理結果としてそのまま出たわけです。

ケース3: タイムゾーンオフセットが1時間ずれる

LocalDateTime.of(1950, 8, 1, 0, 0).atZone(ZoneId.of("Asia/Tokyo"));
// 1950-08-01T00:00+10:00[Asia/Tokyo]

日本標準時のタイムゾーンオフセットは基本的に「+09:00」です。(2回目)
しかし、この結果だと「+10:00」となっています。

原因

ケース1と同じなのですが、tz databaseが実際そうなっているからです。
時事ネタですが、少し前に東京オリンピックのためにサマータイムを導入しようと騒がれていたことがありますよね。その時にニュースなどを見て知っている方も多いかもしれませんが、日本でも1949年〜1951年頃、実際にサマータイムが導入されていました。
そのことが忠実にtz databaseに反映されているわけです。

まとめ

  • 日本標準時では、日付が古い場合はズレが発生したり、タイムゾーンオフセットが変わったりする可能性がある
    • 1888年より前の日付ではタイムゾーンオフセットが「+09:18:59」になる
    • 1582年10月15日より前の日付の処理結果がライブラリによって変わる
    • 1949年〜1951年まではサマータイムが導入されていたため、タイムゾーンオフセットが「+10:00」になる
  • これらを考慮するのは面倒なので、可能であれば扱わずに済むようにすべき
    • Webシステムなら一定より古い日付は入力できないようにするとか
  • どうしても扱う必要がある場合は、日付処理のライブラリの仕様をよく確認する
    • ユリウス暦か、先発グレゴリオ暦か
    • 特にJavaは、旧APIと新APIで異なるので要注意

参考リンク

http://nowokay.hatenablog.com/entries/2015/01/09
https://ja.wikipedia.org/wiki/Tz_database
http://www.mwsoft.jp/programming/other/time_mendoi.html
https://qiita.com/sota2502/items/e8df68d9cfebd01af809

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした