later icon indicating copy to clipboard operation
later copied to clipboard

Time zone support

Open Atrejoe opened this issue 9 years ago • 20 comments

Are there plans to support date calculations based on CRON expressions in other time zones than local or UTC?

Atrejoe avatar Apr 17 '15 07:04 Atrejoe

Currently no. Some people have added timezone support using another library like timezone-js. You would need to update date/timezone.js to create timezone aware date instances.

bunkat avatar Apr 17 '15 08:04 bunkat

Thanks for your prompt response!

I'm a bit stuck what route I need to follow, currently I replaced the later internal method stub with the following

later.date.build = function (Y, M, D, h, m, s) {
                    var value = new timezoneJS.Date(Y, M, D, h, m, s, tz);
                    return value;
                };

Is this the route to go? Can you maybe refer me to other solutions or people that implemented this?

Atrejoe avatar Apr 17 '15 11:04 Atrejoe

Yes, this should be the way to go. You can search the issues for other people looking for timezone support.

bunkat avatar Apr 20 '15 07:04 bunkat

I have a 'easy' workaround for missing timezone. I use the momentjs timezone library http://momentjs.com/timezone/.

Example Gist

osteenbergen avatar Apr 23 '15 12:04 osteenbergen

That looks promising and less hackish than my start. I still have to wrap my head around it and test how it works for my issue. (especially the part where the offset of and 'old' date is used for calculating a 'new' date in a possible other time zone)

I hope to find the time soon and give you an update.

Atrejoe avatar Apr 23 '15 12:04 Atrejoe

If I understand your comment correctly you have a user in location X that says execute at 17:00. Then the user goes on a vacation to location Y and it still needs to execute at 17:00. My solution would be to run the calculation again when the user changes timezones. But you can go for a more complicated solution if you know the date/time when the user changes timezones. Similar to how I did daylight saving correction you could use the offset of the new timezone and adjust the output.

osteenbergen avatar Apr 24 '15 07:04 osteenbergen

No It's a somewhat different situation: A have a bunch of schedules that are executed using CRON jobs in different time zones. I need to calculate the next execution of that job and then present the next execution in the clients' time zone. I know the CRON expression and the timezone of these schedules and can deliver them to the client.

I think your previous solution may still work.

Right now I'm thinking of migrating my client-side calculation to server side next execution calculation, in order to just support one method of time zone correction, with one set of time zones and time zone ids for simplicity's sake. The calculation will be needed server side anyway. This question remains valid though.

Atrejoe avatar Apr 24 '15 07:04 Atrejoe

That is the same use-case I have, and the gist is the same as I implemented. Server does the calculation and stores the timezone, previous date, and next date.

osteenbergen avatar Apr 24 '15 08:04 osteenbergen

Okay, I will test it then, thanks for your prompt response.

If this works I still have one hurdle to overcome: mapping my Windows Time Zones to TimeZone.js time zone id's, which may still drive me to a server side solution to prevent time zone maintenance

Atrejoe avatar Apr 24 '15 08:04 Atrejoe

Your solution worked flawlessly!

Due to objections mentioned earlier, I did switch to server-side calculation though.

Atrejoe avatar Apr 24 '15 15:04 Atrejoe

@osteenbergen I believe there is a simpler way:

var moment = require('moment');

var tz = moment(/* our date object here */);
var laterDate = moment.utc(tz).toDate();

// laterDate is our date object to use in later.js!

mmathys avatar May 29 '15 14:05 mmathys

Did some testing and mmathys simpler way is not a valid solution as the resulting list still runs according to UTC. Or i misunderstand your solution. Could you please submit your solution for the example used in my gist:

  • User writes text schedule "at 17:00"
  • User lives (and thinks) in "Europe/Amsterdam"
  • User expects the system to start the run exactly at 17.00 local time even when the local time changes due to daylight saving
var moment = require("moment-timezone");
var later = require("later");

// The schedule we would like to support specified in local timezone:
var sched = later.parse.text("at 17:00");

// In March CET switches to CEST (daylight saving) so this is a nice example
var timezone = "Europe/Amsterdam";
var start_date = new Date("2015-03-24");

