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.

  1. Never use Time.zone = 'some_time_zone_name' in any rails code, As Time.zone is shared across multiple requests in same thread.
  2. 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.

  1. Time.now, Avoid this in all the coding including specs, replace it with Time.zone.parse

  2. Replace Time.parse with Time.zone.parse

  3. Replace Time.at(v) with Time.zone.at(v) OR Time.at(v).in_time_zone

  4. 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

  5. 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/