How to work with time-zone in rails
Time-zone leaks:
This is an issue where time-zone of one request is passed to another request.
Ex Scenario:
Lets say we have web-server running on a single dyno and we have three different users with different timezones('Mumbai', 'Central America' and 'London').
Here is my code setup that will produce time-zone leaks,
class UserController < ApplicationController before_filter :set_user_time_zone, only: [:method_one_with_time_zone]
def method_one_with_time_zone puts Time.zone #before_filter sets the time zone for this method execution. end
def method_two_without_time_zone puts Time.zone #This method doesn't call any before filter, So it will use the Time.zone end
def set_user_time_zone puts Time.zone Time.zone = current_user.time_zone end end
We won't get the time-zone leaks locally, if our testing goes in following way.
Req 1: USER1 ==> method_one_with_time_zone Output: 'UTC' 'Mumbai' Req 2: USER1 ==> method_two_without_time_zone Output: # 'Mumbai'
If the code is setup this way, then developer might not be able to find issues locally as he will see that tasks are creating properly in 'mumbai' time-zone in second request. even though the second request did not set the time-zone.
Multi-User Environment.
Req 1: USER1 ==> method_one_with_time_zone Output: 'UTC' 'Mumbai' USER2 ===> method_two_without_time_zone 'Mumbai' # Expected zone: 'Central America', Here it will create tasks in wrong zone.
As this method doesn't call the before filter it will have the time-zone of USER1, So it will create tasks in wrong time-zone.
How to avoid such issues.
- Never use Time.zone = 'some_time_zone_name' in any rails code, As Time.zone is shared across multiple requests in same thread.
- Use around filter and Time.use_zone to properly setup your controllers, For reference: [Timezone setup for controllers (Timezone setup for controllers)][1]
Most of the time zone issues are not due to improper time zone, It's due to how we use time_zone.
ex:
def test_method Time.use_zone('Mumbai') do #Developer's tend to think that Time.now will give the time in Mumbai zone. #But it is not the case, It will still use the machine time-zone #Proper usage is Time.zone.now Time.parse '27-10-2014 6am' # This is wrong as it will parse time in machine zone #Proper way to do this so that parsing happens in proper zone is as follows Time.zone.parse '27-10-2014 6am' end puts Time.zone # UTC, Reverted to old zone, i.e machine zone end
Things to avoid and it's replacements.
-
Time.now, Avoid this in all the coding including specs, replace it with Time.zone.parse
-
Replace Time.parse with Time.zone.parse
-
Replace Time.at(v) with Time.zone.at(v) OR Time.at(v).in_time_zone
-
Sometimes you need to convert a time in different zone to another zone. In this case we need to use in_time_zone(time_zone_to_be_passed = Time.zone), in third point it will take current time-zone
-
Never depend on you machine time-zone, So avoid usage of .localtime as it will get your time in machine irrespective of any time zone
ex: For Heroku server, default machine zone is UTC,
Time.zone.now.localtime, will give me the time in UTC, even after doing .zone at the beginning the front
[Useful link on time-zone (Useful Link on Timezone)][2]
Thanks
[1]: http://jessehouse.com/blog/2013/11/15/working-with-timezones-and-ruby-on-rails/http://jessehouse.com/blog/2013/11/15/working-with-timezones-and-ruby-on-rails/
[2]: http://danilenko.org/2012/7/6/rails_timezones/