Convert DateTime String to UTC in rails - ruby-on-rails

I have a string like this:
"2010-01-01 12:30:00"
I need that to convert to UTC from the current local time zone.
I tried this, but it seems to think that the string is already UTC.
"2010-01-01 12:30:00".to_datetime.in_time_zone("Central Time (US & Canada)")
=> Fri, 01 Jan 2010 06:30:00 CST -06:00
I am not sure where to go from here.
added this from my comment:
>> Time.zone = "Pacific Time (US & Canada)"
=> "Pacific Time (US & Canada)"
>> Time.parse("2010-10-27 00:00:00").getutc
=> Wed Oct 27 06:00:00 UTC 2010
>> Time.zone = "Mountain Time (US & Canada)"
=> "Mountain Time (US & Canada)"
>> Time.parse("2010-10-27 00:00:00").getutc
=> Wed Oct 27 06:00:00 UTC 2010
Thanks for any help.

Time.parse("2010-01-01 12:30:00").getutc
EDIT
(grinding teeth while thinking about the nightmare which is Ruby/Rails date/time handling)
OK, how about this:
Time.zone.parse("2010-01-01 12:30:00").utc
Note that Time.zone.parse returns a DateTime, while appending the .utc gives you a Time. There are differences, so beware.
Also, Time.zone is part of Rails (ActiveSupport), not Ruby. Just so you know.

In Rails 4 and above you can directly use in_time_zone
"2010-01-01 12:30:00".in_time_zone
#=> Fri, 01 Jan 2010 12:30:00 EST -05:00
"2010-01-01 12:30:00".in_time_zone.utc
#=> 2010-01-01 17:30:00 UTC

For APIs you can use:
utc_date = Time.parse("2013-05-31 00:00").utc.iso8601 #=> Result will be: 2013-05-30T21:00:00Z
You can check these articles:
Working with time zones in Ruby on Rails
ISO8601 - Dates In Ruby

Related

Set the right UTC DateTime considering clock changes

I have a ruby on rails app that creates event, from the frontend the date of the event is generated in Mountain Time and then the application transforms it in UTC.
The issue is that since the events are generated in the future sometimes we have issues with the clock change.
Let's say the event should happen:
Day X at 9:30 MT
It would be transformed in:
Day X at 14:30 UTC
But if we create an event in the future that fall in the week the clock change we would have an event configured at the wrong time, because it does not take into consideration the clock change.
Is there a way to generate a UTC dateTime from a specific TimeZone considering if the clock change would happen in that date?
According to Rails API
if your app has its time zone configured as Mountain Time(MT),
Daylight Saving Time(DST) works by default
# application.rb:
class Application < Rails::Application
config.time_zone = 'Mountain Time (US & Canada)'
end
Time.zone # => #<ActiveSupport::TimeZone:0x000000...>
Time.zone.name # => "Mountain Time (US & Canada)"
Time.zone.now # => Tue, 14 Dec 2021 09:46:09 MST -07:00
So if the event date falls after
the DST change, parsing (see ActiveSupport::TimeWithZone)
that date should return the appropiate time
time = Time.zone.parse('2022-03-13 01:00:00') # the parsed datetime is in the same timezone
=> Sun, 13 Mar 2022 01:00:00 MST -07:00
time.dst?
=> false
time = Time.zone.parse('2022-03-13 02:00:00')
=> Sun, 13 Mar 2022 03:00:00 MDT -06:00
time.dst?
=> true
You mention that the application transforms it to UTC. So if I assume,
the correct UTC date is passed to the backend(maybe as an ISO8601 encoded string), you should parse it and convert it to the app time zone by doing something like this:
date = params[:date]
# => "2021-12-14 18:05:05"
utc_datetime = DateTime.parse(date, "%Y-%m-%d %H:%M:%S")
=> 2021-12-14 18:05:05 +0000
mt_datetime = utc_datetime.in_time_zone
=> 2021-12-14 11:05:05 MST -07:00
...
end

Why does ActiveRecord return a UTC datetime in this case?

I have three models:
class EventInstance
has_many: :conflict_resource_bookings
belongs_to :event_template
end
class ConflictResourceBooking
belongs_to :event_instance
# Cached to simplify data loading:
belongs_to :event_template
end
class EventTemplate
has_many :event_instances
end
When I'm querying for datetimes, they usually come back in Time.zone:
Time.zone = "Pacific Time (US & Canada)"
ConflictResourceBooking.joins(:event_instance).order("event_instances.starts_at").first.event_instance.starts_at
# => Sat, 11 Feb 2017 15:00:00 PST -08:00
EventInstance.all.minimum(:starts_at)
# => Sat, 17 Dec 2016 13:00:00 PST -08:00
But I was surprised in one case, the datetime from ActiveRecord came in UTC, not Time.zone:
Time.zone = "Pacific Time (US & Canada)"
ConflictResourceBooking.joins(:event_instance).minimum("event_instances.starts_at")
# => 2017-02-11 23:00:00 UTC
ConflictResourceBooking.includes(:event_instance).minimum("event_instances.starts_at")
# => 2017-02-11 23:00:00 UTC
# Or, with EventTemplate:
ConflictResourceBooking.joins(:event_template).minimum("event_templates.starts_at")
# => 2017-01-07 23:00:00 UTC
In a different case, the combination of joins(...).minimum does return a localized datetime:
EventInstance.joins(:event_template).minimum("event_templates.starts_at")
# => Sat, 17 Dec 2016 13:00:00 PST -08:00
I would like to get a datetime in Time.zone, but I don't know why some of them came back in UTC. Have I made a mistake?
Is there something else I can look into to explain this difference?
Setting Time.zone
Our application is multi-tenant with users all around the world. In order to show times in the user's timezone, we assign Time.zone at the beginning of each request (in a ApplicationController.before_action hook). The assigned timezone is from the User record, eg Time.zone = current_user.time_zone.
In the examples above, I was in rails console, so I assigned Time.zone directly.

