前説
Rubyの 日付クラス の暦日操作をまとめます。
ユリウス暦/グレゴリオ暦を念頭に、暦日計算に必要な日付インスタンスの生成と、暦の切り替えを行います。
環境
念のためバージョンを記載します。
% ruby --version
ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-darwin16]
ただし、これから触れるDateクラス/メソッドは 少なくとも Ruby1.8.7 から存在しており、バージョン非依存の挙動と考えられます。
また、以下の例文は全て irb
で示します。
エラー
特定の過去日を指定して日付オブジェクトを生成した場合、以下のようなエラーが発生します。
% irb
irb(main):001:0> require 'date'
=> true
irb(main):002:0> date = Date.new(1582, 10, 4)
=> #<Date: 1582-10-04 ((2299160j,0s,0n),+0s,2299161j)>
irb(main):003:0> date = Date.new(1582, 10, 5)
Traceback (most recent call last):
6: from /Users/pldb/.rbenv/versions/2.6.2/bin/irb:23:in `<main>'
5: from /Users/pldb/.rbenv/versions/2.6.2/bin/irb:23:in `load'
4: from /Users/pldb/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
3: from (irb):3
2: from (irb):3:in `new'
1: from (irb):3:in `initialize'
ArgumentError (invalid date)
irb(main):004:0> date = Date.new(1582, 10, 15)
=> #<Date: 1582-10-15 ((2299161j,0s,0n),+0s,2299161j)>
1582-10-05 〜 1582-10-14 は ArgumentError (invalid date)
です。
エラー原因
この日付は現実に存在しない日付です。ゆえに invalid date
ですが、詳しくは以下の通りです。
まず、Ruby の 日付実装 のコメントを引用します。
* A Date object can be created with an optional argument,
* the day of calendar reform as a Julian day number, which
* should be 2298874 to 2426355 or negative/positive infinity.
* The default value is +Date::ITALY+ (2299161=1582-10-15).
* See also sample/cal.rb.
*
* $ ruby sample/cal.rb -c it 10 1582
* October 1582
* S M Tu W Th F S
* 1 2 3 4 15 16
* 17 18 19 20 21 22 23
* 24 25 26 27 28 29 30
* 31
1582-10-04 まではユリウス暦、その次の日の 1582-10-15 はグレゴリオ暦です。
実際に判定してみますが、1582-10-04に対しては julian? は true で、翌日以降は false (つまり gregorian? がtrue)です。
% irb
irb(main):001:0> require 'date'
=> true
irb(main):002:0>
irb(main):003:0> date = Date.new(1582, 10, 4)
=> #<Date: 1582-10-04 ((2299160j,0s,0n),+0s,2299161j)>
irb(main):004:0> date.julian?
=> true
irb(main):005:0> date = Date.new(1582, 10, 15)
=> #<Date: 1582-10-15 ((2299161j,0s,0n),+0s,2299161j)>
irb(main):006:0> date.julian?
=> false
irb(main):007:0> date.gregorian?
=> true
ユリウス暦による過小に進んだズレをグレゴリオ暦で正した結果、上記のように日付がスキップされています。スキップ自体はインクリメントすることでも確かめられます。
% irb
irb(main):001:0> require 'date'
=> true
irb(main):002:0> date = Date.new(1582, 10, 4)
=> #<Date: 1582-10-04 ((2299160j,0s,0n),+0s,2299161j)>
irb(main):003:0> date += 1
=> #<Date: 1582-10-15 ((2299161j,0s,0n),+0s,2299161j)>
歴史上の背景については wiki など別記事をご確認ください。
これが invalid date
の理由です。ここからさらに理解を進めることにします。
引数 start
これまでの new は末尾の引数を省略しています。省略せずに書いた場合です。
irb(main):008:0> date = Date.new(1582, 10, 15, Date::ITALY)
=> #<Date: 1582-10-15 ((2299161j,0s,0n),+0s,2299161j)>
Date::ITALY
は既定値です。 末尾の引数を省略された場合にデフォルトとして設定されます。
この設定可能な引数については ドキュメント に記載されています。
それぞれの値は先ほど引用したコメント 2298874 to 2426355 or negative/positive infinity
に該当します。
これらを末尾の引数に設定できます。
[PARAM] start:
グレゴリオ暦をつかい始めた日をあらわすユリウス日
「つかい始めた日」というのがポイントで、 ITALY と ENGLAND はこの説明で理解しやすい定数値です。
% irb
irb(main):001:0> require 'date'
=> true
irb(main):002:0> Date::ITALY
=> 2299161
irb(main):003:0> Date.jd(Date::ITALY)
=> #<Date: 1582-10-15 ((2299161j,0s,0n),+0s,2299161j)>
irb(main):004:0>
irb(main):005:0> Date::ENGLAND
=> 2361222
irb(main):006:0> Date.jd(Date::ENGLAND)
=> #<Date: 1752-09-14 ((2361222j,0s,0n),+0s,2299161j)>
irb(main):007:0>
jd にユリウス日を与えると対応するDateオブジェクトを返します。
日付はグレゴリオ暦の開始日です。
上記定数以外の値として、 `should be 2298874 to 2426355` (1581-12-22 〜 1931-01-13) とありますが、範囲外の値を渡してもエラーにはなりませんでした。歴史的な文脈を無視すれば、開始日は自由に設定できるのかもしれません
% irb
irb(main):001:0> require 'date'
=> true
irb(main):002:0> Date::JULIAN
=> Infinity
irb(main):003:0> Date::GREGORIAN
=> -Infinity
negative/positive infinity
です。
Date::JULIAN (Infinity) は「改暦日は無限の未来にあると考えられます」とある通り、グレゴリオ暦を永遠に開始しません。
Date::GREGORIAN (-Infinity) は「改暦日は無限の過去にあると考えられます」とある通り、始まりからすでにグレゴリオ暦です。
個人的には start
よりも暦の type
とでも捉えた方が理解しやすいと感じるところです。
次にこれらの使い方を考えます。
使用方法
現実的には Date::ITALY で十分ですが、研究分野によっては暦の使い分けを避けたいケース、また、暦変換を行いたいケースがあると思われます。
そのようなケースに対応する例文を以下に示します。
生成
基本的にDateインスタンス生成するメソッドでは、この start
を末尾に設定できるようです。
ユリウス暦日による日付インスタンス生成を例とします。
new
% irb
irb(main):001:0> require 'date'
=> true
irb(main):002:0> date = Date.new(2020, 1, 1, Date::JULIAN)
=> #<Date: 2020-01-01 ((2458863j,0s,0n),+0s,Infj)>
irb(main):003:0> date.julian?
=> true
jd
% irb
irb(main):001:0> require 'date'
=> true
irb(main):002:0> date = Date.jd(Date.new(2020, 1, 1, Date::JULIAN).jd, Date::JULIAN)
=> #<Date: 2020-01-01 ((2458863j,0s,0n),+0s,Infj)>
irb(main):003:0> date.julian?
=> true
前述のことではありますが、第一引数には引数なしの jd の戻り値(ユリウス日)を与えています
parse
% irb
irb(main):001:0> require 'date'
=> true
irb(main):002:0> date = Date.parse('2020-1-1', Date::JULIAN)
=> #<Date: 2020-01-01 ((2458850j,0s,0n),+0s,2299161j)>
irb(main):003:0> date.julian?
=> false
その他
以下のメソッドでも同様です。末尾に start
を設定できます。
切り替え
次に暦の切り替えです。
new_start
例文ではユリウス暦として日付インスタンスを生成、それをグレゴリオ暦に換算しています。
% irb
irb(main):001:0> require 'date'
=> true
irb(main):002:0> date = Date.new(2020, 1, 1, Date::JULIAN)
=> #<Date: 2020-01-01 ((2458863j,0s,0n),+0s,Infj)>
irb(main):003:0> date.julian?
=> true
irb(main):004:0> date = date.new_start(Date::GREGORIAN)
=> #<Date: 2020-01-14 ((2458863j,0s,0n),+0s,-Infj)>
irb(main):005:0> date.julian?
=> false
julian
リンク先の通り、 new_start(Date::JULIAN)
と等価です。
% irb
irb(main):001:0> require 'date'
=> true
irb(main):002:0> date = Date.new(2020, 1, 1, Date::GREGORIAN)
=> #<Date: 2020-01-01 ((2458850j,0s,0n),+0s,-Infj)>
irb(main):003:0> date.julian?
=> false
irb(main):004:0> date = date.julian
=> #<Date: 2019-12-19 ((2458850j,0s,0n),+0s,Infj)>
irb(main):005:0> date.julian?
=> true
gregorian
リンク先の通り、 new_start(Date::GREGORIAN)
と等価です。
% irb
irb(main):001:0> require 'date'
=> true
irb(main):002:0> date = Date.new(2020, 1, 1, Date::JULIAN)
=> #<Date: 2020-01-01 ((2458863j,0s,0n),+0s,Infj)>
irb(main):003:0> date.julian?
=> true
irb(main):004:0> date = date.gregorian
=> #<Date: 2020-01-14 ((2458863j,0s,0n),+0s,-Infj)>
irb(main):005:0> date.julian?
=> false
まとめ
- Dateインスタンスはユリウス暦/グレゴリオ暦を指定できる
- デフォルトでは Date::ITALY の
1582-10-15
がグレゴリオ暦の開始日で、それまではユリウス暦となる - いつからグレゴリオ暦に切り替えられるかは、ユリウス日で指定できる
- Date::ITALY / Date::ENGLAND のようにユリウス日を指定することができる
- Date::GREGORIAN / Date::JULIAN のように常にユリウス暦/グレゴリオ暦を指定できる
- 日付インスタンスを生成するメソッドには、このグレゴリオ暦の開始日を末尾の引数に指定できる
- 一度指定した開始日は再設定できる(ユリウス暦 <=> グレゴリオ暦 を換算できる)