Rails 3 timezone confusions - ruby-on-rails

I'm confused on how rails 3 timezones are supposed to work.
So I config rails to use Pacific time, and tell active record to store in Pacific time.
# application.rb
config.time_zone = 'Pacific Time (US & Canada)'
config.active_record.default_timezone = 'Pacific Time (US & Canada)'
Now I submit update a model and this comes through in the params:
"start_at"=>"2013-07-24 00:00:00"
From the console now:
>> Sale.last
=> #<Sale id: 24, start_at: "2013-07-24 00:00:00", ...snipped... >
>> Sale.last.start_at
=> Tue, 23 Jul 2013 17:00:00 PDT -07:00
>> Sale.last.start_at.in_time_zone
=> Tue, 23 Jul 2013 17:00:00 PDT -07:00
So after trying to force everything to Pacific time, its creating time objects form the database by factoring in the -7 hours of Pacific time.
If I set a time to 2013-07-24 00:00:00 I would expect Tue, 24 Jul 2013 00:00:00 PDT -07:00 to come back out. And yet it does not. I was having similar confusing issues when active record was using UTC to store dates. I had a few tests that would fail only after 5pm when time to date conversions yielded a different date.
How do I tame this? Storing UTC dates in the database seems like a good idea, and I can use in_time_zone on time objects for display, but does that means that times in forms must be UTC?
Our application functionality is very tied to server time, and certain thing happen every day as specific times. So forcing everything to Pacific time seems like it should be fine. But so far I can't seem to make this behave consistently.
How do I make all this not suck?

This is going to be a 1/2 answer, re-iterating from comment thread above with some additional information
I hope to update with more later. There are many gotchas with this stuff
UPDATE: finally did a blog post on rails timezones
http://jessehouse.com/blog/2013/11/15/working-with-timezones-and-ruby-on-rails/
See also: http://www.elabs.se/blog/36-working-with-time-zones-in-ruby-on-rails
i would use UTC for the application timezone and the activerecord default. Use I18n.localize method (setup formats in config/locales/en.yml) for display of dates and datetimes; set the current threads timezone based on user settings or a default (whatever makes sense for your app)
if the current thread is set to the users timezone activerecord should do the right magic and save the offset UTC value in the db, then when you display that value to the user with I18n it will display it as the user entered it (converting from UTC to the users timezone) - it gets a bit tricky when you are entering a time for say an event that is taking place in another timezone - in that case the thread needs to be set to the timezone of that location and using I18n displays need to be for the location instead of the user
setting current users timezone
see http://railscasts.com/episodes/106-time-zones-revised which uses around filter
example below uses before_filter
both assume a time_zone column on the user account
some code
class ApplicationController < ActionController::Base
protect_from_forgery
# auth the user
before_filter :authenticate_user!
# Set user's time zone
before_filter :set_user_time_zone
# ....
def set_user_time_zone
if current_user and current_user.time_zone.present?
Time.zone = current_user.time_zone
# else some default?
end
end
end
Alternatively - set based on browser settings, etc...
see http://guides.rubyonrails.org/i18n.html
specifically: http://guides.rubyonrails.org/i18n.html#setup-the-rails-application-for-internationalization
display date times
Use I18n.localize aka I18n.l or just l - see http://guides.rubyonrails.org/i18n.html#adding-date-time-formats
changing the timezone display for part of a view
User is set to Pacific but showing an event time for an event taking place in Eastern
Time.use_zone(event.location.time_zone) do
puts event.start_time
end
WARNING: I have found the above does not work correctly if event was pulled using find_by_sql method, regular active record queries work well

Related

Why does Date.yesterday() return the day before yesterday in rails?

