Natural Language Date & Time Parsing for ActiveRecord

Update: If you’re on Rails 2.1 or later, be sure to read the update to this post.

Chronic is a nice natural language parser for Ruby, but my first stab at adding it to a Rails application immediately felt wrong. I was adding special case code in a controller to re-parse the field if the initial parse failed. Of course, that needed to be duplicated for every field I wanted to support it.

A much better (second) idea was, why not add this directly to ActiveRecord? Every model gets it for free, and it turns out the code is hardly any more complicated.

require 'active_record'
require 'chronic'
 
module ChronicParser
  def self.extended(object)
    class << object
      alias_method_chain :string_to_date, :chronic
      alias_method_chain :string_to_time, :chronic
    end
  end
 
  def string_to_date_with_chronic(string)
    value = string_to_date_without_chronic(string)
    if value.nil?
      now = TzTime.now rescue Time.now
      value = Chronic.parse(string, :now => now).to_date rescue nil
    end
 
    value
  end
 
  def string_to_time_with_chronic(string)
    value = string_to_time_without_chronic(string)
    if value.nil?
      now = TzTime.now rescue Time.now
      value = Chronic.parse(string, :now => now)
    end
 
    value
  end
end
 
ActiveRecord::ConnectionAdapters::Column.extend(ChronicParser)

Put this where it will be executed during Rails initialization and you’re done.

Note also that while Chronic doesn’t claim to support time zones, this code will do the right thing by supplying a local perspective of the user’s current time, using TzTime. Someone in Japan using your site (which is hosted in California) in the early morning will get the expected result for “tomorrow.”

If a zone hasn’t been set for TzTime, it’ll fall back on the server’s current time. If you aren’t using TzTime, modify appropriately.