whenever icon indicating copy to clipboard operation
whenever copied to clipboard

Can whenever take timezone from config?

Open Shwetakale opened this issue 8 years ago • 29 comments

Currently when we write cron jobs using whenever application (rails) timezone is not considered?

For ex. If in config/application.rb I write

config.time_zone = 'Asia/Kolkata'

and in scheduler

every :day, :at => '09:30am' do
  rake "leave_reminder:daily"
end

It is written as it is to cron. My application timezone is different than server and I don't want to change time zone of my server. So when I write cron job I need to change time to server's time zone.

Can we consider server's and applications timezone and convert accordingly so each time people don't have to worry about timezone?

Shwetakale avatar Sep 14 '16 16:09 Shwetakale

Do you fancy submitting a PR for this?

benlangfeld avatar Sep 23 '16 23:09 benlangfeld

@Shwetakale Although, Rails application is independent of whenever, you can access the rails timezone as

require File.expand_path('../config/environment', __dir__)

every :day, :at => Time.zone.parse('09:30am') do
  rake "leave_reminder:daily"
end

Also, the registered cron job would be no different from the one without a specified timezone. eg

require File.expand_path('../config/environment', __dir__)

every :day, :at => Time.zone.parse('09:30am') do
  rake "leave_reminder:daily"
end

every :day, :at => '09:30am' do
  rake "leave_reminder:daily"
end

would result in the entries with the same format.

prasadsurase avatar Dec 01 '16 09:12 prasadsurase

I think this shouldn't be the default behaviour because:

  • It's not a backward compatible change, it would break existing schedule files.
  • It could be confusing for some people. I would expect a tool like Whenever to just act as syntactic sugar on top of the standard cron syntax, so any kind of further automatic transformation would be strange to me.

However, it does seem like a useful feature. Maybe there could be an option to include the Rails environment?

set :use_rails_environment, true

every :day, :at => '09:30am' do
  rake "leave_reminder:daily"
end

This could even appear in newly generated schedule files, commented out by default:

# Uncomment this line to include the Rails environment, e.g. to parse your times in the app's timezone:
# set :use_rails_environment, true

# ...

I could prepare a PR for this.

attilahorvath avatar Mar 06 '17 12:03 attilahorvath

I think passing timezone as a parameter would be more useful, flexible and it is not going to break existing schedule files. It should not be related to rails.

If someone wants to use rails timezone he can pass to this parameter Rails.application.config.time_zone

every :day, :at => '09:30am', by_timezone: 'Eastern Time (US & Canada)' do
  rake "leave_reminder:daily"
end

IlkhamGaysin avatar May 31 '17 09:05 IlkhamGaysin

when i use this "by_timezone:" and deploy using capstrano the server is throwing a error.

Tasks: TOP => whenever:update_crontab (See full trace by running task with --trace) The deploy has failed with an error: Exception while executing as [email protected]: bundle exit status: 1 bundle stdout: bundler: failed to load command: whenever (/var/www/lighthouse/shared/bundle/ruby/2.3.0/bin/whenever) bundle stderr: ArgumentError: Couldn't parse: 38880000

Hari-Orange-Bicycle avatar Sep 13 '17 09:09 Hari-Orange-Bicycle

@Hari-Orange-Bicycle that parameter is not implemented on whenever it was only suggestion of adding this. There is no way to set timezone on whenever tasks.

IlkhamGaysin avatar Sep 13 '17 09:09 IlkhamGaysin

I like @IlkhamGaysin's proposal to set the timezone on a case-by-case basis. However, I still think there should be a way to set a default for the whole schedule file, otherwise you'd need to repeat yourself for each task.

attilahorvath avatar Sep 13 '17 09:09 attilahorvath

I'll have a go at a PR for this later today!

markprovan-itison-zz avatar Sep 13 '17 10:09 markprovan-itison-zz

@attilahorvath Yeah, global would be good too!

IlkhamGaysin avatar Sep 13 '17 13:09 IlkhamGaysin

@IlkhamGaysin Thanks for the response. By the way i tried setting set :job_template, "TZ="Australia/Sydney" bash -l -c ':job'" in the sheduler it worked.

Hari-Orange-Bicycle avatar Sep 13 '17 17:09 Hari-Orange-Bicycle

+1

Just found this PR

This feature would be very handy

ayb avatar Dec 11 '17 09:12 ayb

