Preserving timezones in Rails - ruby-on-rails

If I have a time string of the form "Wed, 22 Jun 2011 09:43:58 +0200" coming from a client, I wish to store it with the time zone preserved. This is important because it's not just the absolute UTC time that is important but also the timezone.
Time.zone.parse(t) will convert the time to whatever the zone that Time.zone is using at the time, losing the source timezone.
Do I have to manually extract the timezone from the above string or is there an idiomatic way to do this?

A DateTime field can only store 'YYYY-MM-DD HH:MM:SS' (MySQL), no Time Zone info.
You should store the datetime in UTC, and the Timezone in a different field, preferably as an integer specifying the offset from UTC in minutes.
You can extract the offset like this:
ruby-1.9.2-p180:001:0>> require 'active_support/all' # included by Rails by default
# => true
ruby-1.9.2-p180:002:0>> dt = DateTime.parse "Wed, 22 Jun 2011 09:43:58 +0200"
# => Wed, 22 Jun 2011 09:43:58 +0200
ruby-1.9.2-p180:003:0>> dt.utc_offset
# => 7200
ruby-1.9.2-p180:004:0>> dt.utc
# => Wed, 22 Jun 2011 07:43:58 +0000
EDIT:
And to round trip the excercise
ruby-1.9.2-p180 :039 > u.utc.new_offset(u.offset)
=> Wed, 22 Jun 2011 09:43:58 +0500
ruby-1.9.2-p180 :040 > u
=> Wed, 22 Jun 2011 09:43:58 +0500

I think you are looking for the following solution:
In ApplicationController:
before_filter :get_tz
def get_tz
#tz = current_user.time_zone
end
def use_tz
Time.use_zone #tz do
yield
end
end
And in a controller add around filter at the beginnig
around_filter :use_tz

Related

How can I change the offset of a timestamp and then store the change in postgresql?

I have a form on my rails app that allows you to create a campaign object where the user can set a start_date, end_date, and timezone for which the dates will officially start and end.
What I would like to do is to apply the offset of the selected timezone to both the start_date and end_date. I've tried a few combinations but I can't seem to make it work. For starters, when I select my start and end dates for 11:00 PM and select Central Time Zone CDT with offset of -0500, the end result in postgresql is the following timestamp.
# No offset is applied
Wed, 08 Jun 2022 23:00:00.000000000 UTC +00:00
In order to try to apply the offset, I've tried a few combinations in my rails code. Here are some examples in a before_validation callback. It's pseudocode but this is the gist of it.
Example #1
date_tmp = send("start_at") # Wed, 08 Jun 2022 23:00:00.000000000 UTC +00:00
date_string = date_tmp.to_datetime.change(offset: '-0500').to_s
assign_attributes("start_at" => date_string) # "2022-06-08T22:00:00-05:00"
The result is no change for example 1.
Example #2
date_tmp = send("start_at") # Wed, 08 Jun 2022 23:00:00.000000000 UTC +00:00
date_string = date_tmp.to_datetime.change(offset: '-0500').to_s
dt = Chronic.parse(date_string.to_time.to_s)
assign_attributes("start_at" => dt) # "2022-06-08 22:00:00 -0500"
None of these variations work. I've also tried to save to postgresql timestamps that look like this:
2022-06-08T22:00:00-05:00
2022-06-08 22:00:00 -0500
Wed, 08 Jun 2022 22:00:00 -0500
I don't understand why rails and postgresql can't save the timestamp with the adjusted utc_offset that I want. What can I be doing wrong?
Try new_offset method on your DateTime instance.
It duplicates datetime object and resets its offset.
Some examples:
date = DateTime.now
date # => Thu, 16 Jun 2022 20:14:58 +0000
date.new_offset('+03:00') # => Thu, 16 Jun 2022 23:14:58 +0300
date.new_offset('-03:00') # => Thu, 16 Jun 2022 17:14:58 -0300

strftime(%Z) returns wrong result

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>]

1.year.from_now is also one day ahead

