レシピ1.1 今日の日付けの割り出し

問題

現在の日付けと時刻、または未来または過去の時間を表すオブジェクトを生成する必要がある。

解決

 ファクトリメソッドTime.nowは、現在のローカルタイムを保持するTimeオブジェクトを生成する。必要であれば、Time#gmtimeを呼び出して、そのオブジェクトをGMTグリニッジ標準時)に交換できる。gmtimeメソッドは、Timeオブジェクトを実際に変更するが、そうしたメソッドに対するRubyの命令規則に従わない(そうしたメソッドもgmtimeのような名前で呼ばれるべきなのだ)。


now = Time.now          # => Sat Mar 18 16:58:07 EST 2006
now.gmtime                # => Sat Mar 18 21:58:07 UTC 2006

# 元のオブジェクトはタイムゾーン変換による影響を受けている
now                            # => Sat Mar 18 21:58:07 UTC 2006

 現在のローカルタイムのDateTimeオブジェクトを生成するには、ファクトリメソッドDateTime.nowを使用する。DateTimeオブジェクトをGMTに変換するには、DateTime#new_offsetを引数無しで呼び出す。Time#gmtimeとは異なり、このメソッドはもとのオブジェクトを直接変更せず、2つめのDateTimeオブジェクトを返す。

require 'date'
now = DateTime.now
# => #<Datetime: 70669826362347677/28800000000,-5/24,2299161>
now.to_s                           # "2006-03-18T16:58:07-0500"
now.new_offset.to_s          # "2006-03-18T21:58:07Z"

#元のオブジェクトはタイムゾーン変換による影響を受けていない。
now.to_s                           # => "2006-03-18T16:58:07-0500"

解説



 TimeオブジェクトとDateTimeオブジェクトは、どちらも西洋の暦や時計の基本的な時間の刻みに対するアクセサメソッドを提供する。どちらのクラスもアクセサとしてyear、month、day、hour(24時間形式)、min、sec、zone、を提供する。Time#isdstを使用すれば、Timeオブジェクトに格納されている時刻が、そのタイムゾーンの夏時間によって調整されているかどうかを確認できる。DateTimeは、夏時間が存在しない物として、処理を行う。

now_time = time.new
now_datetime = DateTime.now
now_time.year                                  # => 2006

now_datetime.year                           # => 2006
now_time.hour                                 # => 18
now_datetime.hour                           # => 18

now_time.zone                                 # => "EST"
now_datetime.zone                           # => "-0500"
now_time.esdst                                # => false

 Time#zoneとDateTime#zoneは少し異なっていることが分かる。Time#zoneはタイムゾーンの名前または略名を返し、DateTime#zoneはGMTからの数値オフセットを文字列で返す。DateTime#offsetを呼び出すと、GMTオフセットを数値(小数で表された日)で取得することが出来る。

now_datetime.offset                          # => Rational (-5, 24) # -5hours

 また、どちらのクラスも秒を小数で表すことができる。これには、TIme#usec(μsecまたは100万分の1秒)およびDateTime#sec_fractionでアクセスすることが出来る。先の例では、DateTimeオブジェクトはTimeオブジェクトの後に生成されているので、両方のオブジェクトが同じ秒内に生成されているにもかかわらず、数値が異なっている。

now_time.usec                                     # => 247930
# つまり、2479億3000万分の1秒
now_datetime.sec_fraction                   # => Rational(62191, 21600000000)
# つまり、約2879億2100万分の1秒

 dateライブラリは、時間を含まないDateTimeのようなDateクラスを提供する。現在の日付けを保持するDateオブジェクトを生成するには、DateTimeオブジェクトを生成し、Dateファクトリメソッドの呼び出しでその結果を使用するのがもとも効果的である。DateTimeは、実際にはDateのサブクラスなので、このようにする必要があるのは、時間のデータを削除して、そのデータが使用されないようにしたい場合だけだ。

class Date
  def Date.now
    return Date.jd(DateTime.now.jd)
  end
end
puts Date.now
# 2006-3-18

 今この瞬間のタイムオブジェクトを生成することに加えて、文字列からタイムオブジェクトを生成することと(レシピ1.2を参照)、別のタイムオブジェクトからタイムオブジェクトを生成すると(レシピ1.5を参照)も可能である。また、ファクトリメソッドを使用して、年、月、日といった暦と時計からタイムオブジェクトを生成することも出来る。
 ファクトリメソッドTime.localおよびTime.gmは、その時間に対するTimeオブジェクトを引数に取る。ローカルタイムの場合はTime.localを使用し、GMTの場合はTime.gmを使用する。yearの後の引数はすべてオプションであり、デフォルト値は0である。

