Ruby日付クラスで暦を操作する（生成/切り替え）

# 前説
Rubyの [日付クラス](https://docs.ruby-lang.org/ja/2.6.0/class/Date.html) の暦日操作をまとめます。
ユリウス暦/グレゴリオ暦を念頭に、暦日計算に必要な日付インスタンスの生成と、暦の切り替えを行います。

# 環境

念のためバージョンを記載します。

```sh
% ruby --version
ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-darwin16]
```

ただし、これから触れるDateクラス/メソッドは 少なくとも [Ruby1.8.7](http://doc.okkez.net/1.8.7/view/class/Date) から存在しており、バージョン非依存の挙動と考えられます。
また、以下の例文は全て `irb` で示します。

# エラー
特定の過去日を指定して日付オブジェクトを生成した場合、以下のようなエラーが発生します。

```ruby
% 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 の [日付実装](https://github.com/ruby/ruby/blob/487d96c6b1cd7f5d415dba27a9684b30dfa9afed/ext/date/date_core.c#L9237-L9249) のコメントを引用します。

```c
* 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?](https://docs.ruby-lang.org/ja/2.6.0/method/Date/i/julian=3f.html) は true で、翌日以降は false (つまり [gregorian?](https://docs.ruby-lang.org/ja/2.6.0/method/Date/i/gregorian=3f.html) がtrue）です。

```ruby
 % 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
```

ユリウス暦による過小に進んだズレをグレゴリオ暦で正した結果、上記のように日付がスキップされています。スキップ自体はインクリメントすることでも確かめられます。

```ruby
 % 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](https://eco.mtk.nao.ac.jp/koyomi/wiki/A5B0A5ECA5B4A5EAA5AACEF1.html) など別記事をご確認ください。
これが `invalid date` の理由です。ここからさらに理解を進めることにします。

# 引数 `start`
これまでの [new](https://docs.ruby-lang.org/ja/2.6.0/method/Date/s/new.html) は末尾の引数を省略しています。省略せずに書いた場合です。

```ruby
irb(main):008:0> date = Date.new(1582, 10, 15, Date::ITALY)
=> #<Date: 1582-10-15 ((2299161j,0s,0n),+0s,2299161j)>
```

`Date::ITALY` は既定値です。 末尾の引数を省略された場合にデフォルトとして設定されます。
この設定可能な引数については [ドキュメント](https://docs.ruby-lang.org/ja/2.6.0/class/Date.html) に記載されています。

* [ENGLAND](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/ENGLAND.html) -> Integer
* [GREGORIAN](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/GREGORIAN.html) -> Date::Infinity
* [ITALY](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/ITALY.html) -> Integer
* [JULIAN](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/JULIAN.html) -> Date::Infinity

それぞれの値は先ほど引用したコメント `2298874 to 2426355 or negative/positive infinity` に該当します。
これらを末尾の引数に設定できます。

[new](https://docs.ruby-lang.org/ja/2.6.0/class/Date.html#S_CIVIL)
> [PARAM] start:
> グレゴリオ暦をつかい始めた日をあらわすユリウス日

「つかい始めた日」というのがポイントで、 [ITALY](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/ITALY.html) と [ENGLAND](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/ENGLAND.html) はこの説明で理解しやすい定数値です。

```ruby
 % 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](https://docs.ruby-lang.org/ja/2.6.0/method/Date/s/jisx0301.html) にユリウス日を与えると対応するDateオブジェクトを返します。
日付はグレゴリオ暦の開始日です。
<details><summary>:last_quarter_moon_with_face:</summary>上記定数以外の値として、 `should be 2298874 to 2426355` （1581-12-22 〜 1931-01-13） とありますが、範囲外の値を渡してもエラーにはなりませんでした。歴史的な文脈を無視すれば、開始日は自由に設定できるのかもしれません</details>
まさに `start` と言うべきですが、 [GREGORIAN](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/GREGORIAN.html) と [JULIAN](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/JULIAN.html) についてはユリウス日ではありません。

```ruby
 % 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](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/JULIAN.html) (Infinity) は「改暦日は無限の未来にあると考えられます」とある通り、グレゴリオ暦を永遠に開始しません。 
[Date::GREGORIAN](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/GREGORIAN.html) (-Infinity) は「改暦日は無限の過去にあると考えられます」とある通り、始まりからすでにグレゴリオ暦です。

個人的には `start` よりも暦の `type` とでも捉えた方が理解しやすいと感じるところです。

次にこれらの使い方を考えます。

# 使用方法

現実的には [Date::ITALY](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/ITALY.html) で十分ですが、研究分野によっては暦の使い分けを避けたいケース、また、暦変換を行いたいケースがあると思われます。
そのようなケースに対応する例文を以下に示します。

## 生成

基本的にDateインスタンス生成するメソッドでは、この `start` を末尾に設定できるようです。
ユリウス暦日による日付インスタンス生成を例とします。

### [new](https://docs.ruby-lang.org/ja/2.6.0/class/Date.html#S_CIVIL)
```ruby
 % 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](https://docs.ruby-lang.org/ja/2.6.0/method/Date/s/jd.html)
```ruby
 % 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](https://docs.ruby-lang.org/ja/2.6.0/method/Date/s/jd.html) の戻り値（ユリウス日）を与えています

### [parse](https://docs.ruby-lang.org/ja/2.6.0/method/Date/s/parse.html)
```ruby
 % 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` を設定できます。

* [ordinal](https://docs.ruby-lang.org/ja/2.6.0/method/Date/s/ordinal.html)
* [commercial](https://docs.ruby-lang.org/ja/2.6.0/method/Date/s/commercial.html)
* [strptime](https://docs.ruby-lang.org/ja/2.6.0/method/Date/s/strptime.html)

## 切り替え
次に暦の切り替えです。

### [new_start](https://docs.ruby-lang.org/ja/2.6.0/method/Date/i/new_start.html)
例文ではユリウス暦として日付インスタンスを生成、それをグレゴリオ暦に換算しています。

```ruby
 % 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](https://docs.ruby-lang.org/ja/2.6.0/method/Date/i/julian.html)
リンク先の通り、 `new_start(Date::JULIAN)` と等価です。

```ruby
 % 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](https://docs.ruby-lang.org/ja/2.6.0/method/Date/i/gregorian.html)
リンク先の通り、 `new_start(Date::GREGORIAN)` と等価です。

```ruby
 % 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](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/ITALY.html) の `1582-10-15` がグレゴリオ暦の開始日で、それまではユリウス暦となる
* いつからグレゴリオ暦に切り替えられるかは、ユリウス日で指定できる
  * [Date::ITALY](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/ITALY.html) / [Date::ENGLAND](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/ENGLAND.html) のようにユリウス日を指定することができる
  * [Date::GREGORIAN](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/GREGORIAN.html) / [Date::JULIAN](https://docs.ruby-lang.org/ja/2.6.0/method/Date/c/JULIAN.html) のように常にユリウス暦/グレゴリオ暦を指定できる
* 日付インスタンスを生成するメソッドには、このグレゴリオ暦の開始日を末尾の引数に指定できる
* 一度指定した開始日は再設定できる（ユリウス暦 <=> グレゴリオ暦 を換算できる）

# 参考リンク

* https://docs.ruby-lang.org/ja/2.6.0/class/Date.html
* https://apidock.com/ruby/Date
* https://github.com/ruby/ruby/blob/487d96c6b1cd7f5d415dba27a9684b30dfa9afed/ext/date/date_core.c
* https://eco.mtk.nao.ac.jp/koyomi/wiki/A5B0A5ECA5B4A5EAA5AACEF1.html