グレゴリオ暦/ユリウス暦計算用C言語のソースコード・
ライブラリ Calendrier を販売しています.
(\1,000 (個人用ライセンス) ほか)
Calendrier リファレンス (Ver.1.0) 公開しました.
このページの主な更新は Blog でお知らせします.
このページでは, グレゴリオ暦またはユリウス暦の日付と,ユリウス日などの通算日数 (通日) を相互変換するアルゴリズムについて説明する. これらは1989年頃に自分で考えたアルゴリズムを再度整理したものである.
一応「ユリウス日」と書いてはいるが,これは通算日数の代表として挙げているだけであり, 基準日注1 (通算日数の第0日とする日付,epoch) は自由に選択できるので,ユリウス日 (Julian Day,JD) や 修正ユリウス日 (Modified JD, MJD) 以外にもさまざまな通算日数を使用することも可能である.
なお国語辞典で「通日 (つうじつ)」を引くと, 「1月1日から通して数えた日数」とあるが, この意味での通日はこのページでは「年内通算日数 (年内通日),the day of the year,day-of-year」と呼ぶことにする (ただし1月1日以外を第0日とすることもある).
OS の API やプログラミング言語の標準ライブラリを使うと日時の計算が簡単にできるが, 日付と時刻を一つの整数型変数で扱うため,表現できる日付の範囲が狭い. そのうえ基準日よりも過去の日時は扱えないものが多く, 天文学や歴史上の日付には使えない.
例えば UNIX 系 OS やC標準ライブラリの (32/64 ビット)
time_t 型で用いられる
UNIX Time
は,1970/01/01(木) 00:00:00 UTC (いわゆる UNIX 紀元) 以後の日時しか扱えない.
正確に言うと,time_t は符号付なのでそれ以前
(32ビット2の補数形式ならば 1901/12/13(金) 20:45:52 UTC 以後)
の日時も表現可能なはずだが,
処理系によっては time_t<0だとライブラリ関数がエラーにしやがる
(参考).使えんやっちゃ.orz
さらに32ビット time_t 型の場合,
2038/01/19(火) 03:14:07 UTC までしか扱えない.
また,そのような日時データ型の基準日は西暦紀元後であり,
負の西暦年を扱うことは想定していないはずである.したがって,
例えばC標準ライブラリのソースを入手して日付 ⇔ 通算日数変換アルゴリズムを流用した場合,
西暦年が負の場合に正しい結果を返さないおそれがある.
他のサイトで公開されている日付計算プログラムについても同様.
それらを入手して紀元前の日付を計算したい場合には,まず負の西暦年
(−4年 (=紀元前5年) 以前) について結果が正しいかを確認する必要がある.
(参考:ソースをざっと眺めて負の西暦年に対応しているか否かを判断するには,
"floor"
と名の付く関数を使用しているかどうかを調べればまず間違いないだろう.)
ここで説明するアルゴリズムは,負の西暦年でも正しい結果を返す.
日付 ⇔ 通算日数相互変換と聞いて誰でもすぐ思いつく方法は,
簡単でわかりやすいが, 起算日からの日数分だけループを回すので効率が悪すぎる. もっとも最近の PC の性能ならば, 数百年分の「日めくり」程度なら1秒以内でできそうだから, 「日付電卓」的な用途ならこれでも十分だろう. しかし,膨大なログファイルや天文学・ 歴史データベースなどに含まれる無数の日時データを変換するのには到底使えない. まして,リアルタイム系など論外 (そもそもそんな遙か過去/未来の日付をリアルタイムで扱う用途があるのかというツッコミはさておき).
他方,ここで説明するアルゴリズムは日数にかかわらず, 一定時間以内 (乗除算数回程度) で計算できるので,リアルタイム系でも使用可能.
余談:「日付 ⇔ 通算日数相互変換」アルゴリズムを考えた少し後の1990年頃,
某パソコン通信の Pascal 掲示板で「ある日付からN日 (数百年) 後の日付を求める方法」
について質問が書き込まれ,何件もの回答が投稿されたが,
揃いも揃って「日めくり」方式だった.(笑)
肝心のアルゴリズムがダメすぎるのに,
小手先のプログラミング・テクニックやソースのきれいさを自慢されてもな〜〜.
質問者も少々呆れているようだった.
(ちなみに私は Pascal はほとんど使っていないので ROM だった.orz
でも,C の掲示板には暦計算ライブラリをアップしたよ.)
グレゴリオ暦から修正ユリウス日を計算するフリーゲルの公式の中の floor(30.59 * (m - 2)) + d は, 3月1日を第1日とするm月d日の年内通算日数+30を求める式である. これはグレゴリオ暦とユリウス暦における月の日数配分を利用した巧妙な計算式だが, これらの暦にベッタリ依存しており,他の暦でも同様の方法が使えるとは限らない (というより普通は使えない) dirty hack である.
年内通算日数を計算するには,各月1日の通算日数の配列を用意すれば,
(特に乗除算回路や浮動小数プロセッサのない CPU だと) フリーゲルの dirty hack
よりもこちらの方が少し速くなる可能性もある.
いや,そのことよりもその方がわかりやすいし,
他の暦でもアルゴリズムが流用できることの方が重要.
(余談:私も昔,
数学者か理論物理学者になりたいと思っていた頃は
何でも数式で表現しようとしてたからフリーゲルの気持ちはわかるつもりだけど,
ソフト屋になってからは何でも (数式よりも一般的な)
アルゴリズムで表現しようとしている.(笑))
なおアルゴリズムの流用性という点では, 年内通算日数よりも年とユリウス日をどう相互変換するかの方がずっと重要である. グレゴリオ暦とユリウス暦以外の暦はよく知らないのでまだ断言はできないが, どんな暦にも (多重) 周期性があるはずであり, ここで説明するアルゴリズムは (多重) 周期性に基づいて年と通算日数を相互変換するものなのでたぶん流用できるだろう.
これはアルゴリズムというより実装レベルの要求仕様. Calendrier では使用目的や処理系の仕様に応じて, プログラマが次のデータ型のサイズを簡単に変更できるようになっている.
このため次の条件を考慮して,最適なデータ型 (サイズ) を選択することができる.
日付 ⇔ 通算日数変換アルゴリズムを考えるにあたっては,各月の日数と, 何年が閏年になるかさえ知っておけばよい.
ご存知のとおり,グレゴリオ暦では, 西暦Y年が閏年か否かは次のように決まる.
if(Yは400の倍数) then 閏年 else if(Yは100の倍数) then 平年 else if(Yは4の倍数) then 閏年 else 平年
ユリウス暦では次のとおり.
if(Yは4の倍数) then 閏年 else 平年
いずれの場合も,Y≦0 でもよいが,次の点に注意.
また,ここではグレゴリオ暦とユリウス暦の両方を扱うので, 両者を関連付けるためには次のことも知っておく必要がある.
グレゴリオ暦が公式に使われ始めた日付は国によって異なるが, 最初に使い始めたイタリアなどでは1582年10月15日(金)である. これより過去の日付を計算上のグレゴリオ暦で表現したものを proleptic Gregorian calendar というらしい.
同様に proleptic Julian calendar というのも存在するが, これはグレゴリオ暦の場合とは事情が異なる. (計算上ではなく) 実際に運用されたユリウス暦では, BC45 年 〜 AD4 年の間は閏年の入れ方が誤っていた.
参考:ユリウス暦 (Wikipedia) ⇒ 初期のユリウス暦の運用
したがって計算上のユリウス暦と歴史上のユリウス暦が一致するのは AD4 年3月以後である. そこで AD4 年2月以前の日付を計算上のユリウス暦で表したものを proleptic Julian calendar というらしい.
"proleptic" を辞書で引いても載ってなかったが, 「proleptic グレゴリオ暦」で検索してみると, 「先発」とか「予期的」と訳している例がある. ここでは「計算上の」と訳しておく.
ここでは一般の通算日数を,関数名 DN (Day Number) で表す.
(「通算日数」の英訳が "day number" かどうかは知らないが,
「ユリウス[通]日」は
"Julian day [number]" なので,たぶん大丈夫だろう.
それに「通算日数」よりも "day number" (日番号) の方が意味がわかりやすい.)
y年m月d日の通算日数 DN(y, m, d) を求めるには,y年内の特定の日付 (例えば1月1日) の通算日数 DN(y, 1, 1) と, その(翌)日からm月d日までの年内通算日数に分けて考えた方がよい.
DN(y, m, d) = DN(y, 1, 1) + (1月2日からm月d日までの日数) ……… (1)
年内通算日数は毎月1日の通算日数の配列を用意しておけばすぐに計算できる. ただし m≧3 の場合はy年が閏年かどうかの判定が必要になるので, もっと簡単にするために年内の基準日を1月1日ではなく3月1日 (閏日 (2月29日) が入る位置の直後) に変更しよう.
DN(y, m, d) = DN(y, 3, 1) + (3月2日からm月d日までの日数) ……… (2)
この場合,1〜2月は前年の13〜14月として扱う. こうすると,年内通算日数を計算するのに閏年の判定は不要になる. あとは DN(y, 3, 1) さえわかればよい. もし閏年が全くなければ話は非常に簡単で,DN(y, 3, 1) は1年ごとに365増えるから次のようになる.
DN(y, 3, 1) = 365 * y + C1 ……… (3)
ここで C1 は,通算日数の基準日によって決まる定数である. 不定積分の積分定数とほとんど同じなので,ここでは積分定数と呼ぶことにする. 4年ごとの閏年を考慮する場合,yを 0,1,2,… と増やしていくと, yが4の倍数になるたびに (3) の値を1ずつ余分に増やさなければならないので,
DN(y, 3, 1) = 365 * y + floor(y / 4) + C2 ……… (4)
別の言い方をすると,floor(y / 4) は基準日からy年3月1日までの間の, 4年ごとの閏日の数+積分定数である. したがってユリウス暦から通算日数への変換式は次のようになる.
DN(y, m, d) = 365 * y + floor(y / 4) + (3月2日からm月d日までの日数) + C2 ……… (5)
さて,ここでは日付から一般の通算日数への変換関数を考えているので, C2 を特定の値に固定したくない. そこでこれらの値は変換関数には含めないで,外に出すことにする. つまり C2=0 としてユリウス暦 ⇒ 通算日数変換関数を定義する.
JulianToDayNumber(y, m, d) = 365 * y + floor(y / 4) + (3月2日からm月d日までの日数) ……… (6)
この関数が0となるのは,0年 (つまり紀元前1年) 3月1日(水)である. つまりこの関数は,0年3月1日(水)を基準日 (第0日) とするy年m月d日の通算日数を計算する (日付はすべてユリウス暦).
同様に,次の関数はグレゴリオ暦0年3月1日(水) を第0日とするグレゴリオ暦y年m月d日の通算日数を計算する.
GregorianToDayNumber(y, m, d) = 365 * y + floor(y / 4) - floor(y / 100) + floor(y / 400) + (3月2日からm月d日までの日数) ……… (7)
なお,ユリウス暦0年3月1日(月) はグレゴリオ暦0年3月1日(水) とは異なる点に注意.
また,ネットで公開されているソースコードでは, 上記の floor(y / n) の部分が単なる整数除算 y / n になってるものが多い. その場合,紀元前 (y≦0) の日付を正しく扱えるかどうかはプログラミング言語の除算演算子の仕様に依存する. (おそらく多くの処理系では誤った結果になる.)
日付から曜日を計算する方法としてはツェラーの公式が有名だが, ややこしい計算をした挙げ句に曜日しか求められないので全然うれしくない. 通算日数を使えば日数計算ができるだけでなく, 7で割った余りがそのまま曜日に対応する. もちろん,商は−∞方向 (過去方向) に丸めること. ツェラーの公式も結局同じことをやってるわけだが, 10進数を使う人間が計算しやすいように工夫したことで規則が複雑になり, プログラムするにはかえって面倒.
ユリウス暦の方が簡単なので,そのアルゴリズムについて説明する. グレゴリオ暦用アルゴリズムは,それを同じ考え方で拡張すればよい.
一般に,通算日数 ⇒ 日付変換アルゴリズムを考える上で厄介なのは, 閏日の有無により1年の日数が変化することである. しかしグレゴリオ暦の場合,閏日の入れ方には多重周期性があり (おそらく他の暦も同様だろう), この点に着目すれば「日めくり方式 (笑)」 よりもはるかに高速のアルゴリズムが得られる.
閏年の規則から,グレゴリオ暦/ユリウス暦には次の周期があることがわかる.
(以後これらの日数を,それぞれ N1,N4,N100,N400 と書くことにする.)
ユリウス暦は1年単位で見れば周期的ではないが, 4年=N4日単位で見れば完全に周期的である. 「日付 ⇒ 通算日数変換アルゴリズム」の場合と同様, 基準日をユリウス暦0年3月1日として, この日から最初 (第0回) の4年周期が始まると考えると都合が良い. 第 K 回の4年周期はユリウス暦 (4 * K) 年3月1日から始まる.
… ─┬─────┬─────┬─────┬─────┬─────┬─ … │ 4年周期 │ 4年周期 │ 4年周期 │ 4年周期 │ 4年周期 │ │(第−2回)│(第−1回)│ (第0回) │(第+1回)│(第+2回)│ … ─┴─────┴─────┴─────┴─────┴─────┴─ … ↑ ↑ ↑ ↑ ↑ ↑ -8/3/1 -4/3/1 0/3/1 4/3/1 8/3/1 12/3/1 (日付は各周期の初日) (基準日)
1つの4年周期は,4つの1年周期と1日の閏日からなる.
┌─────────────────────────┐ │ 4年周期 (第0回) │ ├─────┬─────┬─────┬─────┬─┤ │ 1年周期 │ 1年周期 │ 1年周期 │ 1年周期 │閏│ │ (第0回) │ (第1回) │ (第2回) │ (第3回) │日│ └─────┴─────┴─────┴─────┴─┘ ↑ ↑ ↑ ↑ ↑ ↑ 0/3/1 1/3/1 2/3/1 3/3/1 4/2/29└ 4/3/1
ユリウス暦0年3月1日を第0日とする通算日数の値 DN が与えられたとき, それに対応する4年周期の番号 Q4 と周期内オフセット (周期の初日を第0日として第何日に当たるかを示す) R4 について次式が成立する.
DN = N4 * Q4 + R4 Q4 = floor(DN / N4) R4 = DN − N4 * Q4
上記の4年周期内オフセット R4 に対応する1年周期の番号 Q1 およびオフセット R1 については次式が成立する.
R4 = N1 * Q1 + R1 Q1 = floor(R4 / N1) (R4≧0 なので,floor の代わりに小数部切り捨てでもよい.) Y = 4 * Q4 + 1 * Q1 (a) Q1≦3 の場合 R1 = R4 − N1 * Q1 (b) Q1=4 の場合 4年周期最後の閏日なので,求める日付はY年2月29日である.
上記 (b) の場合はここで通算日数 ⇒ 日付変換終了. (a) の場合,3月1日を第0日とする年内通算日数 R1 に対応する月および日が求める日付である (年はY). ただし13〜14月になる場合は,翌年の1〜2月になるよう年と月を補正する.
通算日数 ⇒ グレゴリオ暦変換を行う場合はユリウス暦の場合と同様, グレゴリオ暦0年3月1日を第0日とする通算日数から400年周期, 100年周期,4年周期,1年周期,年内通算日数を計算すればよい.
グレゴリオ暦用アルゴリズムの解説書は Calendrier に含まれている.
グレゴリオ暦,ユリウス暦,通算日数,通算秒数の計算を簡単かつ高速に行うことができ,
かつカスタマイズ可能で移植性の高いC言語ソースコード・ライブラリ
Calendrier を販売しています.
C++ からも使用可能です.
(価格:\1,000 (個人用ライセンス) ほか)
ソースの一部には,ここに挙げた関数とほとんど同じものも含まれます. また,このページでは概要しか示していない,グレゴリオ暦 ⇒ 通算日数変換アルゴリズムの解説書 (HTML) も含まれています.
通算日数は日付のみを扱える整数型と,1秒未満も扱える浮動小数型 (Excel のシリアル値と同様) があります.通算秒数は整数型のみです.
通算日数の基準日は1日単位, 通算秒数の基準日時は1秒単位で任意に選ぶことができます. 下記の基準日が定義されていますが,それ以外の任意の基準日時も使用可能です.
詳細
時々「Excel UNIX time 変換」などで検索してくる人がいるので追加.
協定世界時のシリアル値:ExcelTimeUTC = UnixTime / 86400 + 25569 日本標準時のシリアル値:ExcelTimeJST = (UnixTime + 32400) / 86400 + 25569
ExcelTimeUTC および ExcelTimeJST のセルには, 例えば "yyyy/mm/dd(aaa) hh:mm:ss" のような日時書式を設定すること.
注意 (2008/11/07(金),2012/07/25(水) 追記)閏秒は考慮していない.そもそも Excel のシリアル値は1日の秒数が一定
(86400秒) であることを前提としているので,閏秒を扱うことは原理的 (数学的) に不可能.
(閏秒を計算することはできないが,そういう問題以前に,
そもそもシリアル値は閏秒を表現することさえできない.)
「通算日数」とはいうものの, 1日単位だけでなく秒以下を単位とするものも含めている.
基準日時 (第0日) (グレゴリオ暦,UTC) |
通算日数の名称 | 使用例 | 備考 |
---|---|---|---|
BC4714/11/24(月) (=−4713/11/24(月)) 12:00 |
ユリウス日 (Julian Day, JD) |
天文学 年代学 |
・普通,基準日時はユリウス暦で BC4713/01/01 12:00 と表記される. ・基準時刻が正午であり,端数 0.5 がつくので使いにくい. (※個人の感想です.) |
BC4714/11/24(月) (=−4713/11/24(月)) 00:00 |
Chronological Julian Day, CJD |
・ユリウス日に 0.5 を加えて端数が出ないようにしたもの. (CJD = JD + 0.5) ・日本ではほとんど用いられないらしい.(Wikipedia) |
|
1858/11/17(水) 00:00 |
修正ユリウス日 (Modified JD, MJD) |
ユリウス日の桁が多すぎる点を修正したもの. (MJD = JD − 2400000.5) |
|
1582/10/15(金) 00:00 (地方時,第1日) |
リリウス日 (Lilian Day number, LD) |
復活祭の 日付の決定 |
基準日は (最初に導入したイタリアなどの) グレゴリオ暦使用開始日. グレゴリオ暦の発明者 Aloysius Lilius にちなんで命名された. (注意:グレゴリ暦の使用開始日は国によって異なる.) |
0(=BC1)/12/31(日) 00:00 (地方時) |
Rata Die, RD | グレゴリオ暦 1/1/1(月) 00:00 (地方時) を第1日とする. | |
1970/01/01(木) 00:00 |
UNIX 紀元 (UNIX Time) |
C言語の time_t 型 |
単位は1日ではなく1秒.閏秒を考慮しなくてよい場合, UNIX Time = (JD − 2440587.5) * 86400 |
1601/01/01(月) 00:00 (第1日) |
ANSI Date | Win32 の FILETIME 型, COBOL |
ANSI Date = JD − 2305812.5 FILETIME 型は 100nsec 単位,64ビット無符号整数で表現. |
1900/01/01(月) 00:00 (第1日) ただし同年3月以後の日時については実質的に 1899/12/31(日) 00:00 (第1日) |
日付の シリアル値 (Microsoft Excel) |
1900年は平年だが,Excel では閏年として扱われる. これはバグというわけではなく,Lotus 1-2-3 のシェアを奪い取るために 「バグ互換」にしたものらしい.(↓参考) | |
0(=BC1)/03/01(水) | ? | Calendrier | グレゴリオ暦 ⇔ 通算日数変換アルゴリズムを考えるのに好都合. |
今日 (2014/02/22(土))「ジュリアンコード」で検索してきた人がいたので調べてみた.
"Julian (date) code" は日付を4桁の10進数で表すもので,
どうやら米国の食品業界で使用されているらしい.
以前から「ジュリアン日付」で検索して来る人が時々いて,
「ユリウス日 (Julian day, Julian date) のことか?
誰が勝手な訳語作ったんだよ!」って思ってたけど,
どうやら "Julian (date) code" のことだったらしい.
そもそも元の "Julian (date) code" が紛らわしい用語なのが悪い!(怒)
ユリウス日もユリウス暦も関係ないやん!
まあ「通算日数」であるという点では元祖ユリウス日と同じなので名前を拝借したんだろうけど,基準日時が全然違う.
「こんな紛らわしい用語作ったの誰だよ!(怒)」と思って少し「ジュリアン日付」
で検索してみたところ,"Julian (date) code"
は IBM 製ではないかと思うようになった.(単なる憶測)
コンピュータの黎明期に,日付をコンピュータ処理するため IBM が "Julian (date) code" を定義し,というのはいかにもありそうだ.
メインフレームで使えるようにした.⇒ 世界のビジネス界に普及した.(あくまでも憶測ですよ憶測)
■Excel でシリアル値とジュリアン日付を変換する方法
■参考
2009/03/06(金) 「ユリウス通日 うるう秒」で検索して来た人がいるので追記.
7.1
でも書いたが,日内時刻を通算日数の小数部として表現する方法は1日の長さが一定
(86400秒) であることが大前提なので,閏秒を扱うことは原理的 (数学的) に不可能.
(計算できるか否かという以前に,そもそも閏秒を表現することさえできない.)
閏秒を扱うには通算日数と日内時刻を分けるか,通算秒数を使うしかない.
世界標準時について調べたことをまとめてみた.
┏━━━━┓ ┏━━━━┓ 世界時 ┃ ┃ ┃ 地球の ┃ ┏━━┓ (*1) ┃天体観測┠→ UT0 →┨ 極運動 ┠─→ UT1 ──→┨ ┃ |誤差|>0.8秒 ┃ ┃ ┃ 補正 ┃ (旧:GMT) ┃比較┠───┐ ┗━━━━┛ ┗━━━━┛ ┌→┨ ┃ │ │ ┗━━┛ ↓ ┏━━━━┓ 国際原子時│ ┏━━┷━━┓ 協定世界時 ┃セシウム┃ │ ┃ 閏秒 ┃ ┃ ┠─→ TAI ─┴────→┨ ┠─→ UTC ┃原子時計┃ ┃挿入/削除┃ │ ┗━━━━┛ ┗━━━━━┛ │ ↓ 時差 ─→(+) │ ↓ 地方標準時 (日本標準時など)
■参考
最近外部から「お昼の12時は『午前12時』、『午後12時』、 それとも『午後0時』どれが正しいのですか?」という主旨の質問が度々寄せられる。 この質問に対する見解を課内で統一しておく必要がある。
地球の自転速度は一定ではありません。 長期的には徐々に遅くなっているものの、短期的には細かく変動しています。
物理学的・天文学的な時間と歴史学的・暦学的・日常生活上の時間との誤差は時代によって異なるため、それを把握しなければ相互変換できません。
ということで ΔT について。
地球時 TT と世界時 UT1 との差が ΔT です。
たまにこの検索ワードでやって来る人がいるけど, なんでそんなことをしたいのかさっぱりわからない.
上の図に描いたとおり,UTC は UT1 (旧 GMT)
との誤差が±0.9秒以内になるように調整されている.
つまり実用上は同じ値なので変換もへったくれもない.
ひょっとするとこの誤差を正確に求めたいのかもしれないけど,
地球の自転速度の不規則変動によって生じるものなので,
計算で求めることはできない.
(ラプラスの悪魔がいれば計算できるかもしれないが.(笑))
閏秒は不規則に追加・削除されるので,計算式やアルゴリズムでは変換できません. 「閏秒のテーブル」を用意し,これに基づいて変換してください. もちろん今後の閏秒の実施に合わせて閏秒テーブルを更新する必要があります.
未整理,敬称略.
旧暦2033年問題とは、西暦2033年秋から2034年春にかけて、日本の旧暦の月名が天保暦の暦法で決定できなくなる問題のこと。1844年 (天保15年) に天保暦が制定されて以来、このような不都合が生じるのは、2033年秋 - 2034年春が最初である。
Copyright © 2007-2016 noocyte, All rights reserved. E-mail: relipmoced (a) yahoo.co.jp (" (a) " を半角のアットマークに書き替えてください.) リンクはご自由に. 「noocyte のプログラミング研究室」トップページに戻る. |