I am a little bit confused by the Date.yesterday(), for example:
$ rails c
Loading development environment (Rails 6.0.3.2)
irb(main):001:0> Date.today
=> Fri, 10 Jul 2020
irb(main):002:0> Date.yesterday
=> Wed, 08 Jul 2020
irb(main):003:0> Time.now
=> 2020-07-10 03:54:46.02207138 +0530
irb(main):004:0>
But if I am not wrong, if today is Friday, which is true, the previous day should be Thursday as I learnt in my primary school..
What's going on in here?
tl;dr: Use Date.current and Time.current instead of Date.today and Time.now.
Your application has its own time zone, Time.zone. Ruby is not aware of time zones, but Rails is. Rails partially updates Time and Date to be aware of time zones, but not completely. Any methods Rails adds will be time zone aware. Date.yesterday and Date.tomorrow, for example. Built-in Ruby methods it leaves alone, like Date.today. This causes some confusion.
Date.today is giving today according to your local time zone, +0530. Date.yesterday is giving yesterday according to your application's time zone which I'm guessing is +0000 (UTC). 2020-07-10 03:54:46 +0530 is 2020-07-09 22:24:46 UTC so Date.yesterday is 2020-07-08.
Use Date.current instead of Date.today. Date.yesterday is a thin wrapper around Date.current.yesterday. Similarly, use Time.current instead of Time.now.
The ThoughtBot article It's About Time (Zones) discusses Rails time zones in detail and has simple DOs and DON'Ts to avoid time zone confusion.
DON'T USE
Time.now
Date.today
Date.today.to_time
Time.parse("2015-07-04 17:05:37")
Time.strptime(string, "%Y-%m-%dT%H:%M:%S%z")
DO USE
Time.current
2.hours.ago
Time.zone.today
Date.current
1.day.from_now
Time.zone.parse("2015-07-04 17:05:37")
Time.strptime(string, "%Y-%m-%dT%H:%M:%S%z").in_time_zone
Make sure to check the location of the server where the application is running. The value of Date.yesterday will likely depend on the location of the server with respect to the international date line. For example, while it is still Thursday, 07/09/2020 in New York City, it is Friday, 07/10/2020 in New Zealand.
In your Rails app, you may have a value set for Time.zone or in the configuration for config.time_zone.
If one of these values is configured, then Rails uses this as follows:
def yesterday
::Date.current.yesterday
end
def current
::Time.zone ? ::Time.zone.today : ::Date.today
end
These are ActiveSupport Date helpers
So it's indeed a result of wrong Timezone, in my case Time.zone.name returned "UTC". The correct zone for me is Asia/Kolkata, to check the presence of the timezone:
ActiveSupport::TimeZone::MAPPING.select { |x| x[/^kol/i] }
Which returned {"Kolkata"=>"Asia/Kolkata"} in my case.
For it to take effect temporarily, I set Time.zone = 'Kolkata' in the rails console.
For a permanent effect, added config.time_zone = 'Kolkata' to {project_root}/config/application.rb.
This fixed the problem, and Date.yesterday(), Date.today(), and Date.tomorrow() are working as expected.

Daylight Savings Time ignored using in_time_zone Rails 4

I'm having a frustrating issue that I can't seem to narrow down. I have searched many similar articles but they are not close enough to my issue to resolve. I am trying to pull a time from the database and display it in more than one time zone. My Rails app is using UTC as default. Here is what I'm doing:
On the create action I take the string of time which will be saved in the time column in my DB:
params[:schedule][:start] = "09:00"
Time.zone = "Central Time (US & Canada)"
#schedule.start = Time.zone.parse(params[:schedule][:start])
The above formats the time as it is supposed to:
2016-04-12 09:00:00 -0500
This is saved in the DB as:
2000-01-01 14:00:00
This has no time offset which is fine since I know it's in UTC. The problem happens when I go to display the time:
#schedule.start.in_time_zone("Central Time (US & Canada)")
This returns:
Sat, 01 Jan 2000 08:00:00 CST -06:00
Now, since this is a time column, I don't care about the date. I plan on formatting the value to only show the time. However, it is showing CST when it is currently CDT.
I can't figure out why this is happening. As I said I am not setting the Time Zone anywhere in my application.rb or anywhere else and I only set the Time zone on the create action which should be fine when moving to a new action.
Any help on clarifying this would be awesome!
This seems to be because when the time is stored it is stored with the date in the year 2000-01-01 which seems to be why it is using CST. How can I ignore the date when converting it to a particular timezone or will I need to change the column type to DateTime to get this to work properly?
It is showing CST simply because the time is read from the database including the stored date, i.e. it's read as 09:00 of Jan 1st 2000.
I guess you'd have to parse the time upon reading the attribute back. You can use a helper method in your model, for example:
# schedule model
def start_in_zone(zone)
self.start.strftime("%H:%M").in_time_zone(zone)
end
This will take only the hours and minutes part of the stored time and parse it in the given time zone with the date set to today. See this example:
"Sat, 01 Jan 2000 08:00:00".to_time.
strftime("%H:%M").
in_time_zone("Central Time (US & Canada)")
# => Tue, 12 Apr 2016 08:00:00 CDT -05:00
The fact that it matters whether it's CST or CDT means you do, on some level, care about the date. While I'm not familiar with the exact rules of Daylight Savings in that region, I do know that Jan 1 is the middle of winter and will definitely not be on Daylight Savings time.
Add the relevant date into your #schedule before putting it into a time zone, and it should fix the problem.

