Last Updated: April 01, 2016
·
1.53K
· rorykoehler

Ruby On Rails: Using user saved ActiveSupport TimeZone String to fire a job at the users midnight

So you have let your users store their timezone in the database so you can work some magic for them at some time in the future. Your form may look something like this:

<%= form_for @object do |f| %>  
    <div class="col-xs-12 field">
        <%= f.label :time_zone, 'Time Zone:', :class => "col-xs-3" %>
        <%= f.time_zone_select( :time_zone, nil, include_blank: true, :class => "col-xs-8") %>
        </div>
        <div class="col-xs-12 actions">
        <%= f.submit "Save Changes" %>
       </div>
<% end %>

Now you want to run a job at midnight every night when the day turns but you need to do it specifically at the users midnight and not UTC (the default rails timezone). Being on the ball you recognize that everything has to be relative to the first place on earth (according to ActiveSupport anyways) to bring in the new day namely Tokelau Islands. Your job may contain code that looks like this:

def AwesomeJobName(object)
    today = DateTime.now.in_time_zone("Tokelau Is.").to_date
    if DateTime.now.in_time_zone(object.time_zone).to_date == today
        do_amazing_magical_thing_that_will_win_your_users_eternal_love_here
    end
end

So what happened? First we grab the date at UTC +13. This is the reference date. It's in the future to almost everywhere on earth, so you know if today there is still tomorrow where you are, midnight has not yet arrived. When it does arrive your hourly cron job that you setup elsewhere (a topic beyond the scope of this post) will suddenly pass the if statement and the enclosed logic will fire. You'll probably want to set some other conditions on the if statement to ensure the time is midnight, to make sure it only fires once at midnight and not every hour of that day until the if statement doesn't pass again. In my case I was using the job to update a status field in the database from pending to active so it would only fire if the status hadn't been updated already, which could only happen once ever. This meant further time gymnastics were unnecessary however your use case will most certainly be different.

The magic above has to do with the in_time_zone(<insert ActiveSupport::Timezone string here>) method. The time_zone_select form helper at the top auto populates a select box which you use to write an ActiveSupport::Timezone string to the database. You can pop that string into DateTime.now.in_time_zone() and it will spit out the correct current DateTime for your user. Try it out in the console with DateTime.now.in_time_zone("Berlin") for example. It should return a result that looks like Fri, 01 Apr 2016 21:36:26 CEST +02:00. This makes working with and calculating between time zones in Rails really seamless and saves a ton of time. No faffing about with TZInfo or any other such confusing time zone sorcery.