51

I'm using Rails 3.2 and ruby 1.9.3 on Debian. I have an app that collects a date, time, and timezone in the form of strings via an HTML form. Something like this:

start_date: "04-15-2010",
start_time: "10:00:00",
timezone: "Central Time (US & Canada)"

What I'd like to do is parse these 3 elements into a single date that is saved into my database as UTC, which in this case would add 7 hours to the start time, once it's in the UTC time zone. So the stored time would be 17:00 once it's in the DB as UTC instead of the received Central time.

I have tried something like this to parse the date:

ActiveSupport::TimeZone[timezone].at DateTime.strptime("{ 2012-04-09 20:00:00 }", "{ %Y-%m-%d %H:%M:%S }").to_i

However, I'm not able to incorporate the time zone into the resulting time with %Z. It either doesn't parse or the time is interpreted as UTC not Central time. So my question is, how to coerce a date string into a certain time zone without changing the value of the actual date/time stored. I'd like to be able to parse the string into a date/time object that includes the correct time zone with it at that time so that future time zone conversions are accurate. I've looked all over and can't find a way to do this. It's strange, since this seems like something common one does with dates inputted from HTML forms. Thank you for any help.

tadman
  • 208,517
  • 23
  • 234
  • 262
Matt Schwartz
  • 3,374
  • 2
  • 20
  • 15

7 Answers7

121

Try this:

zone = "Central Time (US & Canada)"  

ActiveSupport::TimeZone[zone].parse("2013-04-03 17:47:00")
ryancheung
  • 2,999
  • 3
  • 24
  • 25
62

Use String#in_time_zone:

'1970-01-01 08:00:00'.in_time_zone('Europe/Berlin')
# Thu, 01 Jan 1970 08:00:00.000000000 CET +01:00

It parses a string as date with time and returns a new instance of TimeWithZone. Works in Rails 4.0+.

TZInfo::Timezone.all_identifiers might be handy to get a list of supported time zone indentifiers.

Artur INTECH
  • 6,024
  • 2
  • 37
  • 34
  • 1
    This is working for me with `DateTime.parse`. Thanks – daniel Nov 06 '16 at 12:37
  • Generally I prefer `strptime` where possible, since you can specify the format. But in simple cases like `'20:00'.in_time_zone('UTC')` this'll do. Or when you're bound to guess the format. – x-yuri Jul 02 '18 at 18:13
  • Damn, this is super clean and exactly what I was looking for. The only time I've ever wanted to manually set the time zone without converting the time is when parsing date and time strings, so this works very well and much cleaner than the others. – Joshua Pinter Oct 20 '18 at 14:09
  • Correct! I have updated the answer. Thanks! https://github.com/rails/rails/commit/331a82a1c8ec25cd8df3297b434cbb7cc020207a#diff-bbcd7cac86b88e2e0d41897accb19f72 – Artur INTECH Oct 20 '18 at 20:55
  • This is exactly what I needed. Thank you! – sambecker Oct 23 '18 at 21:29
  • 1
    The is the most elegant way. Thank you. – fongfan999 Jun 22 '19 at 18:00
  • Worth notice, if you skip zone name param it's gonna use your rails app timezone set in config. – Artur79 Mar 27 '23 at 19:59
25

%Z is the correct way to specify a Time zone name. Have you tried the following ?

