UTC time resets to 2000-01-01 (ruby). How do I prevent the time from resetting?
You are using the Time
class. This class deals with times, not dates. My guess is that your database column is of type time
as well.
I recommend you use a datetime
(or possibly timestamp
) column type.
How to keep Rails from messing up the date of a time column when converting to UTC?
Thanks for the comments, everyone. Super helpful. I decided to go with DateTime
.
What I learned: If there is a need to compare the difference between two times, or determine whether something falls between two times, Time
will not work, and DateTime
should be used.
It feels like a waste if you don't need the date part, but the truth is that the date part is needed in order to keep the time zone conversion from destroying the relationship between two times.
Saving datetime in UTC isn't accurate sometimes
I propose that you still use the first option but with a little hack: in essence, you can switch off the time zone conversion for the desired attribute and use a custom setter to overcome the conversion during attribute writes.
The trick saves the time as a fake UTC time. Although technically it has an UTC zone (as all the times are saved in db in UTC) but by definition it shall be interpreted as local time, regardless of the current time zone.
class Model < ActiveRecord::Base
self.skip_time_zone_conversion_for_attributes = [:start_time]
def start_time=(time)
write_attribute(:start_time, time ? time + time.utc_offset : nil)
end
end
Let's test this in rails console:
$ rails c
>> future_time = Time.local(2020,03,30,11,55,00)
=> 2020-03-30 11:55:00 +0200
>> Model.create(start_time: future_time)
D, [2016-03-15T00:01:09.112887 #28379] DEBUG -- : (0.1ms) BEGIN
D, [2016-03-15T00:01:09.114785 #28379] DEBUG -- : SQL (1.4ms) INSERT INTO `models` (`start_time`) VALUES ('2020-03-30 11:55:00')
D, [2016-03-15T00:01:09.117749 #28379] DEBUG -- : (2.7ms) COMMIT
=> #<Model id: 6, start_time: "2020-03-30 13:55:00">
Note that Rails saved the time as a 11:55, in a "fake" UTC zone.
Also note that the time in the object returned from create
is wrong because the zone is converted from the "UTC" in this case. You would have to count with that and reload the object every time after setting the start_time
attribute, so that the zone conversion skipping can take place:
>> m = Model.create(start_time: future_time).reload
D, [2016-03-15T00:08:54.129926 #28589] DEBUG -- : (0.2ms) BEGIN
D, [2016-03-15T00:08:54.131189 #28589] DEBUG -- : SQL (0.7ms) INSERT INTO `models` (`start_time`) VALUES ('2020-03-30 11:55:00')
D, [2016-03-15T00:08:54.134002 #28589] DEBUG -- : (2.5ms) COMMIT
D, [2016-03-15T00:08:54.141720 #28589] DEBUG -- : Model Load (0.3ms) SELECT `models`.* FROM `models` WHERE `models`.`id` = 10 LIMIT 1
=> #<Model id: 10, start_time: "2020-03-30 11:55:00">
>> m.start_time
=> 2020-03-30 11:55:00 UTC
After loading the object, the start_time
attribute is correct and can be manually interpreted as local time regardless of the actual time zone.
I really don't get it why Rails behaves the way it does regarding the skip_time_zone_conversion_for_attributes
configuration option...
Update: adding a reader
We can also add a reader so that we automatically interpret the saved "fake" UTC time in local time, without shifting the time due to timezone change:
class Model < ActiveRecord::Base
# interprets time stored in UTC as local time without shifting time
# due to time zone change
def start_time
t = read_attribute(:start_time)
t ? Time.local(t.year, t.month, t.day, t.hour, t.min, t.sec) : nil
end
end
Test in rails console:
>> m = Model.create(start_time: future_time).reload
D, [2016-03-15T08:10:54.889871 #28589] DEBUG -- : (0.1ms) BEGIN
D, [2016-03-15T08:10:54.890848 #28589] DEBUG -- : SQL (0.4ms) INSERT INTO `models` (`start_time`) VALUES ('2020-03-30 11:55:00')
D, [2016-03-15T08:10:54.894413 #28589] DEBUG -- : (3.1ms) COMMIT
D, [2016-03-15T08:10:54.895531 #28589] DEBUG -- : Model Load (0.3ms) SELECT `models`.* FROM `models` WHERE `models`.`id` = 12 LIMIT 1
=> #<Model id: 12, start_time: "2020-03-30 11:55:00">
>> m.start_time
=> 2020-03-30 11:55:00 +0200
I.e. the start_time
is correctly interpreted in local time, even though it was stored as the same hour and minute, but in UTC.
Time zone selections/defaults - how to keep hidden in Ruby on Rails
Have you tried Time.now.in_time_zone
instead of just Time.now
, see this related question
Time is defaulting to 2000-01-01 21:14:00 UTC in rails 4 application
You've probably created those columns as :time
columns in your migrations. If you want to save time of day and date, then you need a datetime column.
Confusingly, even though the ruby Time
is used to represent a point in time (and from that point of view is very similar to the DateTime
class (but very different in terms of implementation)) in sql a TIME
column is different and means a disembodied time of day, with no date attached (for example the concept of 4pm).
Ruby doesn't have a built in class for time of day, so rails chose to represent those with a Time instance. Since the date portion is ignored it is set to an arbitrary date, which happens to be January 1, 2000
Rails and dates, are they stored in UTC by default?
Fortunately Rails will pretty much handle things for you. As others pointed out, dates are stored by AR in UTC format. If you have a time_zone
field for your users table you can do something like this:
# application.rb
config.time_zone = "Mountain Time (US & Canada)" # Default time zone
-
# application_controller.rb
before_filter :set_time_zone, :if => :logged_in?
protected
def set_time_zone
Time.zone = current_user.time_zone if current_user.time_zone
end
All the datetimes should be shown in the proper time zone in your views.
I have had one production app that didn't like using the default time zone, but I can't remember which version of Rails/Ruby it was running.
Why does Rails store times in UTC?
This question has a little bit of a religious feel to it, but I'm going to answer it based on my personal experience.
Always store dates in an unambiguous form. Storing the date in UTC is pretty much the standard in that regard.
Advantages:
- You can do simple math on date-times in the database without needing to convert them.
- You can adjust the display of the dates at the presentation layer
- Web applications can use a little bit of javascript to display local time
Disadvantages:
- You need to convert all the times into some 'local' time on display
- Localtime <-> UTC conversions incur a small processing penalty
Can you get rails to do something different? Possibly, but I've never tried as it just was too much work to fight what IMHO was a sensible design.
Does it make sense to use UTC from a 'just use my timezone' sense? Yes. Your server could be in California, your users in New York and who decides what is local time in that case. The server? The users? Mike, who just happens to be in London for the week on a business trip? In this case what timezone do you use?
Related Topics
Ruby Net-Ssh Calling Bash Script with Interactive Prompts
How to Prevent My Users to Read My Ruby Code
How to Set an Expectancy on Should_Receive and Have It Execute the Original Code
Ruby: How to Dynamically Replace Parameters in Native Pg Gem
Rails: Upload a File or Store a Url
Could Not Find 'Cocoapods' (>= 0.A) Among 48 Total Gem(S) (Gem::Missingspecerror)
How to Schedule a Function to Execute at a Future Time
Fetching Second Row from CSV File in Ruby
Copy into Postgres with Arrays in Schema
Calling Protected Class Method from Instance Method in Ruby
Rails How to Use Associated Model with the Admin Namespace
Rails 4 - Pundit - Create Policy
I Want to Match All Punctuation in My Regexp Except Apostrophes. How to Do That in Ruby
Rails App Has Trouble with Inter-Model Saving
Refactoring Pokemon Type Weekness
Unexpected =>, Expecting '}' in Rspec Expect