Help us understand the problem. What is going on with this article?

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

前説

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 に該当します。
これらを末尾の引数に設定できます。

new

[PARAM] start:
グレゴリオ暦をつかい始めた日をあらわすユリウス日

「つかい始めた日」というのがポイントで、 ITALYENGLAND はこの説明で理解しやすい定数値です。

 % 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オブジェクトを返します。
日付はグレゴリオ暦の開始日です。

:last_quarter_moon_with_face:上記定数以外の値として、 should be 2298874 to 2426355 (1581-12-22 〜 1931-01-13) とありますが、範囲外の値を渡してもエラーにはなりませんでした。歴史的な文脈を無視すれば、開始日は自由に設定できるのかもしれません

まさに start と言うべきですが、 GREGORIANJULIAN についてはユリウス日ではありません。

 % 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::ITALY1582-10-15 がグレゴリオ暦の開始日で、それまではユリウス暦となる
  • いつからグレゴリオ暦に切り替えられるかは、ユリウス日で指定できる
  • 日付インスタンスを生成するメソッドには、このグレゴリオ暦の開始日を末尾の引数に指定できる
  • 一度指定した開始日は再設定できる(ユリウス暦 <=> グレゴリオ暦 を換算できる)

参考リンク

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした