33

Rails' ActiveSupport module extends the builtin ruby Time class with a number of methods.

Notably, there is the to_formatted_s method, which lets you write Time.now.to_formatted_s(:db) to get a string in Database format, rather than having to write ugly strftime format-strings everywhere.

My question is, is there a way to go backwards?

Something like Time.parse_formatted_s(:db) which would parse a string in Database format, returning a new Time object. This seems like something that rails should be providing, but if it is, I can't find it.

Am I just not able to find it, or do I need to write it myself?

Thanks

Orion Edwards
  • 121,657
  • 64
  • 239
  • 328

5 Answers5

62

It looks like ActiveSupport does provide the parsing methods you are looking for (and I was looking for too), after all! — at least if the string you are trying to parse is a standard, ISO-8601-formatted (:db format) date.

If the date you're trying to parse is already in your local time zone, it's really easy!

 > Time.zone.parse('2009-09-24 08:28:43')
=> Thu, 24 Sep 2009 08:28:43 PDT -07:00

 > Time.zone.parse('2009-09-24 08:28:43').class
=> ActiveSupport::TimeWithZone

and that time-zone-aware time can then easily be converted to UTC

 > Time.zone.parse('2009-09-24 08:28:43').utc
=> 2009-09-24 15:28:43 UTC

or to other time zones:

 > ActiveSupport::TimeZone.us_zones.map(&:name)
=> ["Hawaii", "Alaska", "Pacific Time (US & Canada)", "Arizona", "Mountain Time (US & Canada)", "Central Time (US & Canada)", "Eastern Time (US & Canada)", "Indiana (East)"]

 > Time.zone.parse('2009-09-24 08:28:43').utc.in_time_zone('Eastern Time (US & Canada)')
=> Thu, 24 Sep 2009 11:28:43 EDT -04:00

If the date string you're trying to parse is in UTC, on the other hand, it doesn't look like there's any method to parse it directly into a TimeWithZone, but I was able to work around that be first using DateTime.strptime...

If the date you're trying to parse is in UTC and you want it to stay as UTC, you can use:

 > DateTime.strptime('2009-09-24 08:28:43', '%Y-%m-%d %H:%M:%S').to_time
=> 2009-09-24 08:28:43 UTC

If the date you're trying to parse is in UTC and you want it converted to your default time zone, you can use:

 > DateTime.strptime('2009-09-24 08:28:43', '%Y-%m-%d %H:%M:%S').to_time.in_time_zone
=> Thu, 24 Sep 2009 01:28:43 PDT -07:00

It looks like it can even parse other formats, such as the strange format that Time#to_s produces:

irb -> Time.zone.parse('Wed, 23 Sep 2009 02:18:08').to_s(:db)
    => "2009-09-23 09:18:08"

irb -> Time.zone.parse('Wed, 23 Sep 2009 02:18:08 EDT').to_s(:db)
    => "2009-09-23 06:18:08"

I'm quite impressed.

Here are some more examples from [http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html][1]:

  Time.zone = 'Eastern Time (US & Canada)'        # => 'Eastern Time (US & Canada)'
  Time.zone.local(2007, 2, 10, 15, 30, 45)        # => Sat, 10 Feb 2007 15:30:45 EST -05:00
  Time.zone.parse('2007-02-10 15:30:45')          # => Sat, 10 Feb 2007 15:30:45 EST -05:00
  Time.zone.at(1170361845)                        # => Sat, 10 Feb 2007 15:30:45 EST -05:00
  Time.zone.now                                   # => Sun, 18 May 2008 13:07:55 EDT -04:00
  Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone  # => Sat, 10 Feb 2007 15:30:45 EST -05:00

More documentation links for reference:

Bryan Ash
  • 4,385
  • 3
  • 41
  • 57
Tyler Rick
  • 9,191
  • 6
  • 60
  • 60
  • Thanks for posting so much detail. Your DateTime.strptime() reference is very useful for the case where I need to parse a date out of the database that ActiveRecord::Base.connection.select_value() returns as a string. – Lee Nov 02 '09 at 21:06
  • Killer answer. I didn't realize the Time.zone = "..." worked. You just saved me a ton of time. – rhh Aug 04 '10 at 19:23
  • 3
    Thanks for the answer! If you don't want to change global TZ you can use Time::use_zone, eg. for parsing UTC, use Time.use_zone('UTC'){Time.zone.parse '2012-11-01 13:37'}. – Divide Nov 16 '12 at 19:30
  • Thanks for the tip. I wasn't aware of Time::use_zone! – Tyler Rick Nov 19 '12 at 16:11
5
ActiveSupport::TimeZone.new('UTC').parse('2009-09-23 09:18:08')
=> Wed, 23 Sep 2009 09:18:08 UTC +00:00
Leventix
  • 3,789
  • 1
  • 32
  • 41
3

Rails 5 finally provides strptime!

value = '1999-12-31 14:00:00'
format = '%Y-%m-%d %H:%M:%S'
Time.zone.strptime(value, format)
# => Fri, 31 Dec 1999 14:00:00 HST -10:00

ActiveSupport::TimeZone.all.sample.strptime(value, format)
# => Fri, 31 Dec 1999 14:00:00 GST +04:00
deprecated
  • 5,142
  • 3
  • 41
  • 62
2

I just ran into this as well and none of the above answers were satisfactory to me. Ideally one could use ActiveSupport::TimeZone just like Time and call .strptime on it with any arbitrary format and get back the correct TimeZone object. ActiveSupport::TimeZone.strptime doesn't exist so I created this monkeypatch:

 class ActiveSupport::TimeZone
    def strptime(str, fmt, now = self.now)
      date_parts = Date._strptime(str, fmt)
      return if date_parts.blank?
      time = Time.strptime(str, fmt, now) rescue DateTime.strptime(str, fmt, now)
      if date_parts[:offset].nil?
        ActiveSupport::TimeWithZone.new(nil, self, time)
      else
        time.in_time_zone(self)
      end
    end
  end
Ben Mabey
  • 1,269
  • 10
  • 18
1
>> "2009-09-24".to_date
=> Thu, 24 Sep 2009
>> "9/24/2009".to_date
=> Thu, 24 Sep 2009

Works great unless your date is in some weird format.

eremite
  • 1,886
  • 15
  • 18