Best way to convert timeZone in Rails - ruby-on-rails

I want to convert one time from one timezone to another.
Example:
Image that I have a first Time, like
t=....
puts t
=> Sun Aug 12 00:00:00 +0200 2012
I want to have the exact same hour, but in a different time zone.
I can do this:
mytimezone.local(t.year,t.month,t.day,t.hour,t.min)
=> Sun, 12 Aug 2012 00:00:00 BRT -03:00
So is there a way to accomplish the same things in a better way?

Have you tried in_time_zone method?
> t = Time.parse("Sun Aug 12 00:00:00 +0200 2012")
=> 2012-08-12 02:00:00 +0400
> t.in_time_zone('Eastern Time (US & Canada)')
=> Sat, 11 Aug 2012 18:00:00 EDT -04:00
> mytimezone = ActiveSupport::TimeZone.new('Eastern Time (US & Canada)')
=> (GMT-05:00) Eastern Time (US & Canada)
> t.in_time_zone(mytimezone)
=> Sat, 11 Aug 2012 18:00:00 EDT -04:00

Related

How do you convert the following time from UTC to EST in Ruby (without Rails)?

I am trying to covert the UTC time "2018-04-02T14:30:00Z" to EST in pure Ruby. I noticed the following discrepancy. If I parse the time in UTC using Rails and then add the EST zone_offset I get a different time than using the "in_time_zone" helper. 9:30 versus 10:30.
2.2.4 :001 > t = Time.parse "2018-04-02T14:30:00Z"
=> 2018-04-02 14:30:00 UTC
2.2.4 :002 > t + Time.zone_offset("EST")
=> 2018-04-02 09:30:00 UTC
2.2.4 :003 > t.in_time_zone('Eastern Time (US & Canada)')
=> Mon, 02 Apr 2018 10:30:00 EDT -04:00
Rails does time zones really well, and you're going to have a difficult time replicating the results of ActiveSupport::TimeWithZone#in_time_zone without just using it.
For example, as you've pointed out:
>> t = Time.parse "2018-04-02T14:30:00Z"
>> t.in_time_zone('Eastern Time (US & Canada)')
Mon, 02 Apr 2018 10:30:00 EDT -04:00
But consider that you can likewise do:
>> t = Time.parse "2018-01-02T14:30:00Z"
>> t.in_time_zone('Eastern Time (US & Canada)')
Tue, 02 Jan 2018 09:30:00 EST -05:00
In other words, ActiveSupport is handling not only your time zones, but also your Standard/Daylight Savings challenges, all for free. Not even moment.js will do that (or if it will, I haven't figured out how).
Have you considered using require 'active_support', which would give you this functionality without using all of Rails?
you can try the localtime method, something like Time.parse("2018-04-02T14:30:00Z").localtime("-05:00").strftime("%m/%d/%Y %I:%M %p") to pretty print to get the time object remove the strftime
I believe that "Eastern Time (US & Canada)" is actually EDT and not EST. That would explain your hour difference.
irb(main):014:0> t = Time.parse "2018-04-02T14:30:00Z"
=> 2018-04-02 14:30:00 UTC
irb(main):015:0> t.in_time_zone('EST')
=> Mon, 02 Apr 2018 09:30:00 EST -05:00
irb(main):016:0> t.in_time_zone('Eastern Time (US & Canada)')
=> Mon, 02 Apr 2018 10:30:00 EDT -04:00
irb(main):017:0>
See also: https://time.is/EST
and https://time.is/EDT
Also, timezones are a huge pain and routinely break my brain.

How to taking time zone into account when using Date#to_time method

I have a Rails project that has Tokyo(+0900) time zone.
And OS local time zone is Bangkok(+0700).
Date#to_time method doesn't take time zone into account.
Date.current.to_time
2016-06-21 00:00:00 +0700
I'm using now Time.zone.parse method:
Time.zone.parse(Date.current.to_s)
Tue, 21 Jun 2016 00:00:00 JST +09:00
Is there a better way to convert a date to time with the proper time zone?
Time.zone
Time.zone
#<ActiveSupport::TimeZone:0x007fa8402f86f0 #name="Tokyo", #utc_offset=nil, #tzinfo=#<TZInfo::TimezoneProxy: Asia/Tokyo>, #current_period=#<TZInfo::TimezonePeriod: #<TZInfo::TimezoneTransitionDefinition: #<TZInfo::TimeOrDateTime: -578044800>,#<TZInfo::TimezoneOffset: 32400,0,JST>>,nil>>
config/application.rb
module MyProject
class Application < Rails::Application
config.time_zone = 'Tokyo'
end
end
This has been fixed in Ruby 2.4
https://wyeworks.com/blog/2016/6/22/behavior-changes-in-ruby-2.4
to_time by default takes local time zone to convert the time in time zone, and it accepts only :local or :utc time zone in the parameter. so you have to set the time zone before you apply .to_time on Date object.
Time.zone = "Tokyo"
irb(main):046:0> Date.current.to_time
=> Tue, 21 Jun 2016 09:00:00 JST +09:00
OR you can user .use_zone with the block to keep that set that time zone for the particular block.
irb(main):046:0> Time.use_zone("Tokyo"){Date.current.to_time}
=> Tue, 21 Jun 2016 09:00:00 JST +09:00
Above result will always give you time with respect to UTC so time will be added with respect to selected time zone. if you want it to be set to start of the day you can use beginning_of_day
Time.zone = "Tokyo"
irb(main):048:0> Date.current.to_time.beginning_of_day
=> Tue, 21 Jun 2016 00:00:00 JST +09:00
irb(main):049:0> Time.use_zone("Tokyo"){ Date.current.to_time.beginning_of_day }
=> Tue, 21 Jun 2016 00:00:00 JST +09:00
Using Date object for Time Zone operations is not a better approach, always use Time object to deal with Time Zone
Time.zone = "Tokyo"
irb(main):050:0> Time.zone.now.beginning_of_day
=> Tue, 21 Jun 2016 00:00:00 ICT +09:00
irb(main):051:0> Time.use_zone("Tokyo"){ Time.zone.now.beginning_of_day }
=> Tue, 21 Jun 2016 00:00:00 JST +09:00
Do’s and Don’ts of Rails Timezones

Why Time.now returns PST if my TIme.zone is UTC?

In my Rails app I do the following:
Time.zone.name #=> 'UTC'
Why when I do:
Time.parse('2014-12-19').end_of_day.to_datetime
I get: Fri, 19 Dec 2014 23:59:59 -0800
And when I do:
Time.use_zone('Pacific Time (US & Canada)') { Time.parse('2014-12-19').end_of_day.to_datetime }
I get the exact same thing: Fri, 19 Dec 2014 23:59:59 -0800
Why is the zone not being applied?
You need to use Time.zone.parse to make it use the proper timezone.
2.1.3 (main):0 > Time.zone.name
=> "UTC"
2.1.3 (main):0 > Time.zone.parse('2014-12-19').end_of_day.to_datetime
=> Fri, 19 Dec 2014 23:59:59 +0000
Are you looking for this
Time.use_zone('Pacific Time (US & Canada)') { Time.parse('2014-12-19').end_of_day.to_datetime.in_time_zone}
This would give you the correct time zone. For more information on this have a look at THIS SO QUESTION
You are probably on Windows, which has a bug in Rails and Rails will always return your local time zone. See How does Rails know my timezone?
You can also use
zone = ActiveSupport::TimeZone['Hawaii']
zone.at(Time.now)
=> Tue, 16 Jun 2015 09:22:53 HST -10:00
But I rather like Pamio's idea
Time.now.in_time_zone('Hawaii')
=> Tue, 16 Jun 2015 09:43:17 HST -10:00

In Rails, what's the nicest way to create a specific time (not now) in a particular time zone?

Here's one way, but can you think of a more idiomatic way?
>> Time.use_zone('Sydney'){ Time.zone.parse('2011-04-12 2pm') }
=> Tue, 12 Apr 2011 14:00:00 EST +10:00
I think you're looking for
Time.find_zone('Alaska').local(2011,1,1)
=> Sat, 01 Jan 2011 00:00:00 AKST -09:00
Time.find_zone('Amsterdam').local(2011,1,1)
=> Sat, 01 Jan 2011 00:00:00 CET +01:00
Time.find_zone('Sydney').local(2011,1,1)
=> Sat, 01 Jan 2011 00:00:00 EST +11:00
Time.find_zone('Wellington').local(2011,1,1)
=> Sat, 01 Jan 2011 00:00:00 NZDT +13:00
This also works with parse
Time.find_zone('Sydney').parse('2011-04-12 2pm')
=> Tue, 12 Apr 2011 14:00:00 EST +10:00
For parsing a date within a specific time zone, you can use ActiveSupport::TimeZone
> ActiveSupport::TimeZone["Sydney"].parse("2011-04-12 2pm")
=> Tue, 12 Apr 2011 14:00:00 EST 10:00
TimeZone API documentation is here:
http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html#method-c-5B-5D
This is what I use:
Time.zone.local(2011, 4, 12, 14, 0)
As said above, to create a time in a specific timezone (e.g., 4/10/2014 1:30pm New York):
#event.start_time = Time.find_zone('Eastern Time (US & Canada)').local(2014,4,10,13,30)
=> Thu, 10 Apr 2014 13:30:00 EDT -04:00
#event.start_time.utc
=> 2014-04-10 17:30:00 UTC
When it is saved to your db, it will be converted to UTC (in Postgres at least if using a timestamp type in your migration), and on future access it will be displayed relative to the application timezone set in config/application.rb
To properly display the local time, we also store the timezone name (e.g., 'Eastern Time (US & Canada)' ) in our database. So, when we want to print the time in our views, we do...
#event.start_time.in_time_zone(#event.timezone)
=> Thu, 10 Apr 2014 13:30:00 EDT -04:00
To get the abbreviated timezone (e.g., EST)
#event.start_time.in_time_zone(#event.timezone).zone
=> "EDT"
How about using the *in_time_zone* helper..
Time.now.in_time_zone('Sydney')

converting Date object to TimeWithZone

I need to convert a Date object into a TimeWithZone object representing the beginning of that day in a given time zone.
The following approach works, but seems too convoluted as it requires me to convert the date to a string:
?> date = Date.parse("2010-02-17")
=> Wed, 17 Feb 2010
>> ActiveSupport::TimeZone['Eastern Time (US & Canada)'].parse(date.to_s)
=> Wed, 17 Feb 2010 00:00:00 EST -05:00
>> ActiveSupport::TimeZone['UTC'].parse(date.to_s)
=> Wed, 17 Feb 2010 00:00:00 UTC 00:00
Is there a better way I'm missing?
Edit:
People are suggesting variations of:
?> date.to_datetime.in_time_zone('Eastern Time (US & Canada)').beginning_of_day
=> Tue, 16 Feb 2010 00:00:00 EST -05:00
As you can see, this isn't an equivalent conversion since it leaves me at the start of Feb. 16th EST, instead of the start of Feb. 17th EST.
I'm late to the party, but this is still a great question. ActiveSupport's in_time_zone was introduced since the O.P., but it does exactly what you want without parsing a string (slow) or setting Time.zone (risky):
>> date = Date.parse("2010-02-17")
=> Wed, 17 Feb 2010
>> date.in_time_zone('Eastern Time (US & Canada)')
=> Wed, 17 Feb 2010 00:00:00 EST -05:00
Of course if you want the beginning of day expressed at utc, you can do this:
>> date.in_time_zone('Eastern Time (US & Canada)').utc
=> 2010-02-17 05:00:00 UTC
If you have Time.zone set in Rails then you can call Date#at_beginning_of_day (see http://api.rubyonrails.org/classes/Date.html#method-i-at_beginning_of_day). Contrast this with Date#to_datetime:
Time.zone
=> #<ActiveSupport::TimeZone:0x10cf10858 #tzinfo=#<TZInfo::TimezoneProxy: Etc/UTC>, #utc_offset=nil, #current_period=nil, #name="UTC">
date = Date.today
=> Thu, 31 May 2012
date.to_datetime
=> Thu, 31 May 2012 00:00:00 +0000
date.at_beginning_of_day
=> Thu, 31 May 2012 00:00:00 UTC +00:00
Time.zone = 'America/Chicago'
=> "America/Chicago"
date.to_datetime
=> Thu, 31 May 2012 00:00:00 +0000
date.at_beginning_of_day
=> Thu, 31 May 2012 00:00:00 CDT -05:00
I strongly recommend against any solution that converts the date to a time using to_datetime or to_time because those methods are unaware of the zone, and tacking in_time_zone onto the result, as some answers suggest, won't retroactively fix the mistake. Also, don't try to build your own daylight saving time math using UTC offsets. You're bound to get it wrong, and you're doing work unnecessarily.
Use the TimeZone itself which has this logic built in.
Given a zone and a date, you can get a TimeWithZone for the beginning of the day like this:
time = zone.local(date.year, date.month, date.day)
If you want a specific time of day other than the beginning, you can pass the hour, minute, and second as the 4th, 5th, and 6th arguments to #local.
If zone is actually your system's local time zone (Time.zone), then ActiveSupport will let you shorten the above to this:
time = date.to_time_in_current_zone
All of the above handle daylight saving time correctly. Let's verify that by looking at the UTC offsets for two times, one that's outside DST and one that's within DST:
irb(main):009:0> zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
=> (GMT-05:00) Eastern Time (US & Canada)
irb(main):010:0> t1 = zone.local(2013, 1, 1)
=> Tue, 01 Jan 2013 00:00:00 EST -05:00
irb(main):011:0> t2 = zone.local(2013, 5, 1)
=> Wed, 01 May 2013 00:00:00 EDT -04:00
irb(main):012:0> t1.utc_offset
=> -18000
irb(main):013:0> t2.utc_offset
=> -14400
Would something like this work for you?
'2010-04-01'.to_time.in_time_zone('Eastern Time (US & Canada)').beginning_of_day
Subtract utc_offset:
d = Date.today
Time.zone.class.all.map(&:name).map { |tz| dt = d.to_datetime.in_time_zone(tz); dt -= dt.utc_offset }
Using ActiveSupport::TimeZone[tz] doesn't take daylight savings time into account.
Time.zone.class.all.map(&:name).map { |tz| o = d.to_datetime.in_time_zone(tz).utc_offset - ActiveSupport::TimeZone[tz].utc_offset }

Resources