こんにちは!kossyです!
さて、今回はRailsのActiveSupportのメソッドであるin_time_zoneメソッドの処理の中身を見てみたので、
ブログに残してみたいと思います。
メソッドの定義位置を確認
まずはsource_locationでメソッドの定義位置を確認しましょう。
Date.today.method(:in_time_zone).source_location => ["/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/core_ext/date_and_time/zones.rb", 20]
zones.rbの20行目に定義されていることがわかりました。
githubのRailsのソースコードを見に行ってみましょう。
コメントアウト部分を訳してみます。
ゾーンが指定されている場合、またはTime.zone_defaultが設定されている場合は、同時時間をTime.zoneで返します。 それ以外の場合は、現在の時刻を返します。
Time.zone = 'Hawaii' # => 'Hawaii'
Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00
Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00
このメソッドは、オペレーティングシステムのタイムゾーンの代わりにローカルゾーンとしてTime.zoneを使用することを除いて、Time#localtimeに似ています。
TimeZoneを引数として識別するTimeZoneインスタンスまたは文字列を渡すこともできます。変換は、Time.zoneではなくそのゾーンに基づいて行われます。
ソースコード部分はこんな感じ。
module DateAndTime module Zones def in_time_zone(zone = ::Time.zone) time_zone = ::Time.find_zone! zone time = acts_like?(:time) ? self : nil if time_zone time_with_zone(time, time_zone) else time || to_time end end private def time_with_zone(time, zone) if time ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone) else ActiveSupport::TimeWithZone.new(nil, zone, to_time(:utc)) end end end end
見たことないメソッドもあるので、コンソールで実行しながら動作を確認してみます。
Time.zone
$ Time.zone => #<ActiveSupport::TimeZone:0x0000556b8a3a7488 @name="Tokyo", @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>, @utc_offset=nil> $ Time.zone.methods => [:<=>, :to_s, :tzinfo, :strptime, :=~, :tomorrow, :yesterday, :name, :period_for_local, :period_for_utc, :periods_for_local, :local_to_utc, :utc_to_local, :rfc3339, :at, :parse, :now, :iso8601, :local, ...]
ActiveSupport::TimeZoneインスタンスを返すメソッドのようです。
application.rbでtimezoneをTokyoにしているので、name属性にTokyoが指定されていました。
in_time_zoneの引数は、zoneが与えられていればそのzoneを使い、与えられてなければTime.zoneの返り値が使われていました。
Time.find_zone!
$ zone = Time.zone $ Time.find_zone! zone => #<ActiveSupport::TimeZone:0x0000556b8a3a7488 @name="Tokyo", @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>, @utc_offset=nil>
Time.find_zone!は引数で与えられてzone情報を元にしたActiveSupport::TimeZoneクラスのインスタンスを返すメソッドでした。
acts_like?
メソッドの中身は以下でした。
レシーバが引数のクラスのように振る舞うかどうかを検証しているようです。
def acts_like?(duck) case duck when :time respond_to? :acts_like_time? when :date respond_to? :acts_like_date? when :string respond_to? :acts_like_string? else respond_to? :"acts_like_#{duck}?" end end
in_time_zoneにおいては、timeクラスのような振る舞いをするかどうかを検証し、trueならselfを、falseならnilを返すようです。
time_zoneが存在すればprivateメソッドであるtime_with_zoneを呼んでいますね。
private def time_with_zone(time, zone) if time ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone) else ActiveSupport::TimeWithZone.new(nil, zone, to_time(:utc)) end end
コンソールで試してみます。
$ zone = Time.zone $ time = Time.now => 2021-03-06 11:45:37 +0000 $ ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone) => Sat, 06 Mar 2021 20:45:37 JST +09:00
Time.zone.nowした時と同様の返り値が返りました。
Time.zone.now.class => ActiveSupport::TimeWithZone ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone).class => ActiveSupport::TimeWithZone
どちらもActiveSupport::TimeWithZoneクラスのインスタンスが返っていますね。
ActiveSupport::TimeWithZoneクラスの中身は以下に定義してあります。
ユースケース
to_timeとin_time_zoneがよく比較対象として上がるようです。
to_timeが環境のタイムゾーンを基に値を算出するのに対し、in_time_zoneはRailsのタイムゾーンを参照してくれるので、
環境によって際が生まれずにブレが無くなるといったメリットがあります。
勉強になりました。
大いに参考にさせていただいたサイト
この場を借りて御礼を申し上げます。
Active Support コア拡張機能 - Railsガイド
rails/time_with_zone.rb at 914caca2d31bd753f47f9168f2a375921d9e91cc · rails/rails · GitHub