date_and_time = '%m-%d-%Y %H:%M:%S %Z'
DateTime.strptime("04-15-2010 10:00:00 Central Time (US & Canada)",date_and_time)
0x4a6f4672
  • 27,297
  • 17
  • 103
  • 140
  • Does `%Z` allow for time-zone names with spaces? I always thought it expected things like `EST5EDT` or `-0500`. – tadman Apr 10 '12 at 16:21
  • 1
    I am not sure, the example above works for me and gives the result "Thu, 15 Apr 2010 10:00:00 -0600". If you know the offset you can pass it also directly in the form of "-0600". I guess the system recognizes every timezone from "rake time:zones:all". – 0x4a6f4672 Apr 10 '12 at 16:25
  • 1
    I meant to say this. Thank you @0x4a6f4672! This was not working: DateTime.strptime("{ 2012-04-09 20:00:00 Central Time (US & Canada) }", "{ %Y-%m-%d %H:%M:%S %Z}") but when I took out the curly brackets as in your example, it worked. The following worked, and the only difference was the removed curly braces, that I had read in some strptime examples or docs as part of how strptime worked: DateTime.strptime("2012-04-09 20:00:00 Central Time (US & Canada)", "%Y-%m-%d %H:%M:%S %Z"). I thought it was the spaces in the time zone throwing it off but it was the braces. Thanks again. – Matt Schwartz Apr 11 '12 at 01:28
  • 11
    This does not work, because DateTime does not handle daylight savings correctly. If I enter the DateTime.strptime for 10am April 15, I will get: Thu, 15 Apr 2010 10:00:00 -0600. It should actually be Thu, 15 Apr 2010 10:00:00 -0500 because April 15 is during CDT (Central Daylight Time), not CST (Central Standard Time). Use ryancheung's answer below to get the right results: ActiveSupport::TimeZone["Central Time (US & Canada)"].parse("2013-04-03 17:47:00") – idrinkpabst Aug 01 '13 at 21:50
  • My Time.zone.name is not recognized by %Z in strf/ptime (kept saving what was passed as if in Greenwich), so I found the offset separately, divided by 3600, then zero-padded it and added "+" for formatting to "+0600". Insane I cannot just type DateTime.strptime(blah).in_time_zone or Time.zone.strptime(blah). – JosephK Jun 19 '17 at 15:51
2

This is the method that I came up with. Not the prettiest, but it works. Allows parsing the string using a specified format, and then turning it into the format that I know Time.zone.parse requires.

class ActiveSupport::TimeZone
  def strptime(time, format='%m/%d/%Y')
    formatted = Time.strptime(time, format).strftime('%Y-%m-%d %T')
    parse(formatted)
  end
end

Then you can do something like what was mentioned in another question, but with a specified format:

zone = "Central Time (US & Canada)"  
ActiveSupport::TimeZone[zone].strptime('2013-04-03', '%Y-%m-%d')

Or if you already have a time zone set:

Time.zone = "Central Time (US & Canada)" 
Time.zone.strptime('01/13/2006')

I used a default format of %m/%d/%Y because that's what my user input is most of the time. You can customize this to your needs, or use the default format DateTime uses which is believe is iso8601 (%FT%T%z)

ifightcrime
  • 1,216
  • 15
  • 18
  • My time zone is set - looked great, but: "undefined method `strptime' for # – JosephK Jun 19 '17 at 15:28
  • Be aware that since 5.0 ActiveSupport::TimeZone added this method. src here https://github.com/rails/rails/commit/a5e507fa0b8180c3d97458a9b86c195e9857d8f6 – ngelx Sep 24 '19 at 16:35
2

I've finally found the dirty, yet definitive way to do this.

First, parse the string using plain Ruby Time.strptime like this:

time = Time.strptime('12 : 00 : PM', '%I : %M : %p')

This way you get the parsed Time, but not yet in correct timezone. To fix that, let's convert the time to string form and parse it with the standard ActiveSupport::TimeZone#parse

Time.zone.parse(time.to_s)

The result is the ActiveSupport::TimeWithZone with our time parsed into the correct timezone.

The reason why we have to do it this way is that neither ActiveSupport::TimeZone nor ActiveSupport::TimeWithZone support the strptime method. So we have to parse the Time with core Ruby strptime that does not have timezone information, convert it to format acceptable in ActiveSupport objects and then parse it yet again.

Phitherek_
  • 734
  • 1
  • 8
  • 13
  • `Time.zone.parse('2020-01-22')` as used here is the easiest way to parse a date in the current timezone. Worth noting that it returns `nil` for invalid inputs. – jethro Jan 22 '20 at 21:59
-1

To have DateTime take the date string and attach a timezone other than UTC without changing the values of the date string , use this, its easy , doesnt break on leap day :)

xx = DateTime.strptime("9/1/15 #{object.time_zone}", "%m/%d/%Y %Z")
Jon
  • 555
  • 5
  • 5
-1

Convert specific date format in UTC.

ActiveSupport::TimeZone['UTC'].parse(Time.strptime('01/24/2019T16:10:16', "%m/%d/%YT%H:%M:%S").asctime)
DenisKo
  • 92
  • 1
  • 7