Running scheduled jobs in multiple timezones using clockwork


Many applications need to work across timezones for different purposes.
On one of our applications, we needed to support customers at locations across different timezones.

For one of the feature on this application, we needed to create a reports for data from past day.

Since the customers were across different timezones, we needed to run this scheduled task for report creation, for each customer at specific time in its timezone. We needed to let it process data set for the report from time A to time B in that specific timezone.

Implementations

One way to achieve this, would be to create a single scheduled job. That would then fetch all customers, and trigger another delayed background job per customer.

This would be repetitive though, if we had to do this for customers with similar timezones.

Using Clockwork

We use clockwork to manage such background jobs. From its description:

Clockwork is a scheduler which can be used as replacement of cron. It’s a light weight running Ruby process which runs similar to web or worker processes. It allows you to schedule recurring jobs at specific time or date similar to cronjob.

In clockwork one specifies the jobs we want to schedule in a file called clock.rb. We specify the tasks to be scheduled using the every method provided by clockwork and specify the frequency and name of the job:

every(frequency, name_of_job)

every takes blocks as arguments to execute per given frequency:

every(1.day, 'midnight job') do 
  MyScheduledJob.new.process
end

We can also specify the time and timezone using at and tz options.
This helps us to achieve our desired result of scheduling jobs at different timezone specific times.

Following is an example of sample clock.rb that schedules a job of MyScheduledJob that is scheduled to run every day at 12:00 AM mid-night in ET timezone.

...
module Clockwork
  # Schedule a job to run every day at mid-night in ET timezone.
  every(1.day, 'midnight job', at: '00:00', tz: 'America/New_York') do
    # Passed block that would execute the given job to run at given schedule.
    MyScheduledJob.new.process
  end
end

Using these we can now schedule a job per customer’s timezones:

all_uniq_timezones_across_customers = Customer.pluck("DISTINCT timezone")
all_uniq_timezones_across_customers.each do |timezone|
  every(1.day, 'My report job', at: "08:00", tz: timezone) do
    MyReportJob.perform_later(timezone)
  end
end

In the above example, we first collect all the timezones that we want to run the job in. For each of the timezones we schedule a job using the every method provided by clockwork, to run every day, at 08:00 am for that specific timezone.

We passed a block that triggers our report job that will process all customers having timezone timezone.