Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

[Rails5] Active Support Core ExtensionsのStringクラス(3)#to_time、#to_date、#to_datetime

こんにちは、hachi8833です。

Active Support探訪シリーズ第3回は、Core Extensionsのconversions_rbにある、Stringクラスの3つのメソッドです。

今回のメソッド

条件

conversions.rb

ソースのコメント等は適宜省略します。

require 'date'
require 'active_support/core_ext/time/calculations'

class String
  def to_time(form = :local)
    parts = Date._parse(self, false)
    used_keys = %i(year mon mday hour min sec sec_fraction offset)
    return if (parts.keys & used_keys).empty?

    now = Time.now
    time = Time.new(
      parts.fetch(:year, now.year),
      parts.fetch(:mon, now.month),
      parts.fetch(:mday, now.day),
      parts.fetch(:hour, 0),
      parts.fetch(:min, 0),
      parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
      parts.fetch(:offset, form == :utc ? 0 : nil)
    )

    form == :utc ? time.utc : time.to_time
  end

  def to_date
    ::Date.parse(self, false) unless blank?
  end

  def to_datetime
    ::DateTime.parse(self, false) unless blank?
  end
end

Active Supportらしい短いコードなのでほっとします。

追伸: RubyMineとpryはコードダイブにも便利

Railsのコードを追うときは、実際にはどのクラスのメソッドが呼び出されているのかを常に気にする必要があります。RubyMineの強力なコードジャンプ機能pryでのメソッド定義表示は、こういうときにもとても役に立ちますね。

コードの内容

String#to_timeString#to_dateString#to_datetimeはRubyにはないメソッドです。

たまにピュアRubyを使っていると#to_dateがないことに気付いて、いそいそとActive Supportをrequireしたりしますね。

最初に以下をrequireしています。

require 'date'
require 'active_support/core_ext/time/calculations'

参考までに、require元のactive_support/core_ext/time/calculationsではさらに以下をrequireしています。

require 'active_support/duration'
require 'active_support/core_ext/time/conversions'
require 'active_support/time_with_zone'
require 'active_support/core_ext/time/zones'
require 'active_support/core_ext/date_and_time/calculations'
require 'active_support/core_ext/date/calculations'

コード内で頻繁に使われている#parseメソッドは、RubyのDateクラス、Timeクラス、DateTimeクラスのメソッドです。以下ではDate#parseを明示的に呼んでいます。

::Date.parse(self, false) unless blank?

String#to_dateString#to_datetime

#to_date#to_datetimeunless blank?だけチェックして、素直にDate#parseDateTime#parseを呼んでいます。

  def to_date
    ::Date.parse(self, false) unless blank?
  end

  def to_datetime
    ::DateTime.parse(self, false) unless blank?
  end

簡単でよかった。

String#to_time

String#to_timeはもう少し凝ったコードです。デフォルト値は:localです。

  def to_time(form = :local)
    parts = Date._parse(self, false)
    used_keys = %i(year mon mday hour min sec sec_fraction offset)
    return if (parts.keys & used_keys).empty?

    now = Time.now
    time = Time.new(
      parts.fetch(:year, now.year),
      parts.fetch(:mon, now.month),
      parts.fetch(:mday, now.day),
      parts.fetch(:hour, 0),
      parts.fetch(:min, 0),
      parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
      parts.fetch(:offset, form == :utc ? 0 : nil)
    )

    form == :utc ? time.utc : time.to_time
  end

#_parseメソッド

上でアンダースコア付きの#_parseメソッドが呼ばれています。

parts = Date._parse(self, false)

Date#_parseはRubyのext/date/date_core.cにありました。リンク先はRubyの標準ライブラリなのでC言語のコードになっています。

アンダースコアなしのDate#parseは時間の文字列をDateオブジェクトに変換するメソッドですが、アンダースコアありのDate#_parseはハッシュを返す点が異なります。

parse and _parse

#fetchメソッド

続く#fetchメソッドはRubyのHashクラスで、hash.cにあります。hash.cファイルがRubyプロジェクトディレクトリの直下にあったのでちょっとびっくりしました。

parts.fetch(:year, now.year),

Hash#fetchはハッシュのキーを与えて値を返しますが、キーが空の場合のデフォルト値を与えられるので簡潔に書くことができます。

タイムゾーンがUTCの場合の処理

最後にformが:utcの場合の処理を加えています。それ以外の場合はformで指定したタイムゾーン(デフォルトは:local)が使われます。

form == :utc ? time.utc : time.to_time

こんなふうになっていたんですね。

関連記事

[Rails5] Active Support Core ExtensionsのStringクラス(2)html_safe

[Rails5] Active Support Core ExtensionsのStringクラス(1)String#blank?

Rails: ビューのHTMLエスケープは#link_toなどのヘルパーメソッドで解除されることがある


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。