Today's date is 2016-09-19. I add one year to it, and I expect the result to be 2017-09-19. Instead, I get 2017-09-20. One year plus one day ahead. Is this behavior as intended?
$ rails c
2.3.1 :001 > Time.now.to_date.iso8601
=> "2016-09-19"
2.3.1 :002 > 1.year.from_now.to_date.iso8601
=> "2017-09-20"
If you want get ecactly date with time zones, you can use Time.current
1.year.from_now
#=> Wed, 20 Sep 2017 05:38:50 UTC +00:00
Time.current
#=> Tue, 20 Sep 2016 05:39:08 UTC +00:00
since or its alias from_now calculate the offset based on Time.current which is equivalent to Time.zone.now – both return a ActiveSupport::TimeWithZone instance:
Time.current #=> Mon, 19 Sep 2016 19:56:34 SST -11:00
Time.zone.now #=> Mon, 19 Sep 2016 19:56:35 SST -11:00
Time.now on the other hand returns an ordinary Time instance in your system's timezone, which can differ from the Rails timezone:
Time.now #=> 2016-09-20 08:56:36 +0200
To avoid confusion, you should always use Time.current / Time.zone.now when working within Rails. You can however pass another "base time" to since:
1.year.since(Time.now) #=> 2017-09-20 08:56:37 +0200
Or, because you are working with dates:
1.year.since(Date.today) #=> Wed, 20 Sep 2017
There is also Date.current which is equivalent to Time.zone.today:
1.year.since(Date.current) #=> Wed, 19 Sep 2017
Turns out it's a timezone thing. I'm UTC -7 hours.
Time.now returns the time in my timezone.
1.year.from_now returns the time in UTC+0, 7 hours ahead of where I am.
It's 10pm here, so it's the next day at UTC+0.

How to save a date into rails using the console

I have a table that has a date field in it. How would I save the date using the console? I tried
event = Event.create(name:"Concert", date:08/20/2016, location:'Portland', state:'OR')
However, I am getting an Invalid octal digit error.
You'll want to pass in an actual Date object, which you can get from a string with the Date.parse method:
event = Event.create(name: "Concert", date: Date.parse('2016-08-20'), location: 'Portland', state: 'OR')
Note that I've rewritten your date to be in a different format. The MM/DD/YYYY format is not portable across locales, so I'd strongly suggest you use YYYY-MM-DD (the ISO 8601 format).
Using a string in the correct format will do the trick. For example:
>> foo = Foo.create date: "20/8/2016"
(0.0ms) begin transaction
SQL (1.0ms) INSERT INTO "foos" ("date") VALUES (?) [["date", Sat, 20 Aug 2016]]
(0.9ms) commit transaction
#<Foo id: 1, date: "2016-08-20">
>> foo.date
Sat, 20 Aug 2016
ActiveSupport provides core extensions on the String class for conversions from strings to date, time, and datetime. This is probably ok, and more convenient, to take advantage of while testing around in Rails console.
Taking advantage of this extension in the application itself (instead of explicitly parsing with Date.parse) is totally up to you and your team.
From the source:
"1-1-2012".to_date # => Sun, 01 Jan 2012
"01/01/2012".to_date # => Sun, 01 Jan 2012
"2012-12-13".to_date # => Thu, 13 Dec 2012
"12/13/2012".to_date # => ArgumentError: invalid date
Just to be thorough, examples for String#to_time
"13-12-2012".to_time # => 2012-12-13 00:00:00 +0100
"06:12".to_time # => 2012-12-13 06:12:00 +0100
"2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 +0100
"2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100
"2012-12-13T06:12".to_time(:utc) # => 2012-12-13 06:12:00 UTC
"12/13/2012".to_time # => ArgumentError: argument out of range
And String#to_datetime:
"1-1-2012".to_datetime # => Sun, 01 Jan 2012 00:00:00 +0000
"01/01/2012 23:59:59".to_datetime # => Sun, 01 Jan 2012 23:59:59 +0000
"2012-12-13 12:50".to_datetime # => Thu, 13 Dec 2012 12:50:00 +0000
"12/13/2012".to_datetime # => ArgumentError: invalid date
Try this, for example:
date = Date.parse('3rd Feb 2001') ####=> #<Date: 2001-02-03 ...>
Reference: Date class

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

Resources