Issue comparing DateTime/Time in different zones in Rails

In my application the user sets a specific end time for a task. When a task is loaded, it creates a corresponding CheckIn using that task's information (self in this case is the task):
def create_checkin_end_time(day)
week = (Date.today .. Date.today + 6)
week.each do |day_of_week|
if day_of_week.strftime("%A") == day
return DateTime.new(day_of_week.year, day_of_week.month, day_of_week.day, self.end_time.hour, self.end_time.min)
end
end
end
I've set config.time_zone equal to "Eastern Time (US & Canada") but instead of creating the task as UTC it submits as EST then subtracts 4 hours from that.
[2] pry(main)> CheckIn.last.end_time
CheckIn Load (0.7ms) SELECT "check_ins".* FROM "check_ins" ORDER BY "check_ins"."id" DESC LIMIT 1
=> Sun, 20 Oct 2013 19:30:00 EDT -04:00
[3] pry(main)> Task.last.end_time
Task Load (0.7ms) SELECT "tasks".* FROM "tasks" ORDER BY "tasks"."id" DESC LIMIT 1
=> Sun, 20 Oct 2013 23:30:00 EDT -04:00
The end time for that instance of the model should be 11 PM, based on the task that was used to create it. This is my first go at working with time in Rails so any help would be really appreciated. Thank you.
UPDATE:
Switched it back to UTC and added the method to ApplicationController, but the real problem here was the way I was creating the CheckIn. Because Task.end_time was used as reference in the creation and was called out of the database as EST, the offset was doubled in the creation of the CheckIn. Real weird. My solution was to use .change on creation:
CheckIn.create(task_id: self.id, start_time: self.create_checkin_start_time(day).change(:hour => self.start_time.hour + 4), end_time: self.create_checkin_end_time(day).change(:hour => self.end_time.hour + 4))
This is by NO MEANS a great/permanent solution. I'm in a bit of a time-crunch at the moment and this will suit my needs.
I'd recommend leaving config.time_zone as UTC so you don't see these kind of weird changes happening in the database.
If you want to force a particular timezone on the users of the application, then your best bet is as a before_filter inside ApplicationController:
before_filter :set_timezone
def set_timezone
Time.zone = "Eastern Time (US & Canada)"
end
That way then, all the times will be saved to the database as UTC times, but will be displayed on the site as being in Eastern time.
The reasons for doing this are pretty simple: it's easier to convert from one universal central time (UTC) than it is to convert from some arbitrary point back to UTC and then to some other arbitrary point.

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"

Rails timestamps don't use the right timezone

I'm a bit confused about timezones in rails. I want my rails app to use British Summer Time (like daylight savings in the US) for the timestamps set in updated_at and created_at in my models. I changed my environment.rb to say
config.time_zone = 'London'
The ubuntu server my app is on seems to use BST for it's time: in the command line, for example, if i type 'date' i get the current time (not offset by an hour). In the rails console, i see the following:
>> time = Time.now
=> Wed Oct 27 16:29:17 +0100 2010
>> time.zone
=> "BST"
All fine. However, if i make a new AR model object and save it, the timestamps are from an hour ago. So, it looks like this is using UTC. Now, i can see the logic in this: since the timestamps might be used in the model logic, you want them to be based on an unvarying yardstick time, ie UTC. But, this is a weird bit of behaviour that i don't understand:
#change a record and save it
>> someobj.save
=> true
#object's updated_at is one hour ago
>> someobj.updated_at
=> Wed, 27 Oct 2010 15:34:22 UTC +00:00
>> Time.now
=> Wed Oct 27 16:34:31 +0100 2010
#however, Time.now - object's updated at is just a few seconds.
>> Time.now - someobj.updated_at
=> 15.305549
So, before doing the subtraction, updated_at is converted into the current time zone.
The reason i want to show the date in the current time zone is just for status reports etc in the views: if someone updates something i want them to see that it was updated '1 minute ago' not 'one hour ago'.
Can anyone unconfuse me? cheers, max
EDIT: My immediate problem, of showing the right time in the status, is solved by using the 'time_ago_in_words' helper, which adjusts for time zone. I'd still like someone to explain what's going on with the timestamps though :)
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:
someobj.updated_at.localtime
Note the offset listed at the end of the times -- the first offset is 0, the second is 1. When the time calculation occurs, the offset is included automatically, so that the subtraction gives you the correct result. someobj.updated_at and Time.now each displays its value in a different time zone, so they are really only 9 seconds apart, not 1 hour and 9 seconds.

Resources