概要

1582年10月5日〜1582年10月14日までの10日間は、何らかの自然現象(ゴゴゴゴゴ)によって時間が消し去れてた期間として知られています。プログラミング言語を使ってこの日を取り扱おうとすると、いろんな結果が出力されます。

今日はそんな素敵な日付である1582年10月5日と戯れて、貴重な1日を無駄にしてみたいと思います。

@CretedDate 2013/09/23 @author mwSoft

Java

とりあえずJavaから。バージョンは7。

// 1582/10/05をパース
Date dt1 = new SimpleDateFormat("yyyy/MM/dd").parse("1582/10/05");
System.out.println(dt1);

// 1582/10/04をパース
Date dt2 = new SimpleDateFormat("yyyy/MM/dd").parse("1582/10/04");
System.out.println(dt2);

// 1582/10/04の1日後を見てみる
Date dt3 = new Date(dt2.getTime() + 24 * 3600 * 1000L);
System.out.println(dt3);
結果
dt1 = Fri Oct 15 00:00:00 JST 1582
dt2 = Thu Oct 04 00:00:00 JST 1582
dt3 = Fri Oct 15 00:00:00 JST 1582

1582/10/05をパースすると、Oct 15、つまり1582/10/15として解釈されました。1982年10月5日なんて日付はこの世に存在しないので、15日として扱われたわけです。当然の挙動ですね。10月4日に1日を足しても、やはり10月15日になります。

じゃ、6日をパースするとどうなるかというと、こうなりました。

Date dt = new SimpleDateFormat("yyyy/MM/dd").parse("1582/10/06");
System.out.println(dt);
結果
dt = Sat Oct 16 00:00:00 JST 1582

Oct 16になってます。4日→4日、5日→15日、6日→16日、7日→17日 ・・・ 14日→24日、15日→15日 となるようです。10月14日の方が10月15日より未来になるあたりは面白いですね。

ちなみにJavaの日付処理ライブラリのJoda(身長66cm)だと結果は変わってきます。下記のように1582/10/05をパースすると、普通に1582/10/05が返されます。

DateTime dt1 = DateTimeFormat.forPattern("yyyy/MM/dd").parseDateTime("1582/10/05");
System.out.println(dt1.toString("yyyy/MM/dd"));

Date dt2 = dt1.toDate();
System.out.println(dt2);
結果
dt1 = 1582-10-05T00:00:00.000+09:18:59
dt2 = Mon Sep 24 23:41:01 JST 1582

しかもJodaのDateTimeからgetDate(JavaのDateを返してくれるメソッド)したら、9/24 23:41:01とかになってますね。この23:41とかいう中途半端な時間は、歴史的なタイムゾーンのせいですね。1887年以前は日本のタイムゾーンは+9:18:59だったので。

JodaTimeの時間の扱いはISO8601準拠だそうです。Chronologyを指定すれば挙動を揃えられるはず。

Ruby

続いてRubyいってみましょう。バージョンは2.0.0を使用。

require 'date'
Date.parse("1582/10/05")
結果
ArgumentError: invalid date

1582/10/05をパースしようとしたら引数がおかしいぞエラーが出ました。つまり「そんな日付ねーよ」と言われてるわけです。10/04や10/15は普通にパースできますが、10/05〜10/14はエラーになります。確かにそんな日付は存在しないので、当然の振る舞いですね

じゃ、10月4日に1日足してみましょう。

d = Date.parse("1582/10/04")
(d + 1).to_s
結果
1582-10-15

Javaの時と同じく、1582年10月4日の翌日は、1582年10月15日になりました。

ちなみにRailsのプロジェクトをscaffoldして日付に1582年10月5日を入れたところ、1582年10月15日で登録されました。10月6日だと10月16日。「このエラー使えばRailsのサイト落とせるんじゃ?」と期待した皆さん。ご期待に添えず申し訳ない。

なぜこうなるのか

いつまでも自然現象とかわけのわからないことを言ってると怒られそうなんで、知ってる人も多いと思いますが、この現象について簡単に説明。

昔々、ユリウス暦と呼ばれる暦(こよみ)がありました。ユリウス・カエサル(ジュリアス・シーザー)のユリウスです。ユリウス暦は4年に1度閏年を入れるというシンプルな仕組みで、精度はあまりよくありませんでした。具体的には128年で1日分くらい実際の暦とズレが出ました。

で、1582年くらいにさすがにズレが激しくなってイカンだろうということでグレゴリオ暦に変更になりました。今使われている4年に1度閏年で100年に1度閏年スキップがあって400年に1度やっぱスキップしない年があるアレです。

導入された日が1582年10月15日でした。その際にズレを矯正する為に、10月5日〜10月14日まではすっ飛ばされました。

グレゴリオ暦の導入日は国によって違っていて、日本なんかは明治維新後の1873年です。まぁ、日本の場合はそれ以前も暦をバンバン変更してましたしプログラムでその辺を意識することなんてないと思いますが、ロケールによって暦が変更されるタイミングを分けてるプログラムもあるんだっけか。

1582年10月4日以前はユリウス暦を適用している言語もあれば、先発グレゴリオ暦(ISO8601)を採用してすべてグレゴリオ暦として扱っているものもある。そうすると過去の日付を違う言語で扱った際にズレが出るようになる。日本の場合、1887年以前にタイムゾーンの違いもあるし、1900年より前の日付を扱う場合はいろいろ考えないといけないことが多い。ISO8601とグレゴリオ暦では世紀の扱いが違ったりとか、ユリウス暦は日付の加算が正午だったりとか、そういう話もあるので真面目に考え始めると死にたくなる。

データベースによっても扱いは変わるので、言語とDBの間で日付の解釈が違ったりするとどうなるんだっけか。とりあえずMySQL、PostgreSQL、SQLiteあたりは先発グレゴリオ暦だったはず。

ということでまとめると、暦(こよみ)は面倒くさいです。もういいや、Stringとvarchar(8)で。

閏年の扱い

ユリウス暦とグレゴリオ暦では閏年のタイミングが違う。特に100で割り切れ且つ400で割り切れない年に閏年が発生しないところが違う。

下記のようにJavaのCalendarとJodaで1000年の閏年を確認すると、Javaはtrue(閏年である)、Jodaはfalse(閏年ではない)と結果が分かれる。ユリウス暦ではtrueだし、グレゴリオ暦ではfalseになるので。

String ymd = "1000/01/01"; 

// JavaのCalendarで1000年は閏年か調べる
Date d = new SimpleDateFormat("yyyy/MM/dd").parse(ymd);
Calendar cal = Calendar.getInstance();
cal.setTime(d);
System.out.println(cal.getActualMaximum(Calendar.DAY_OF_YEAR) > 365);

// Jodaで1000年は閏年か調べる
DateTime dt = DateTimeFormat.forPattern("yyyy/MM/dd").parseDateTime(ymd);
System.out.println(dt.year().isLeap());
結果
Java : true
Joda : false

Rubyだとこうなった。Date.leap? はgregorianを使っている? でも、Date.parse('1000/02/29')は普通にパースできるのだけど。

Date.leap?(1000)
=> false

Date.julian_leap?(1000)
=> true

Date.gregorian_leap?(1000)
==> false

Python

バージョンは手元に入ってた3.2.3使用。

from datetime import datetime
datetime.strptime('1582/10/05', '%Y/%m/%d')
結果
datetime.datetime(1582, 10, 5, 0, 0)

というわけで、Pythonの場合は特にすっ飛ばしたりせずに10月5日になるようです。JavaのJodaの時と一緒ですね。

じゃ、10月4日に1日足すとどうなるか。

from datetime import datetime
from datetime import timedelta
d1 = datetime.strptime('1582/10/04', '%Y/%m/%d')
d2 = d1 + timedelta(days=1)
結果
datetime.datetime(1582, 10, 4, 0, 0)
datetime.datetime(1582, 10, 5, 0, 0)

普通に10月5日なりました。

見ての通りPythonはグレゴリオ暦を利用しているようなので、1000年が閏年か判定すると当然falseになる。

import calendar
calendar.isleap(1000)
結果
False

JavaScript

Firefox25.0で実行。

var dt = new Date( Date.parse("1582/10/05") );
alert( dt );
結果
Tue Oct 05 1582 00:00:00 GMT+0900 (LMT)

普通に1582年10月5日でパースされました。

閏年を確認しても、false。

alert( new Date(1000, 1, 29).getMonth() == 1 );
結果
false

PHP

PHPもPythonやJavaScriptと同じですね。

$dt = date_parse('1582/10/05');
print( $dt['year'] . '/' . $dt["month"] . "/" . $dt["day"] );
結果
1582/10/5

疲れた

疲れたので今回はこの辺で終了。ホントはDBも実演しようかと思ってたのだけど、ドキュメント見て結果はだいたい読めてるからいいや。

結論としては、面倒だから1990年以前の日付は扱わないようにしようそうしよう。