閏年(うるうどし)の話題。
Twitterで見かけた話題で「西暦1年は閏年かどうかぱっとわからん人おる?」という些か煽り気味のツイートを見かけたのだけども、反射的に「閏年じゃないに決まってるじゃん」とぱっと答えてしまわないだろうか。本当にそうだろうか? そう単純な話なのだろうか?
プログラミングを学んでカレンダーを扱うことを学ぶ際に置閏法についても簡単に触れられることがある。置閏法というのは閏年や閏月(太陰暦では1年が13ヵ月になるケースがあり追加の月を閏月と呼ぶ)をどのようなルールで挿入するかという話で、まさにアルゴリズムであるからプログラミングの話題と相性がいい。
置閏法
現代の西暦の置閏法(ちじゅんほう)は
といった手続きで閏年(つまり2月29日がある)か平年かを判定することができる。
2000年問題といえば西暦を2桁で扱っていたシステムの桁溢れというのが表の問題だったが、裏の問題として閏年というのもあった。2000年は400で割り切れるから閏年なわけだが、どういうわけか100で割り切れるので平年だと判定してしまうバグが見られた。単に4で割るルールだけにしておけば結果的に2000年には問題が起きなかったのに、中途半端に100年ルールだけ追加しちゃったもんだから2000年2月29日にトラブルを出すことになったシステムがいくらかあったようだ。
やるなら半端は良くない。
その暦はいつからのものか?
そう、半端は良くない。
先に述べた置閏法は現代の西暦の置閏法であるが、この暦をグレゴリオ暦という。2014年にJava8が出るまでの古いカレンダークラスの場合(Java8以降のDate Time API の話は後述)、抽象クラスである java.util.Calendar に対して具象クラス java.util.GregorianCalendar が定義されていた。グレゴリアンの名前の通り、グレゴリオ暦をモデル化したものである。さあこれで、ここまで漠然と「西暦」と呼んでいたものが「グレゴリオ暦」という名前をもって具体化してきた。このグレゴリオ暦はローマ教皇グレゴリウス13世の名前からきている。グレゴリウス13世によってそれ以前の暦であるユリウス暦の改良が命じられ、1582年10月15日からグレゴリオ暦が行用されるようになった。
旧暦であるユリウス暦はガイウス・ユリウス・カエサルにより紀元前45年1月1日から実施されたものである。西暦1年を考える場合、当時の暦はユリウス暦であることに注意せねばならない。
西暦1年当時は閏年だったかどうかぱっとわからん人おる? 僕はぱっとわからなかった。
ユリウス暦の置閏法
ユリウス暦の置閏法は
- 西暦を 4 で割り切れる年は閏年
- 上記以外は平年
とシンプルである。なんだ、ユリウス暦だとしても100の倍数の年以外は一緒じゃないか、と安心した人も多いだろう。
ただし、上記は紀元8年以降の話である。
紀元前45年にカエサルがこの暦法を導入した際に閏年は4年に1回と決められたが、直後の紀元前44年にカエサルが暗殺された後、誤って3年に1回ずつ閏日が挿入された。この誤りを修正するため、ローマ皇帝アウグストゥスは、紀元前6年から紀元後7年までの13年間にわたって、3回分(紀元前5年、紀元前1年、紀元4年)の閏年を停止した
ユリウス暦 - Wikipedia
あぶない! 西暦1年というのはユリウス暦の導入直後の混乱期で閏年が正しく運用されていなかったようだ。
なお紀元前45年から紀元8年までの間に、どの年に閏年が置かれていたのかについては諸説ある。1999年にローマ暦とエジプト暦の両方の日付が記載された紀元前24年当時の暦が発見され研究に進展があったとのこと。日本語版Wikipediaには該当論文が示されていないが、英語版Wikipediaの記述からすると ALEXANDER JONES 氏の1999年の論文 CALENDRICA II: DATE EQUATIONS FROM THE REIGN OF AUGUSTUS (PDF)が該当のようである。このカレンダーに基づくC. J. Bennett氏の2003年の研究によれば紀元前44, 41, 38, 35, 32, 29, 26, 23, 20, 17, 14, 11, 8年が3年おきの閏年でAD 4から4年おきの置閏法へと戻ったとのことである。
暦法と紀年法
西暦1年あたりの閏年の混乱を見てきたが、そもそもカエサルの時代に「西暦を4で割る」なんて考え方はされていなかったはずである。何しろ紀元前なので。
ここで暦法と紀年法について確認しておきたい。おおざっぱに言えば紀年法は年を数える方法論で、1年をどのように捉えるかの暦と分離可能なのである。例えば日本では和暦が用いられるが、これは紀年法が和暦で、暦法としてはグレゴリオ暦を用いている。なお、日本が現代の暦になったのは明治5年(西暦1872年)11月の改暦以降のことなので、これより前の時代を扱う場合は暦が連続しないので注意が必要である。このあたりの変遷は国立国会図書館の江戸から明治の改暦が読みやすいだろう。
そもそも西暦自体が525年にローマの神学者ディオニュシウス・エクシグウスによって算出されたものである。
525年、ディオニュシウスはローマ教皇ヨハネス1世の委託を受けてキリスト教の移動祝日である復活祭の暦表(復活祭暦表)を改訂する際に、当時ローマで用いられていたディオクレティアヌス紀元(ローマ皇帝ディオクレティアヌスの即位を紀元とする)に替えて、イエス・キリストの受肉(生誕年)の翌年を元年とする新たな紀元を提案した。これはディオクレティアヌスがキリスト教の迫害者であり、その迫害者の名を残す事が疎まれたからである[3]。
西暦 - Wikipedia
つまり、西暦525年から遡って元年を決めたわけである。なお、ディオニュシウスの推定したキリストの誕生年と、ナザレのイエスの実際の誕生年にはズレがあるとされており、紀元前4年頃に生まれたと考えられている。これはヘロデ大王の治世(紀元前37年 - 紀元前4年)の末期に生まれたという記述による。
では、西暦525年よりも前の時代の紀年法はディオクレティアヌス紀元で遡ればいいかというと、ディオクレティアヌス紀元の元年も西暦284年までしか遡れない。西暦1年は遠いのである。ディオクレティアヌス紀元以前はローマ建国紀元(Ab urbe condita, AVC/AUC)が用いられていたようである。ローマ建国紀元の元年は、紀元前1世紀のローマ人ウァッロによって推定された、都市国家ローマの建国年(紀元前753年)ということである。これもまた、推定で過去に遡って元年が定められたパターンである。さらに前は執政官ふたりの名前を挙げて表したようだ。(紀年法や暦法についてはここで取り上げたもの以外もたくさんあるということは付記しておく)
さて、ここで何が言いたいかというと「西暦を4で割りきれるなら閏年」というのはたまたまであるということ。神学者ディオニュシウスの推定で定められた西暦の紀元からの数字だとたまたま4で割りきれる年が閏年の年となった。後付けである。その西暦をもとに1582年に定められたグレゴリオ暦が、閏年の調整のためにきりがよい100で割りきれる年や、400で割り切れる年を用いた、という後付け設定によるものだ、という点である。
グレゴリオ暦の前、ユリウス暦は「西暦を4で割りきれる年を閏年をする」と定められたわけではなかった。ユリウス暦が定めらたのは紀元前のことで、初期には閏年が誤って3年おきに入れられたりその調整で閏年がない期間があったりした。それから500年余りたった頃に後付けで西暦が定められた。そうすると、たまたまユリウス暦の閏年が西暦を4で割り切れる年だった。しかし単純に4年1度の閏年では春分の日がだんだんずれてきて西暦1582年にグレゴリオ暦に改められ、閏年の100年ルールと400年ルールが生じた。西暦を100で割る、400で割る、というのは後付けの西暦の切りのいい数字を用いたに過ぎない。
なお、1582年から施行されたグレゴリオ暦の暦法を、1582年以前にも適用したものは proleptic Gregorian calendar と呼ばれるようだが、日本語では定訳がなく、先発グレゴリオ暦、遡及グレゴリオ暦、予測的グレゴリオ暦、予期的グレゴリオ暦などと訳されるようだ。
曜日
400年間のうち閏年は97日あるため、日数は 365 × 400 + 97 = 146,097 日 となる。これは7で割ることができ400年で20,871週間あることになる。グレゴリオ暦は曜日も含めて400年周期の暦と言える。
曜日の算出にはツェラーの公式が用いられることがあるが、このツェラーの公式を用いた曜日計算も1582年10月15日のグレゴリオ暦への切り替え以前に適用できないことは注意が必要である。計算することはできても、それは実際にその時代に用いられた暦ではないナニカ(先発グレゴリオ暦)である点は理解しておきたい。
ユリウス暦とグレゴリオ暦との間には約10日のズレが生じていた。切り替えに際してこの10日のズレを調整したわけだが、曜日については連続した形で適用された。つまり、ユリウス暦での 1582年10月4日(木曜日)の翌日がグレゴリオ暦 1582年10月15日(金曜日)とされた。
また、切替えはヨーロッパ各国で一斉に行えたわけではなく、イタリア、スペイン、ポルトガルなどごく少数の国であったという。ローマ教皇による発令だったが、ローマ教皇というのはカトリックのトップであって、プロテスタントやその他のキリスト教派閥にまでその威光が通じたわけではなかったのだろう。その後、徐々に広まっていったようである。
Javaの実装
1995年の初期のJavaから存在するjava.util.Dateクラスは、当初は日付を表す文字列をパースしたりする機能も有していたのだが、1997年のJava1.1にはこれらの機能は早々に非推奨(Deprecated)になってしまっている。そうした機能はjava.util.Calendarに分離された。java.util.Calendarはカレンダーの抽象クラスで、グレゴリオ暦に相当するのはjava.util.GregorianCalendarである。
Java8 (2014年)が出た際にはDate and Time APIというものが追加された。(JSR 310: Date and Time API)
Java8以降でプログラミングするのであれば概ね新しいDate and Time APIを使いたいところだ。
さて java.util.GregorianCalendar だが、
歴史的に、グレゴリオ暦を最初に採用した国々では、1582年10月4日(ユリウス歴)のあとに1582年10月15日(グレゴリオ歴)が続きました。 このカレンダはこれを正確にモデル化しています。 グレゴリオ暦への切換え日の前は、GregorianCalendarではユリウス暦を実装しています。 グレゴリオ暦とユリウス暦の唯一の違いはうるう年のルールです。 ユリウス暦は4年ごとにうるう年を指定しますが、グレゴリオ暦では、400で割り切れない世紀の初年をうるう年にしません。
GregorianCalendarは、先発グレゴリオ暦およびユリウス暦を実装します。 すなわち、日付の計算では、現在のルールを無限の過去あるいは未来に向けて適用します。 このため、GregorianCalendarはすべての年について一貫した結果を生成するために使用できます。 ただし、GregorianCalendarを使用して得られた日付は、歴史的に、現代と同様のユリウス暦が採用されたAD 4年3月1日以降の日付だけが正確です。 この日付より前には、うるう年のルールは不規則に適用されており、BC 45年以前にはユリウス暦は存在さえしていませんでした。
GregorianCalendar (Java SE 11 & JDK 11 )
マジか!
Calendar c = new GregorianCalendar(1582, Calendar.OCTOBER, 4); System.out.print(c.getTime()); c.add(Calendar.DAY_OF_MONTH, 1); System.out.print(c.getTime());
1582年10月4日(木)の翌日が1582年10月15日(金)になっている……。
100年ルールも確認してみよう。1600年は400年ルールが適用なので、1500年と1700年で比較してみよう。
Calendar c = new GregorianCalendar(1500, Calendar.MARCH, 1); c.add(Calendar.DAY_OF_MONTH, -1); System.out.println(c.getTime()); c = new GregorianCalendar(1700, Calendar.MARCH, 1); c.add(Calendar.DAY_OF_MONTH, -1); System.out.println(c.getTime());
ユリウス暦である1500年3月1日の前日は2月29日となっている。一方、グレゴリオ暦の1700年は100年ルールが適用されて3月1日の前日は2月28日だ。
先発グレゴリオ暦のつもりでjava.util.GregorianCalendarを使うと、予期しない動きをするかもしれない。
なお、純粋に先発グレゴリオ暦として用いたい場合は setGregorianChange(java.util.Date) メソッドでグレゴリオ暦への切り替え日をより過去にすることで対処することができる。
ISO 8601
ISO 8601 で日付と時刻の表記に関する国際規格が定められている。
端的に言えばこのISO 8601は先発グレゴリオ暦で、1582年10月15日以前にも適用される。ただし、0000年から1582年の範囲は、事前に通信の送信側と受信側との間での合意がある場合にのみ使うことができるとされている。
このISO 8601に相当するのが IsoChronology である。Java8 以降の Date and Time API での標準はこのIsoChronologyとなっている。
java.time.chronoパッケージの解説によればIsoChronologyの他に
が標準で実装が提供されている。これらの暦にまで踏み込むと話が長くなりすぎるので打切り。太陽太陰暦での閏月などについては興味のある人は調べてみると面白いだろう。