Yet Another Date Class

なぜ Ruby か

暦相互変換プログラム when.exe は、開発してもう10年近くになるMS-DOS
コマンドライン型ツールです。開発しながら暦の勉強をしてきたため、そ
のアーキテクチャはつぎはぎだらけで、改造や強化には向きません。また、
内部構造が汚くなっているため、UNIX や MacOS など他のプラットホーム
に移植したり、ソースリストを公開して、そのアルゴリズムを社会に還元
することもできません。これまではプログラム自体に手をつけることなく、
when.exe を検索ツールとして tcl/tk や Web から使用するという方策で、
しのいできました。
約7年前にプログラムを C++ で全面的に作り直そうと考え、着手しました。
C++ を選択したのは、暦は「互いに似ているがちょっと異なる」というもの
が多く、オブジェクト指向でアーキテクチャを整理するのに向いているから
です。ところがデータを含めて約1万行近く作成したところで、自分が作っ
ているのが、暦相互変換プログラムそのものではないことに気づいたのです。
「互いに似ているがちょっと異なる、今知られているものだけではなく、
今後見つかるものも簡単に作れる」というアーキテクチャにするためには、
C++ は力不足でした。そのため前提となる必要機能を作り込む必要があっ
たのです。今なら Ruby の用語で、必要とした機能を示すことができます。
などです。このことに気づいた時点で、時期尚早ということで C++ での
開発を中断しました。そして、これらのほとんどすべてをカバーしている
Ruby に出会ったことで、開発を再会する気になったのです。現時点でも
Ruby ほどこれらの機能を備えている言語は他にありません。約3年前に
一度手をつけたときはスキルが伴わず「オブジェクト指向のエッセンスは
継承関係 - is a - だ」という思いこみがあって失敗しました。今回、
包含関係 - have a - を積極的に使ってようやく作業が軌道にのりました。

when.exe の Ruby 化のための検討

現在when.exeRuby  化を行っています。既存ライブラリである
Date ,newdate を参考にしつつ試作品(v0.41) を作ってみました。
今回の変更はLocation クラスの追加と、それにともなうインド暦
の正規対応です。Surya-Siddhanta も計算できます。アドヴァイス
いただければ幸いです(旧バージョンの仕様案はこちら )。
このクラスライブラリへのコメントはこちらへ。

継承木 兼 目次

継承木を下記に示します。それぞれをクリックすると詳細説明が出ます。
まず Date::Extended  , 次に Calendar  を読んでください。

Object --+-Date -Date::Extended  +-Eras 
         |                       |
         |                       |
         +-Range ----Period      +-Civil 
         |                       |
         |                       |
         +-Outfit -+-Calendar ---+-DMD      +-ILSB 
         |         |             |          |
         |         |             |          +-Tibet 
         |         |             |          |
         |         +-Residue     +-EMD -----+-ChinaB -+-ChinaJ --ChinaS 
         |         |             |          :         |
         |         |             |          +-FMD0    +-ChinaT
         |         |             |          |
         |         +-Ephemeris   +-FMD -----+-FMD1 ---+-Java
         |         |                        |         |
         |         |                        +-FMD2    +-Maya
         |         +-Timezone -----DST      |
         |         |                        +-Week -----Easter 
         |         |                        |
         |         +-Location               +-ThaiC ----ThaiB 
         |                                  |
         +-Location::Coordinate             +-Jewish 
         |
         +-Location::Cycle

通日用メソッドの整理

「絶対」時系 - 原則として文字列 jd を含む -

Date#rjd
ユリウス通日(-4712-01-01T12:00:00Zからの経過時間(単位 日))を返します。
下位互換性保持のため、jd を sdn の意味に使うので、date2.rb にあわせて
rjd(Real/Rational Julian Day)と命名しています。詳しくはDate#sdn をご
覧ください。
Date#jdn
rjd の小数点以下を四捨五入した整数( Julian Day Number または Julian Day
Noon のつもり)を返します。
Date#jdf
rjd - jdn を返します。正であるとは限りません。

「地方」時系 - 原則として文字列 sd を含む -

Date#sd
地方時通日(地方時-4712-01-01T12:00:00からの経過時間(単位 日))を返します。
Date#sdn
Date#jd
地方時正午通日(*)を返します。
* sd の小数点以下を丸めた整数
1日の始まりの時刻が暦法によって異なるため、丸めは必ずしも四捨五入で
はありません。本メソッドは必ず正午に対応する sdn (Serial Day Number
または Serial Day Noon のつもり - 将来はユリウス通日との関係がなくな
る可能性があるので「地方時の」という含みを持つ、Local Day Number と
はしない)を返します。
最初のDateクラスは時刻概念を持たなかったので、日付に対応するユリウス
通日(整数)をメソッドjdで返しました。これは非常に妥当性がある振る舞い
であり、当時それ以外の選択肢があるはずもなかったのです。今回下位互換
性の保持のため、「絶対」時系-原則として文字列 jd を含む-と地方」時系
-原則として文字列 sd を含む-という命名の例外とします。
Date#sdf
sd - sdn を返します。正であるとは限りません。
Date#day_fraction
sdf + 1/2 を返します。標準添付のDate に対する互換性のために存在しています。

日付時刻の基本表現(文字列形式)

CCYY-MM-DD[T[hh:mm:ss.s[Z|+ZZ[:]zz]]]
    a  b   c   d          e
【要請】
 (1) 通常用いられている西暦の場合、ISO8601 の拡張表現と同一となる。
 (2) 1年が1月1日に始まらない暦法(例:会計年度)を表現できるようにする。
 (3) 1日が午前0時に始まらない暦法(例:イスラム暦)を表現できるようにする。
 (4) 中国の太陰太陽暦の日付を表現できるようにする。
 (5) インドの太陰太陽暦の日付を表現できるようにする。
 (6) CCYY が '   0' 〜 '9999' の範囲に入る場合、同一の暦法/時差に対して
     文字列による昇順ソートで、日付時刻を昇順ソートできる。 
【区切り文字】
 下表の通りとする。ただし日の区切りが午前0時の場合'-'のかわりに'T'を用いる。

 +----------+-------+-------------------------------------+---------+------+
 |区切り文字|   a   |                b                    |    c    |  e   |
 |          +--+----+--+------+------+------+------+------+--+------+      |
 |    (code)| Y|    | M|  S1  |  S2  |  S3  |  S4  |その他| D|      |      |
 +----------+--+----+--+------+------+------+------+------+--+------+------+
 | !  (0x21)|  |    |-5|      |      | 黒分 |      |      |  |      |      |
 | %  (0x25)|  |    |-4|      |閏黒分|      |      |重複月|  |      |      |
 | &  (0x26)|  |    |-3|閏白分|閏白分|閏白分|      |      |  |      |      |
 | *  (0x2A)|-1|前年|-2|閏黒分|      |閏黒分|      |      |-1| 前日 |      |
 | +  (0x2B)|  |    |-1|      | 黒分 |      |      |      |  |      |東経側|
 | -  (0x2D)| 0|当年| 0| 白分 | 白分 | 白分 | 白分 |通常月| 0| 当日 |西経側|
 | <  (0x3C)|  |    | 1| 黒分 |      |      | 黒分 |      |  |      |      |
 | =  (0x3D)| 1|翌年| 2|      |      |      |閏白分| 閏月 | 1| 翌日 |      |
 | >  (0x3E)|  |    | 3|      |      |      |閏黒分|      | 2|翌々日|      |
 +---+------+--+----+--+------+------+------+------+------+--+------+------+

日付時刻の基本表現(内部形式) ([]は配列, {}はハッシュ)

【年】
   一般形  =   [年号, 検索番号, [年の上位階層, .. , [年, オフセット]]]
     年号          :   Eras クラスの場合に年を特定するために年号がつきます。
                       国名や対応する人名が定義されているときは'.'で区切っ
                       てそれらを結合した文字列になります。
     検索番号      :   同一のキーに一致する年号が複数ある場合、検索番号で
                       年号を特定します(例えば天保という年号は、日本、後梁
                       北斉の3ヶ国にあります)。
     年の上位階層  :   年の上位階層がある場合につきます。マヤ暦のカトゥン・
                       バクトゥン、あるいはギリシャのオリンピアード番号など。
     年            :   常につきます。単なる整数であり、普通の暦では、これだ
                       けで年を指定できます。
     オフセット    :   運用と暦法で年の区切りが異なる場合につきます。これが
                       現れるもっとも日常的な例は満年齢です。暦法はグレゴリ
                       オ暦でも満年齢は誕生日が年の区切りです(オフセット値
                       は上表 Y 欄の通りです)。 ['A', [42, 1]] は 'A' さんが
                       正月を過ぎているが誕生日前で満42歳であることを示しま
                       す。誕生日を過ぎると ['A', [43, 0]] になります。
【月】
   一般形  =   [月番号, 詳細番号]  ([]は配列, 詳細番号なしならただの整数)
     月番号        :   閏と白分・黒を無視した月の通し番号です。
                       普通の暦では、これだけで月を指定できます。
     詳細番号      :   対応する閏と白分・黒分情報を示します(値は上表 M 欄の
                       通りです。
【日】
   一般形  =   [日番号, オフセット]  ([]は配列, オフセットなしならただの整数)
     日番号        :   日の区切り、余日・欠日を無視した日の通し番号です。
                       普通の暦では、これだけで日を指定できます。
     オフセット    :   運用と暦法で日の区切りが異なる場合、および余日のある
                       暦法の場合につきます。イスラーム暦やインド暦が典型的
                       です(値は上表 D 欄の通りです)。
【時】【分】【秒】【秒の小数】
   一般形  =   通し番号
     通し番号      :   時分秒の通し番号を意味する整数(Integer )と秒の小数を
                       意味する分数(Rational )です。
                       夏時間から冬時間への変更時や閏秒の挿入時に、
                       [重複する時または秒, 1]という配列にするかもしれませ
                       んが、今後の検討課題でまだ実装していません。

現在のステータス

今後の作業

データの移行
  暦法名を整理する(頭文字によるマッチング・ルール)。
  事前実装予定の暦法を Region ディレクトリに定義する。
    Japan, China, East_Asia, South_East_Asia, India, ..., Maya
エラー処理を整理する。
年と日の指定に剰余類を許すようにする。
Marshal に対応する。
NIPE を拡張ライブラリ化する。

問題点

Eras
  読み込み時に毎回、年号開始日の日付をユリウス通日化するのは冗長。
  Marshal などを使って、効率化すべき。
Marshal
  Calendar が Proc オブジェクトを保持している(_m_in_y, d_in_mの計算のため)
  ので、Marshal できない。見直す必要がある。
ThaiC
  『中国天文学史文集(第3集)』のデータでうまく計算が整合しないので、確認
  する必要がある。
Tibet
  『蔵暦的原理与実践』のアルゴリズムが、まだ今回の date.rb のアーキテク
  チャに変換されていないため、余日の表記が他の暦と整合していない。要調整。
DST
  夏時間から冬時間への切替時の、1時間の重複が処理されていない。閏時など
  も含めて、対策を考える必要がある。