Time.local(1999, 12, 31, 23, 5, 1044)
# => Fri Dec 31 23:21:05 EST 1999

Time.gm(1999, 12, 31, 23, 21, 5, 22, 1044)
# => Fri Dec 31 23:21:05 UTC 1999

TIme.local(1991, 10, 1)
# => Tue Oct 01 00:00:00 EDT 1991

Time.gm(2000)
# => Sat Jan 01 00:00:00 UTC 2000

 DateTimeにおいてTime.localに相当するのは、civilファクトリメソッドである。このメソッドの引数はTime.localのものとほぼ同じだが、完全に同じではない。

[year, month, day, hour, minute, second, timezone_offset, date_of_calendar_reform].

次に、Time.localとTime.gmtの主な違いをまとめる。

・小数秒のためのusec引数がない。小数秒はsecondに有理数を渡すことによって表せる。
・引数は全てオプションである。だが、年のデフォルト値は紀元前4712年なので、これではおそらく都合が悪い。
タイムゾーンごとに異なるメソッドを提供するのではなく、GMTからのオフセットを小数日として渡さなければならない。デフォルト値は0であり、タイムゾーンなしでDateTime.civilを呼び出すと、GMTでの時間が返される。

DateTime.civil(1999, 12, 31, 23, 21 Rational(51044, 100000)).to_s
# => "1999-12-31T23:21:00Z"

DateTime.civil(1991, 10, 1).to_s
# => "1991-10-01T00:00:00Z"

DateTime.civil(2000).to_s
# => "2000-01-01T00:00:00Z"

 ローカルタイムゾーンGMTオフセットを取得する最も簡単な方法は、DateTime.nowの結果に対してoffsetを呼び出すことである。そして、このオフセットをDateTime.civilに渡せばよい。

my_offset = DateTime.now.offset           # Rational(-5, 24)

DateTime.civil(1999, 12, 31, 23, 21 Rational(51044, 100000), my_offset).to_s
# => "1999-12-31T23:21:00-0500"

 暦の改正のことも忘れてはならない。Timeオブジェクトが限られた範囲内の日付け(32ビットシステムでは20世紀と21世紀の日付け)しか表せないことを思い出そう。DateTimeオブジェクトは、どのような日付けでも表すことが出来る。DateTimeで過去の日付けを扱う際には、この広い範囲の大小として、暦の改正を考慮しなければならない。過去の日付けを使用する場合には、ユリウス暦(4年ごとにうるう年となる)からより正確なグレゴリオ暦(うるう年がない場合がある)への切り替えによって生ずるずれに遭遇するかもしれない。
 暦が改正された時期は国ごとに異なっており、それによって生ずるずれの大きさも異なっている。地域の暦には、何世紀にもわたるユリウス暦に関連するものと想定される。アメリカとイギリスのユーザーのために、Rubyにはイギリスとその植民地がグレゴリオ暦に改正した日に相当するDate::ENGLAND定数が用意されている。DateTimeクラスのコンストラクタとファクトリメソッドは、各国で暦が改正された日を示す追加引数として、Date::ENGLANDまたはDate::ITALYを受け取る。これらの引数は旧式のユリウス日でも指定できるため、全ての国の古い日付けを処理できる。

# イタリアでは、1582年10月4日の翌日が1582年10月15日
#
Date.new(1582, 10, 4).to_s
# => "1582-10-04"
Date.new(1582, 10, 5).to_s
# => ArgumentError: invalid date
Date.new(1582, 10, 4).succ.to_s
# => "1582-10-15"

#イギリスでは、1752年9月2日の翌日が1752年9月14日
#
Date.new(1752, 9, 2, Date::ENGLAND).to_s
# => "1752-09-02"
Date.new(1752, 9, 3, Date::ENGLAND).to_s
# => ArgumentError: invalid date
Date.new(1752, 9, 2, DateTime::ENGLAND).succ.to_s
# => "1752-09-14"
Date.new(1582, 10, 5, Date::ENGLAND).to_s
# => "1582-10-05"

 Rubyグレゴリオ変換機能を使用する必要は恐らくないだろう。すでに正確に割り出され、特定のロケールに関連づけられている古い日付けをコンピュータアプリケーションで扱うことは希だからだ。


参照

・各国のグレゴリオ変換の日付一覧(http://www.polysyllabic.com/GregConv.html
・レシピ1.7、レシピ1.8