I have a project model with a datetime attribute to define the deadline. The deadlines are of different time zones, and I receive them in a string format like below:
Jan 1st 2013 00:00:00 EST
Feb 9th 2013 23:59:00 PST
I want to store these values in the default UTC format in the database. I've seen that there are many options to parse the time like Time.zone.parse and Time.parse. My question is: what's the best practice to parse the datetime of different time zones? I'm using Rails 3.2.9.
You need not worry about that at all, as long as you set correct timezone in config/application.rb:
config.time_zone = 'UTC'
You just assign the time strings to the attributes, ActiveRecord will convert it correctly.
1.9.3p125 :002 > project.deadline = "Jan 1st 2013 00:00:00 EST"
=> "Jan 1st 2013 00:00:00 EST"
1.9.3p125 :003 > project.deadline
=> Tue, 01 Jan 2013 05:00:00 UTC +00:00
1.9.3p125 :004 > project.deadline = "Feb 9th 2013 23:59:00 PST"
=> "Feb 9th 2013 23:59:00 PST"
1.9.3p125 :005 > project.deadline
=> Sun, 10 Feb 2013 07:59:00 UTC +00:00
ActiveRecord uses Time.zone.parse to parse the strings internally.
When you run Time.parse it will convert the timestamp to your configured timezone in rails. For example, my rails app runs in EST.
[5] pry(main)> Time.parse('Jan 1st 2013 00:00:00 EST')
=> 2013-01-01 00:00:00 -0500
[6] pry(main)> Time.parse('Feb 9th 2013 23:59:00 PST')
=> 2013-02-10 02:59:00 -0500
Notice the +3:00hrs for the PST timestamp used to get the result into my EST timezone.
To get the UTC version of each timestamp, just call utc
[7] pry(main)> Time.parse('Jan 1st 2013 00:00:00 EST').utc
=> 2013-01-01 05:00:00 UTC
[8] pry(main)> Time.parse('Feb 9th 2013 23:59:00 PST').utc
=> 2013-02-10 07:59:00 UTC
Related
I have this date
date = Mon, 15 Aug 2016 13:00:00 UTC +00:00
which is ActiveSupport::TimeWithZone class
Then, I need to get the time in time zone "Fiji"
start_in_time_zone = date.in_time_zone("Fiji")
This returns Tue, 16 Aug 2016 01:00:00 +12 +12:00
Then, I need to present the date with the name of the time zone, so
time_zone_abbr = start_in_time_zone.strftime("%Z")
It should return "FJT"
but returns "+12"
Any idea why?
I am using ruby 2.3.7 and rails 4.2.7
UPDATE
If I do
start_in_time_zone = date.in_time_zone("Madrid")
it returns
"CEST"
UPDATE 2
I have tried to see where the problem is by setting different time.
date=Time.utc(2018, 07, 25, 20, 30, 45)
date.class #=> Time
date.in_time_zone("Madrid") #=> Wed, 25 Jul 2018 22:30:45 CEST +02:00
date.in_time_zone("Fiji") #=> Thu, 26 Jul 2018 08:30:45 +12 +12:00
date.in_time_zone("EST") #=> Wed, 25 Jul 2018 15:30:45 EST -05:00
Sadly, it seems there is no 'FJT' abbreviation assigned to 'Fiji' in timezone data used by Rails. Also, support for those abbreviations seems patchy regarding Pacific timezones.
irb(main):002:0> DateTime.now.in_time_zone('Samoa').strftime('%Z')
=> "+13"
irb(main):003:0> DateTime.now.in_time_zone('Midway Island').strftime('%Z')
=> "SST"
irb(main):004:0> DateTime.now.in_time_zone('Samoa').strftime('%Z')
=> "+13"
irb(main):005:0> DateTime.now.in_time_zone('Tokelau Is.').strftime('%Z')
=> "+13"
irb(main):006:0> DateTime.now.in_time_zone('Wellington').strftime('%Z')
=> "NZST"
UTC offset is displayed as fallback. If it's any help, remember that full name and additional information can be retrieved with .time_zone.tzinfo on ActiveSupport::TimeWithZone objects. 'FJ' code is recognized by TZInfo::Country.
irb(main):056:0> TZInfo::Country.get('FJ')
=> #<TZInfo::Country: FJ>
irb(main):057:0> TZInfo::Country.get('FJ').zone_info
=> [#<TZInfo::CountryTimezone: Pacific/Fiji>]
I,m using following versions of Ruby and Rails
Ruby : 2.0.0p481 &
Rails : 4.1.1
Could not find any method in DateTime class to convert time to IST.
Tried DateTime.in_time_zone in IRB, could convert IST to EST but not vise versa.
2.0.0-p481 :003 > date = "Thu Jan 07 2016 16:20:00 GMT+0530 (India Standard Time)"
2.0.0-p481 :003 > date = date.in_time_zone('Eastern Time (US & Canada)')
2.0.0-p481 :003 > date
=> Thu, 07 Jan 2016 05:50:00 EST -05:00
Wanted to know how can I convert other tine zones to IST.
Please let me know if there is any way from which I can achive this.
ActiveSupport::TimeZone provide the names of all timezones. You can list out all timezones by doing
ActiveSupport::TimeZone.all.map(&:name)
For just US timezones
ActiveSupport::TimeZone.us_zones.map(&:name)
So change your time with the available timezones. Like this
irb> date = DateTime.now
=> Thu, 31 Dec 2015 22:15:59 +0530
# convert to EST
irb> date_est = date.in_time_zone("Eastern Time (US & Canada)")
=> Thu, 31 Dec 2015 11:45:59 EST -05:00
# convert to IST
irb> date_ist = date_est.in_time_zone("Chennai")
=> Thu, 31 Dec 2015 22:15:59 IST +05:30
Happy coding...
I'm using datetimepicker and need to save the string datetime obtained from params to a datetime in a specific, user dependent time zone. It will allow me to save proper UTC datetime to database.
params[:notify_at] #=> "2014-07-05 14:30:00"
user.time_zone #=> #<ActiveSupport::TimeZone:0x00000007535ac8 #name="Warsaw", #utc_offset=nil, #tzinfo=#<TZInfo::TimezoneProxy: Europe/Warsaw>, #current_period=nil>
And I would like to do something like:
date = params[:notify_at].to_datetime(user.time_zone) #=> Sat, 05 Jul 2014 12:30:00 +0000
(its 14:30 in user's localtime but 12:30 in UTC)
You can use the in_time_zone method. For example:
DateTime.current.in_time_zone("Alaska")
# => Fri, 23 May 2014 07:21:30 AKDT -08:00
So for your use case:
params[:notify_at].to_datetime.in_time_zone(user.time_zone)
Pro Tip: If using Rails v4+ you can actually do this directly on the string:
"2014-07-05 14:30:00".in_time_zone("Alaska")
# => Sat, 05 Jul 2014 14:30:00 AKDT -08:00
UPDATE
You can parse a string directly into a time zone (where the String should already be IN that time zone) like this:
Time.zone.parse("2014-07-05 14:30:00")
# => Sat, 05 Jul 2014 14:30:00 CEST +02:00
So for your use case do:
user.time_zone.parse(params[:notify_at])
Try this:-
date = params[:notify_at].to_datetime.in_time_zone(user.time_zone)
Rails console output:-
1.9.3p385 :004 > d= "2014-07-05 14:30:00"
=> "2014-07-05 14:30:00"
1.9.3p385 :010 > d.to_datetime.in_time_zone("Pacific Time (US & Canada)")
=> Sat, 05 Jul 2014 07:30:00 PDT -07:00
1.9.3p385 :011 > d.to_datetime.in_time_zone("Alaska")
=> Sat, 05 Jul 2014 06:30:00 AKDT -08:00
1.9.3p385 :012 > to_datetime.in_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')
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 }