Make Rails ignore daylight saving time when displaying a date

I have a date stored in UTC in my Rails app and am trying to display it to a user who has "Eastern Time (US & Canada)" as their timezone. The problem is that rails keeps converting it to Eastern Daylight Time (EDT) so midnight is being displayed as 8am when it should be 7am. Is there anyway to prevent the DST conversion?
>> time = DateTime.parse("2013-08-26T00:00:00Z")
=> Mon, 26 Aug 2013 00:00:00 +0000
>> time.in_time_zone("Eastern Time (US & Canada)")
=> Sun, 25 Aug 2013 20:00:00 EDT -04:00
Update
I eventually went with a twist on #zeantsoi 's approach. I'm not a huge fan of adding too many rails helpers so I extended active support's TimeWithZone class.
class ActiveSupport::TimeWithZone
def no_dst
if self.dst?
self - 1.hour
else
self
end
end
end
Now I can do time.in_time_zone("Eastern Time (US & Canada)").no_dst
Create a helper that utilizes the dst? method on TimeZone to check whether the passed timezone is currently in DST. If it is, then subtract an hour from the supplied DateTime instance:
# helper function
module TimeConversion
def no_dst(datetime, timezone)
Time.zone = timezone
if Time.zone.now.dst?
return datetime - 1.hour
end
return datetime
end
end
Then, render the adjusted (or non-adjusted) time in your view:
# in your view
<%= no_dst(DateTime.parse("2013-08-26T00:00:00Z"), 'Eastern Time (US & Canada)') %>
#=> Sun, 25 Aug 2013 19:00:00 EDT -04:00

Daylight Savings Time not calculated properly in Rails 2.3.5?

I have a very strange issue with Daylight Savings Time (DST) in my app. For some reason, whenever I receive a time from the table, it doesn't adjust itself for DST. For example, if I create a new Time in the console, in the appropriate time zone, write it to the database, and then try to retrieve it from the database, it comes back as one hour earlier.
Here's an example:
Here, we can see that using the console, creating a new Time at 15:00 EST is equal to 19:00 UTC (since adjusted for DST, which makes it -0400 instead of the usual -0500):
ruby-1.8.6-p114 > Time.zone
=> #<ActiveSupport::TimeZone:0x12b1b68 #name="UTC", #tzinfo=nil, #utc_offset=0>
ruby-1.8.6-p114 > Time.zone = "Eastern Time (US & Canada)"
=> "Eastern Time (US & Canada)"
ruby-1.8.6-p114 > Time.zone.parse("15:00")
=> Thu, 09 Sep 2010 15:00:00 EDT -04:00
ruby-1.8.6-p114 > Time.zone.parse("15:00").utc
=> Thu Sep 09 19:00:00 UTC 2010
ruby-1.8.6-p114 > Time.zone.parse("15:00").dst?
=> true
Now, I try to write that same time to the database, and retrieve it back:
ruby-1.8.6-p114 > b = Book.new
=> #<Book id: nil, return_time: nil, created_at: nil, updated_at: nil>
ruby-1.8.6-p114 > b.return_time = Time.zone.parse("15:00")
=> Thu, 09 Sep 2010 15:00:00 EDT -04:00
ruby-1.8.6-p114 > b.save
=> true
ruby-1.8.6-p114 > result = Book.find(:last).return_time
=> Sat Jan 01 19:00:00 UTC 2000
ruby-1.8.6-p114 > result.zone
=> "UTC"
ruby-1.8.6-p114 > result.in_time_zone
=> Sat, 01 Jan 2000 14:00:00 EST -05:00
ruby-1.8.6-p114 > result.dst?
=> false
My environment.rb has this:
config.time_zone = 'UTC'
And application_controller.rb has this:
before_filter :set_user_time_zone
def set_user_time_zone
if current_user
Time.zone = current_user.time_zone
else
Rails.logger.error '[Time.zone.now.to_s][ERROR]: Missing current_user from in set_user_time_zone!'
end
end
Any ideas as to what could be happening here and how to fix it? I've been at this for days now, so really, any help would be greatly appreciated!
Thank you very much.
It looks like you're only saving the time portion; note how the date part goes from Thu, 09 Sep 2010 to Sat, 01 Jan 2000. Since the DST calculation depends on the date, this is probably where you're losing the information. (January 1st is not in DST, so assuming that date, the DST calculation is correct). You probably need to save a DATETIME in the database, not just TIME.
I'm not sure what version of Rails you're using, but I had the same problem in 2.1.2. It seems the time zone library included just doesn't work half the year, but the developers didn't think it was worth switching; hopefully they've rethought that decision by now.
In any case, we ended up using the tzinfo_timezone plugin, which computes UTC offsets correctly during daylight savings time.

Rails ActiveSupport Time Parsing?

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
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:
http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html
http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html
ActiveSupport::TimeZone.new('UTC').parse('2009-09-23 09:18:08')
=> Wed, 23 Sep 2009 09:18:08 UTC +00:00
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
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
>> "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.

Resources