@markprovan-itison did you link the issue to the PR ?

Startouf avatar Apr 06 '18 10:04 Startouf

@Startouf Sorry, I didn't get round to having a go at this. Thanks for the reminder, I may have a look this weekend.

markprovan-itison-zz avatar Apr 06 '18 10:04 markprovan-itison-zz

+1 would be handy

However, it will require whenever to load the Rails environment when updating the schedule.

So my current workaround for this is:

# load your Rails app
require_relative './environment'

# note I only care about %H:%M format here, so this method is in no way robust.
def timezoned(str)
  Time.zone.parse(str).utc.strftime('%H:%M')
end

# as an example
every :monday, at: timezoned('11am') do
  rake 'email:weekly_report'
end

madejejej avatar Jun 03 '18 22:06 madejejej

I for example have a task that needs to be run at ['7:00 am', '9:00 am', '11:00 am', '1:00 pm', '3:00 pm', '5:00 pm', '7:00 pm']. Unfortunately, that becomes complicated. Because in development, my app is running in a virtual machine that goes off my PC's time (MST), but the live server is in UTC.

csga5000 avatar Jun 25 '18 22:06 csga5000

In what way is it important to run tasks at scheduled times in a local development environment, @csga5000? Also, what is preventing you from setting the VM timezone to UTC?

benlangfeld avatar Jun 25 '18 23:06 benlangfeld

Mostly because it's nice to have it in sync with live, and for testing that the times work right prior to pushing live. The latter is mostly just convenience. I use vagrant which doesn't appear to support timezone configuration without a plug-in.

Regardless, if you think both of those are lousy arguments, I think my code is more readible in the timezone it's based on.

csga5000 avatar Jun 25 '18 23:06 csga5000

But either way, due to daylight savings, I'm pretty sure my code is going to have to look like this:

every 1.hours do
	rake "sometask"
end

sometask

hour = Time.now.in_time_zone(TZInfo::Timezone.get('America/Denver')).hour

if [7, 9, 11, 13, 15, 17, 19].index(hour) == nil
	exit
end

(which requires the tzinfo gem)

csga5000 avatar Jun 25 '18 23:06 csga5000

The only reasonable way to express your jobs in a timezone that you care about for business purposes is to set your servers to that timezone.

You can provision your vagrant box with a shell script to change the timezone on first boot; it’s super simple.

As for running jobs in local development: does your workflow really include waiting for the time the job should run? Most people just fake clock time in development, and only execute real schedules in a staging environment, which should unquestionably be configured the same as your production environment.

benlangfeld avatar Jun 25 '18 23:06 benlangfeld

I'm pretty sure I tried setting the time to test a timezone issue a while ago and if I ran the Linux command to change the time it errored and said something about going off the host time and not being able to change , so I had to change my host time and restart the virtual machine. But this is a pretty big tangent.

The point is I liked the "by_tomezone" option presented earlier. But what I realized when I considered just writing my times in UTC is in most cases you'll probably need to do time checking for things that are timezone specific in your script not the Cron because of DST, or due to supporting various time zones, so if you're that adamant against it then whatever, it's mostly just useful as a convenience in some cases. And if you want something that feature rich, something like laravels scheduler makes more sense, that has cron call it every minute and manages tasks from there allowing it to do things like prevent overlap, and handle timezones.

csga5000 avatar Jun 26 '18 03:06 csga5000

Indeed DST is the major problem. Even if we implemented this, it would be wrong for half the year unless you continually executed Whenever to modify the crontab when the timezone changes.

I’ll repeat that the only reasonable way to write the schedule in a timezone relevant to your business is to set your servers to that timezone.

benlangfeld avatar Jun 26 '18 10:06 benlangfeld

I think that's a short-sited solution much of the time. Your task will probably work for now, but if your software scales you'll have different clients with different timezones. At that point it's much more effort to convert all your dates to UTC, then handle that in software, and lastly make your cron call every hour and do timezone handling in your software. Whereas if you start with the latter, then all you have to do it make it query your users timezone and use that instead of a hardcoded one.

csga5000 avatar Jun 26 '18 13:06 csga5000

This will help anyone looking to trigger a cron based on timezone without changing the server timezone.

require "active_support/all" Time.zone = "Pacific Time (US & Canada)" set :output, "log/cron_log.log" every 1.day, :at => Time.zone.parse('2:15 am').utc do rake "sometask" end

