I have an application which uses the following config:
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'm offering the user a form that asks for a separate date (datepicker) and time field (drop down). On the before method I'm concatenating the date & time into a single datetime field. It seems though that the Time object is somehow in UTC, while the Date object doesn't care about that.
When storing this into the database, somehow the timezone gets corrected, but the time is 2 hours off. Is this due to the fact that the time is still in UTC? How can I compensate for that in a way also daylight savings is respected?
Code sample
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'}
The callback in the model
def create_timestamp
self.date = day.to_datetime + time.seconds_since_midnight.seconds
end
After saving, there's a 2 hour difference between de time I've chosen in the form.
This is how it looks like after creating a new record where time is just the same time as the record was created, 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 van see, the time is stored in UTC, while it is actually 14:10 CET +0200 where I created the record. So that could be causing the discrepancy. Which you can see in date, where the time is +2 hours from the created_at.
Thanks
Keep everything in UTC and back out the differences.
When the form submits the fields and you concatenate the DateTime object it will be interpreted as UTC... so you must do the conversions after the information is submitted.
# 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
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 shifting around just because you switched time zones.
If you want to know what the timestamp is in your local time zone, you just have to ask for it that way:
obj.updated_at.localtime
PART 2:
Ruby won’t consider Rails’ time zone configuration. This means that if your system is using the America/Sao_Paulo time zone and your application is using America/Los_Angeles, Ruby will still consider the former configuration.
Time.now.zone
#=> "BRST"
Time.zone = "America/Los_Angeles"
#=> "America/Los_Angeles"
Time.now.zone
#=> "BRST"
It is best to leave the application-wide time zone as UTC and instead allow each individual user to set their own time zone.
Add Time Zone attribute to the user
create_table :users do |t|
t.string :time_zone, default: "UTC"
...
end
Get time zone from the form
<%= f.input :time_zone %>
We can then use around_action to set user's preferred time zone:
# 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 time zone to use_zone method on the Time class (a method which was added by ActiveSupport). This method expects a block to be passed to it and sets the time zone for the duration of that block, so that when the request completes, the original time zone is set back.
DOs:
Get current time: Time.zone.now
Get Day: Time.zone.today
Time from timestamp: Time.zone.at(timestamp)
Parse time: Time.zone.parse(str)
DONT'S:
Time.now
DateTime.now
Date.today
Time.at(timestamp)
Time.parse(str)
Use Time.current
instead of Time.now
.
Use Date.current
instead of Date.today
.
Check out this post for more details on Rails Timezone:
working-with-time-zones-in-ruby-on-rails
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With