Timezone Alarm in Rails
I have an application that uses the following configuration:
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
config.time_zone = 'Amsterdam'
config.active_record.default_timezone = :utc
end
I offer the user a form that asks for a separate date (datepicker) and a time field (drop down). In the before method, I concatenate the date and time into one datetime field. It seems that the Time object somehow occurs in UTC, while the Date object doesn't care about it.
When storing this in the database, somehow the timezone is adjusted, but the time is 2 hours. Is it because the time is still in UTC? How can I compensate for this in the same way as the summer savings?
Sample code
The form:
= simple_form_for @report do |f|
.row
.col.s6
= f.association :report_category
.col.s6
= f.association :account
.row
.col.s6.l4
= f.input :day, as: :string, input_html: {class: 'datepicker current-date'}
.col.s6.l4
= f.input :time, as: :string, input_html: {class: 'timepicker current-date'}
Callback in model
def create_timestamp
self.date = day.to_datetime + time.seconds_since_midnight.seconds
end
After saving, there is a 2 hours difference between the time I selected in the form.
Here's how it looks after creating a new post, where the time is at the same time as the post, to see the difference:
date: Sat, 20 May 2017 16:10:00 CEST +02:00,
created_at: Sat, 20 May 2017 14:10:33 CEST +02:00,
updated_at: Sat, 20 May 2017 14:10:33 CEST +02:00,
deleted_at: nil,
time: 2000-01-01 14:10:00 UTC,
day: Sat, 20 May 2017,
As you can see, the time is stored in UTC, but in fact it is 14:10 CET +0200, where I created the entry. Thus, it can cause inconsistency. Which you can see in the date where the time is +2 hours from created_at.
thank
source to share
Keep everything in UTC and undo the differences.
When the form submits the fields and you concatenate the DateTime object, it will be interpreted as UTC ... so you have to do the transformations after the information is submitted.
Model.rb
# Set the Proper DateTie
def create_timestamp
# Create the DateTime
date = day.to_datetime + time.seconds_since_midnight.seconds
# Set the timezone in which you want to interpret the time
zone = 'Amsterdam'
# Find the offset from UTC taking into account daylight savings
offset = DateTime.now.in_time_zone(zone).utc_offset / 3600
# Back out the difference to find the adjusted UTC time and save it as UTC with the correct time
self.date = date - offset.hours
end
source to share
PART 1
Timestamps are stored in UTC by default and this is probably the best way to do it. If you move from one server environment to another, you don't want all of your times to switch just because you were switching time zones.
If you want to know what timestamp is in your local timezone, you just need to ask about it like this:
obj.updated_at.localtime
PART 2:
Ruby doesn't respect Rails timezone configuration. This means that if your system uses America / Sao_Paulo time zone, and your application uses America / Los_Angeles, Ruby will still consider the old configuration.
Time.now.zone
#=> "BRST"
Time.zone = "America/Los_Angeles"
#=> "America/Los_Angeles"
Time.now.zone
#=> "BRST"
It is better to leave the application's timezone in UTC and instead allow each individual user to set their own timezone.
Add timezone attribute to user
create_table :users do |t|
t.string :time_zone, default: "UTC"
...
end
Get time zone from form
<%= f.input :time_zone %>
We can then use around_action to set the user's preferred timezone:
# app/controllers/application_controller.rb
around_action :set_time_zone, if: :current_user
private
def set_time_zone(&block)
Time.use_zone(current_user.time_zone, &block)
end
We pass the current user's timezone to the use_zone method in the Time class (a method that was added by ActiveSupport). This method expects a block to be passed to it and sets the timezone for the duration of that block so that when the request is complete, the original timezone will be set back.
DOs:
Get the current time: Time.zone.now
Get the day: Time.zone.today
Time from timestamp: Time.zone.at(timestamp)
Analysis time: Time.zone.parse(str)
DONT'S:
Time.now
DateTime.now
Date.today
Time.at(timestamp)
Time.parse(str)
Use Time.current
instead Time.now
.
Use Date.current
instead Date.today
.
Check out this post for more information on Rails timezone:
source to share
As far as I know, the method to_datetime
uses the system time, so the final date is generated with your system timezone.
self.date = day.to_datetime + time.seconds_since_midnight.seconds
creates Sat, 20 May 2017 14:10:00 +0000
. Since it is already in UTC, it is stored as-is in the database, and later Rails adds 2 hours to display it in the Amsterdam timezone.
You can fix this by avoiding to_datetime
conversion
self.date = day + time.seconds_since_midnight.seconds
Or, if day
is a string, you can do
self.date = Time.zone.parse(day) + time.seconds_since_midnight.seconds
Also I recommend this article about the problems with different time zones.
source to share