[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

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

関連記事

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833 コボラー、ITコンサル、ローカライズ業界を経てなぜかWeb開発者志願。 これまでにRuby on Rails チュートリアルの大半、Railsガイドのほぼすべてを翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

BPSアドベントカレンダー

人気の記事