Rails timezone differences between Time and DateTime - ruby-on-rails

I have the timezone set.
config.time_zone = 'Mountain Time (US & Canada)'
Creating a Christmas event from the console...
c = Event.new(:title => "Christmas")
using Time:
c.start = Time.new(2012,12,25)
=> 2012-12-25 00:00:00 -0700 #has correct offset
c.end = Time.new(2012,12,25).end_of_day
=> 2012-12-25 23:59:59 -0700 #same deal
using DateTime:
c.start = DateTime.new(2012,12,25)
=> Tue, 25 Dec 2012 00:00:00 +0000 # no offset
c.end = DateTime.new(2012,12,25).end_of_day
=> Tue, 25 Dec 2012 23:59:59 +0000 # same
I've carelessly been using DateTime thinking that input was assumed to be in the config.time_zone but there's no conversion when this gets saved to the db. It's stored just the same as the return values (formatted for the db).
Using Time is really no big deal but do I need to manually offset anytime I'm using DateTime and want it to be in the correct zone?

Yep. Time.new will intepret parameters as local time in the absence of a specific timezone, and DateTime.new will intepret parameters as UTC in the absence of a specific timezone. As documented. You may want to replace Time.new with Time.local for clarity throughout your code.
What you can do is use DateTime's mixins for Time to invoke Time.local(2012,12,25).to_datetime. But if the year/month/day is coming from a user/browser, then perhaps you should get the user's/browser's timezone instead of using your server's.
If you need to create a migration to fix existing data in the DB, new_date_time = Time.local(old_date_time.year, old_date_time.mon, old_date_time.mday, old_date_time.hour, old_date_time.min, old_date_time.sec).to_datetime

Related

Time in DB compared to current time

I have a couple of stores that I'd like to display if they're open or not.
The issue is that I have my current time.
Time.current
=> Sat, 11 Jun 2016 11:57:41 CEST +02:00
and then if I for example take out the open_at for a store I get:
2000-01-01 00:00:00 +0000
so what I have now is:
def current_business_hour(current_time: Time.current)
business_hours.where('week_day = ? AND open_at <= ? AND close_at >= ?',
current_time.wday, current_time, current_time).first
end
and then I check if a current_business_hour is present. However this is calculating it wrong by what seems like two hours. The open_at and close_at should be within the time zone of Time.current.
In Rails, dates and times are normally saved in UTC in the database and Rails automatically converts the times to/from the local time zone when working with the record.
However, for pure time type columns, Rails doesn't do such automatic conversion if the time is specified as a string only. It must be specified as a Time object instead, which includes the local time zone.
So, for example, if you wanted to store the open_at time as 14:00 local time, you should not set the attribute with a plain string, because it will be saved to the db verbatim, not converted to UTC:
business_hour.open_at = '14:00'
business_hour.save
# => UPDATE `business_hours` SET `open_at` = '2000-01-01 14:00:00.000000', `updated_at` = '2016-06-11 15:32:14' WHERE `business_hours`.`id` = 1
business_hour.open_at
# => 2000-01-01 14:00:00 UTC
When Rails reads such record back, it indeed thinks it's '14:00' UTC, which is off by 2 hours in the CEST zone.
You should convert the time from string to a Time object instead, because it will contain the proper local time zone:
business_hour.open_at = Time.parse('14:00')
business_hour.save
# => UPDATE `business_hours` SET `open_at` = '2000-01-01 12:00:00.000000', `updated_at` = '2016-06-11 15:32:29' WHERE `business_hours`.`id` = 1
business_hour.open_at
# => 2016-06-11 14:00:00 +0200
Note that the column is now stored in UTC time. Now, you can safely compare the time columns with any other rails datetime objects, such as Time.current.

Rails4: Saving and displaying date in user's timezone

I am working on a rail4 app. Where I want to store dates in all mysql tables in UTC. However I store user's timezone in a specific table, called users. When user logs in, I get user's timezone form user table and save in session.
I am able to save date in all tables in UTC as default value of config.time_zone is UTC for activerecords and activemodels. But while displaying I want to show dates in user's timezone. As well as, when any user inputs a date/time in any html form, then I want to save it in the equivalent UTC format.
What is the best way to achieve this?
Rails, activerecord and MySQL will save all the timestamp fields in UTC. Without you having to do anything.
In your application.rb file where the configuration of the Application is done, you define the default time zone if you want the display of timestamps to take place on time zone different from UTC.
Hence
config.time_zone = 'Central Time (US & Canada)'
will display the timestamp fields (without you having to do anything special in other piece of code) using the Central Time.
When you want each of your users to have timestamps displayed in different time zone you can store the time zone in a column along side the user data. The column can be called time_zone and can contain the string of the user preferred time zone.
But, you have to tell the timestamp object to display itself to the specific timezone. This is done with the help of the method in_time_zone(timezone) that DateTime object responds to.
Example (when the default time zone is UTC):
1.9.3-p194 :004 > d = DateTime.new(2012, 9, 1, 6, 30, 0)
=> Sat, 01 Sep 2012 06:30:00 +0000
1.9.3-p194 :005 > d.in_time_zone("Central Time (US & Canada)")
=> Sat, 01 Sep 2012 01:30:00 CDT -05:00
Or you can change the time zone globally for the request at hand on a before or around filter. There is a documentation on internet if you do a google on that.
Read also this one: http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html
for various alternatives to approach the problem.
You could store the time in UTC, and store the timezone separately. Timezones are commonly stored as a UTC-offset in seconds (seconds are the SI unit of time).
Then you can display it like so:
utime = Time.now.utc.to_i # this value can be any format that Time.at can understand. In this example I'll use a unix timestamp in UTC. Chances are any time format you store in your DB will work.
=> 1375944780
time = Time.at(utime) # parses the time value (by default, the local timezone is set, e.g. UTC+08:00)
=> 2013-08-08 14:53:00 +0800
time_in_brisbane = time.in_time_zone(ActiveSupport::TimeZone[36000]) # sets the timezone, in this case to UTC+10:00 (see http://stackoverflow.com/a/942865/72176)
=> Thu, 08 Aug 2013 16:53:00 EST +10:00
time_brisbane.strftime("%d %b %Y, %l:%M %p %z") # format with strftime however you like!
=> "08 Aug 2013, 4:53 PM +1000"