// Calculate the list of occurences using the virtual time
var list = later.schedule(sched).next(10, start_date);
/*
Schedule now runs at 17.00 UTC
[ Tue Mar 24 2015 18:00:00 GMT+0100 (CET),
  Wed Mar 25 2015 18:00:00 GMT+0100 (CET),
  Thu Mar 26 2015 18:00:00 GMT+0100 (CET),
  Fri Mar 27 2015 18:00:00 GMT+0100 (CET),
  Sat Mar 28 2015 18:00:00 GMT+0100 (CET),
  Sun Mar 29 2015 19:00:00 GMT+0200 (CEST),
  Mon Mar 30 2015 19:00:00 GMT+0200 (CEST),
  Tue Mar 31 2015 19:00:00 GMT+0200 (CEST),
  Wed Apr 01 2015 19:00:00 GMT+0200 (CEST),
  Thu Apr 02 2015 19:00:00 GMT+0200 (CEST) ]
*/

list.map(function(item){ 
  var tz = moment(item);
  return moment.utc(tz).toDate(); 
});
/* 
Schedule still runs at 17.00 UTC
[ Tue Mar 24 2015 18:00:00 GMT+0100 (CET),
  Wed Mar 25 2015 18:00:00 GMT+0100 (CET),
  Thu Mar 26 2015 18:00:00 GMT+0100 (CET),
  Fri Mar 27 2015 18:00:00 GMT+0100 (CET),
  Sat Mar 28 2015 18:00:00 GMT+0100 (CET),
  Sun Mar 29 2015 19:00:00 GMT+0200 (CEST),
  Mon Mar 30 2015 19:00:00 GMT+0200 (CEST),
  Tue Mar 31 2015 19:00:00 GMT+0200 (CEST),
  Wed Apr 01 2015 19:00:00 GMT+0200 (CEST),
  Thu Apr 02 2015 19:00:00 GMT+0200 (CEST) ]
*/

osteenbergen avatar May 29 '15 15:05 osteenbergen

For everyone who uses later with nodejs and doesn't mind having moment as a dependency you can use this fork for time zone support. the text and cron parsers accept the time zone as last argument and execute the schedule in that zone, e.g. later.parse.cron(pattern,true,'America/Toronto')

mansami avatar Mar 02 '16 08:03 mansami

@bunkat will you accept a PR for timezone support?

balmasi avatar Mar 09 '16 00:03 balmasi

If you added it as a build option so we could build versions with or without timezone support, than sure. Sort of like what I did to build the core library and then the library with parsers. Otherwise it just increases the size of the library too much for those people that don't need it.

bunkat avatar Mar 09 '16 20:03 bunkat

It's pretty annoying that setting something to run at 8am actually runs at 9am for 6 months of the year. If you don't intend to support this perhaps it should be mentioned somewhere obvious in the documentation?

jonfreedman avatar Apr 25 '16 08:04 jonfreedman

This does work if you set Later to use your local timezone. The only scenario that doesn't work is if you want to use an arbitrary timezone. I'd be happy to take a PR that adds full timezone support, but since I don't need it, I'm not planning on adding it myself.

bunkat avatar Apr 25 '16 08:04 bunkat

Yep, adding later.date.localTime() fixed my problem, thanks.

jonfreedman avatar Apr 25 '16 12:04 jonfreedman

Is there a way to make timezones work with setInterval and the text parser?

@osteenbergen 's solution works for getting a finite number, but what if I want this schedule to execute indefinitely?

noahprince22 avatar Dec 01 '17 00:12 noahprince22

Also, just my 2 cents. If your interval directly specifies the TIME block (if looking at the Text Parser diagram) and you set later.js to be in UTC. Then any DateTime that gets output will be at UTC time zone. But the user who has a TZ specified that schedule, so now we need to deduct his TZ offset to get "Actual UTC". I saw that this is not the case if you are omitting the TIME block, then it does provide "Actual UTC", so no need to further process it.

Below is what works for me

var clientTZ = "Africa/Johannesburg";
// var interval = "every 3 min";
var interval = every 1 day at 08:00;
var sched = later.parse.text(interval);
var nextRunDTs = later.schedule(sched).next(1);

if(interval.includes("at") || interval.includes("after")  || interval.includes("before")) 
    momentFirstRundDT = DateTime_TZ_to_UTC(clientTZ, nextRunDT);
else
    momentFirstRundDT = moment(nextRunDT);

gloventRehan avatar Mar 16 '18 02:03 gloventRehan