This will trigger a cron based on PST time while server timezone is not changed

SowmiyaSangaiah avatar Sep 06 '18 09:09 SowmiyaSangaiah

@SowmiyaSangaiah 's answer should be the correct answer with the utc part. otherwise it's not useful cus mostly the cloud server are in UTC time unless you set it manually.

ysyyork avatar Dec 02 '18 20:12 ysyyork

Unfortunately if you require 'active_support/all' and declare a Time.zone that observes daylight savings time (like Pacific Time (US & Canada)), your cron execution times will only be correct for part of the year (the half you were in when you generated the crontab). In the example above, that crontab "based on PST time" will have execution times that are 1 hour different during the half of the year when PDT time is observed (from March to November according to Wikipedia).

I think the best solution to this problem is crontab's CRON_TZ variable. "The CRON_TZ variable can be set to an alternate time zone in order to affect when the job is run." (source) However, there's a catch – CRON_TZ is only available on certain Linux distros. These seem to include CentOS, Fedora and Ubuntu.

If your distro supports CRON_TZ, you can add something like this near the top of your Whenever schedule.rb:

env 'CRON_TZ', 'America/Los_Angeles'

This will add the following line to your crontab:

CRON_TZ=America/Los_Angeles

dan-jensen avatar May 05 '19 02:05 dan-jensen

Till the time you can fix this issue by converting your local time to server time. Refer this article https://medium.com/@rishiip/tweaking-into-whenever-gem-timezone-ee24fa7d9f70

pooja-mane avatar Mar 26 '21 15:03 pooja-mane

I searched this up because a customer requested that a sync with an upstream system occur every few hours, but definitely occur "just before 1pm".

We landed on using whenever as a heartbeat and filtering on local time at the task level itself. The customer is in a part of the US that follows daylight savings time so I don't think cron was ever going to get us here with time changes anyway; the hosting servers are UTC.

In schedule.rb we'll run it every hour at 50 minutes past.

every '50 * * * *' do
  rake "ingest:scheduled"
end

At the task level we'll ensure that it's one of our targeted local times.

  desc "Ingest pending orders if local time is 4:50am, 6:50am...12:50pm, 2:50pm...10:50pm"
  task scheduled: [:environment] do
    # client requested this runs just before 1pm local time specifically...and we're going for a 2 hour 
    # update schedule during the day otherise...
    # whenever/cron schedule runs at the top of the hour...we'll target wall clock time here
    # 4:50am 6:50am 8:50am 10:50am 12:50pm 2:50pm 4:50pm 6:50pm 8:50pm 10:50pm
    if [4,6,8,10,12,14,16,18,20,22].include? Time.now.in_time_zone('EST5EDT').hour
      Order.ingest
    end
  end

Admittedly another viable option would have been to simply run the ingest every hour at 50 minutes past the hour, but where's the challenge in that?

darrend avatar May 06 '21 20:05 darrend

I had a need to run multiple jobs in different time zones, some taking daylight savings into account, and others not taking it into account. I adapted a solution from here to fit my needs.

If the timezone does not need to change, simply converting to UTC will work. Otherwise, specifying the correct timezone to use works as well. Below, timezone "Arizona" does not change for daylight savings.

require "active_support/all"

def timezoned (time, timezone)
    Time.zone = timezone || "Central Time (US & Canada)"
    timezone_time = Time.zone.parse(time).utc
    timezone_time
end

# every day at 9:45am, 12:45pm, 3:45pm, 5:45pm Arizona Time
every 1.day, at: [timezoned('9:45 am','Arizona'),timezoned('12:45 pm','Arizona'), timezoned('3:45 pm','Arizona'), timezoned('5:45 pm','Arizona')],  do
rake 'some_rake_task'
end

# every day at 5pm CT
every 1.day, at: timezoned('5 pm','Central Time (US & Canada)'), do
rake 'some_rake_task'
end

loukkevin avatar Mar 15 '22 02:03 loukkevin

I think this feature has no place in Whenever. It should be dealt with in cron either with CRON_TZ or by setting the system time zone. schedule.rb is only evaluated once, when you update crontab. Because of this, regardless of how you translate from one time zone to another in ruby code, once the time string ends up in crontab, it will be forever interpreted using the system time zone which most likely will be UTC and will not pay attention to daylight time saving.

dan-corneanu avatar Mar 30 '23 00:03 dan-corneanu