Changing ActiveRecord timezone for a session

I've been trying to change the timezone for a user session using the Time.zone method. I get the correct date and time when I do Time.zone.now but when I do a select query using ActiveRecord::Base.connection.select_all, the datetime columns in the result are always in UTC. What do I need to do so that all the select queries return data in the timezone that was set using the Time.zone method, without changing the views? Please help.
It's easiest to convert the Time to the local timezone with ruby.
Time.now.in_time_zone()
use it as such.
Time.now.in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
You can give the function a timezone string, or an UTC offset
http://apidock.com/rails/Time/in_time_zone

Convert local time to UTC in Rails

In my MySQL database the updated_at field is being stored as UTC. Last week a record was entered at 7pm EDT and it's updated_at value is "2012-08-01 23:00:00". I'm trying to convert local time for a web client to UTC that can be compared against the updated_at field in the database.
As an example, I'd like to convert '08/01/2012 07:00 pm' to '2012-08-01 23:00:00' (accounting for me being in EDT) but I'm missing the time zone aspect to the conversion. '7:00 pm' is local time and could be from any time zone. My current code:
ruby-1.9.2-head :015 > from_date = DateTime.strptime('08/01/2012 07:00 pm', '%m/%d/%Y %H:%M %p')
=> Wed, 01 Aug 2012 19:00:00 +0000
ruby-1.9.2-head :016 > from_date = Time.zone.parse(from_date.to_s).utc
=> 2012-08-01 19:00:00 UTC
Any thoughts?
Edit: Also, I could use "%Z" in my strptime call but that's assuming I have the timezone in the value, which I currently do not. I guess I could use Javascript to send that along with the date/time value.
local to utc
created_at.utc
utc to local
created_at.localtime
First I would check your time zone setting.
In your environment.rb (Rails 2) or application.rb (Rails 3) file, you can set the default timezone with: config.time_zone = 'Eastern Daylight Time (EDT)'
Secondly I would look at this post for information and guidance and tips to meet your needs:
http://databasically.com/2010/10/22/what-time-is-it-or-handling-timezones-in-rails/

ActiveRecord DateTime field not matching object

i've got a Course model, which has a datetime attribute. If I look at it from the database i get one time, and if i look at it from the object i get a different date & time.
>> Course.last.attribute_for_inspect :datetime
=> "\"2012-01-04 01:00:00\""
>> Course.last.datetime
=> Tue, 03 Jan 2012 19:00:00 CST -06:00
Does anyone know why this value is different, and what I can do to fix it? The time from Course.last.datetime is correct, but my queries on the course table aren't working correctly due to the mix-up.
From the fine manual:
attribute_for_inspect(attr_name)
Returns an #inspect-like string for the value of the attribute attr_name.
[...]
Date and Time attributes are returned in the :db format.
So, when attribute_for_inspect is used for a datetime, it will return the string that the database uses for that value. Rails stores times in the database in UTC and any sensible database will use ISO 8601 for formatting timestamps on the way in and out.
2012-01-04 01:00:00 is the UTC timestamp in ISO 8601 format. When Rails returns a datetime, it converts it to an ActiveSupport::TimeWithZone instance and includes a timezone adjustment based on the timezone that you have configured in your application.rb. You're using the CST timezone and that's six hours behind UTC; subtracting six hours from 01:00 gives you 19:00 and you lose a day from crossing midnight. The human friendly format, Tue, 03 Jan 2012 19:00:00 CST -06:00, is just how ActiveSupport::TimeWithZone represents itself when inspected and the console uses x.inspect to display x.
As far as fixing your queries goes, just use t.utc before sending in a time t and you should be good.
Configuring your Rails App Timezone in application.rb
set config.active_record.default_timezone to :local as it is set to :utc by default in the application.rb
paste this code in your application.rb
config.active_record.default_timezone = :local #or :utc
config.time_zone = "Singapore" #your timezone

Resources