Java Quartz-Scheduler across TimeZone
Asked Answered
H

3

12

My server runs on Europe/Rome timezone -and this one is the default tz on the server-, I need to schedule jobs according the user's timezone, so, if a user, living on Pacific/Honolulu timezone, schedules a CronTrigger that fires every day at 22:00pm for his region of the Earth I have found this solution:

CronTrigger trigger = newTrigger()
  .withIdentity("name", "group")
  .withSchedule(
    cronSchedule("0 0 22 ? * *").inTimeZone(TimeZone.getTimeZone("Pacific/Honolulu"))
  )
  .startNow()
  .build();

On my server this job starts at 09:00am of the "my" next day.

There are particular problems to be taken into consideration besides the fact to keep updated the timezone (i.e. Timezone Updater Tool) ?

If I want to define the .startAt() and .endAt() for the previous job, is this kind of date ok? A possible daylight saving time is safe using this procedure?

Calendar calTZStarts = new GregorianCalendar(TimeZone.getTimeZone("Pacific/Honolulu"));
calTZStarts.set(2013, Calendar.JANUARY, 10);

Calendar calTZEnds = new GregorianCalendar(TimeZone.getTimeZone("Pacific/Honolulu"));
calTZEnds.set(2013, Calendar.JANUARY, 30);

Calendar calStarts = Calendar.getInstance();
calStarts.set(Calendar.YEAR, calTZStarts.get(Calendar.YEAR));
calStarts.set(Calendar.MONTH, calTZStarts.get(Calendar.MONTH));
calStarts.set(Calendar.DAY_OF_MONTH, calTZStarts.get(Calendar.DAY_OF_MONTH));
calStarts.set(Calendar.HOUR_OF_DAY, calTZStarts.get(Calendar.HOUR_OF_DAY));
calStarts.set(Calendar.MINUTE, calTZStarts.get(Calendar.MINUTE));
calStarts.set(Calendar.SECOND, calTZStarts.get(Calendar.SECOND));
calStarts.set(Calendar.MILLISECOND, calTZStarts.get(Calendar.MILLISECOND));

Calendar calEnds = Calendar.getInstance();
calEnds.set(Calendar.YEAR, calTZEnds.get(Calendar.YEAR));
calEnds.set(Calendar.MONTH, calTZEnds.get(Calendar.MONTH));
calEnds.set(Calendar.DAY_OF_MONTH, calTZEnds.get(Calendar.DAY_OF_MONTH));
calEnds.set(Calendar.HOUR_OF_DAY, calTZEnds.get(Calendar.HOUR_OF_DAY));
calEnds.set(Calendar.MINUTE, calTZEnds.get(Calendar.MINUTE));
calEnds.set(Calendar.SECOND, calTZEnds.get(Calendar.SECOND));
calEnds.set(Calendar.MILLISECOND, calTZEnds.get(Calendar.MILLISECOND));

CronTrigger trigger = newTrigger()
  .withIdentity("name", "group")
  .withSchedule(
    cronSchedule("0 0 22 ? * *").inTimeZone(TimeZone.getTimeZone("Pacific/Honolulu"))
  )
  .startAt(calStarts.getTime())
  .endAt(calEnds.getTime())
  .build();

or I have to set simply start and end using:

Calendar calTZStarts = new GregorianCalendar();
calTZStarts.set(2013, Calendar.JANUARY, 10, 0, 0, 0);

Calendar calTZEnds = new GregorianCalendar();
calTZEnds.set(2013, Calendar.JANUARY, 30, 0, 0, 0);

CronTrigger trigger = newTrigger()
  .withIdentity("name", "group")
  .withSchedule(
    cronSchedule("0 0 22 ? * *").inTimeZone(TimeZone.getTimeZone("Pacific/Honolulu"))
  )
  .startAt(calTZStarts.getTime())
  .endAt(calTZEnds.getTime())
  .build();

Then the job starts/ends correctly in "Pacific/Honolulu" defined days?

Thanks in advance for every suggestion

Hoarfrost answered 11/1, 2013 at 7:55 Comment(0)
H
13

I think I found the solution, tested and it works until proved otherwise ;)

Recup My server runs on a specific timezone (i.e. Europe/Rome)

If a user on Pacific/Honolulu TZ want to schedule a job that starts on Sun, 27 Jan 2013 at 3:00PM end ends on Thu, 31 Jan 2013 at 9:00PM that fire every day every five minutes starting from 2:00PM to 10:55PM (0 0/5 14-22 * * ?) the correct way is the following:

  • set the user timezone in inTimeZone method on the CronScheduleBuilder
  • adapt to the server time the startAt and endAt dates by converting from Pacific/Honolulu to Europe/Rome

Sample Code:

// Begin User Input
String userDefinedTZ = "Pacific/Honolulu"; // +11

int userStartYear = 2013;
int userStartMonth = Calendar.JANUARY;
int UserStartDayOfMonth = 27;
int userStartHour = 15;
int userStartMinute = 0;
int userStartSecond = 0;

int userEndYear = 2013;
int userEndMonth = Calendar.JANUARY;
int UserEndDayOfMonth = 31;
int userEndHour = 21;
int userEndMinute = 0;
int userEndSecond = 0;
// End User Input


Calendar userStartDefinedTime = Calendar.getInstance();
// set start schedule by user input
userStartDefinedTime.set(userStartYear, userStartMonth, UserStartDayOfMonth, userStartHour, userStartMinute, userStartSecond);

Calendar userEndDefinedTime = Calendar.getInstance();
// set end schedule by user input
userEndDefinedTime.set(userEndYear, userEndMonth, UserEndDayOfMonth, userEndHour, userEndMinute, userEndSecond);


CronTrigger trigger = newTrigger()
  .withIdentity("name", "group")
  .withSchedule(
    // define timezone for the CronScheduleBuilder
    cronSchedule("0 0/5 14-22 * * ?").inTimeZone(TimeZone.getTimeZone("Pacific/Honolulu"))
  )
  // adapt user start date to server timezone
  .startAt( convertDateToServerTimeZone(userStartDefinedTime.getTime(), userDefinedTZ) )
  // adapt user end date to server timezone
  .endAt( convertDateToServerTimeZone(userEndDefinedTime.getTime(), userDefinedTZ) )
  .build();

Utility to convert dates based on tz:

public Calendar convertDateToServerTimeZone(Date dateTime, String timeZone) {
    Calendar userDefinedTime = Calendar.getInstance();
    userDefinedTime.setTime(dateTime);
    if(!TimeZone.getDefault().getID().equalsIgnoreCase(timeZone)) {
    System.out.println        ("original defined time: " + userDefinedTime.getTime().toString() + " on tz:" + timeZone);
    Calendar quartzStartDate = new GregorianCalendar(TimeZone.getTimeZone(timeZone));
    quartzStartDate.set(Calendar.YEAR, userDefinedTime.get(Calendar.YEAR));
    quartzStartDate.set(Calendar.MONTH, userDefinedTime.get(Calendar.MONTH));
    quartzStartDate.set(Calendar.DAY_OF_MONTH, userDefinedTime.get(Calendar.DAY_OF_MONTH));
    quartzStartDate.set(Calendar.HOUR_OF_DAY, userDefinedTime.get(Calendar.HOUR_OF_DAY));
    quartzStartDate.set(Calendar.MINUTE, userDefinedTime.get(Calendar.MINUTE));
    quartzStartDate.set(Calendar.SECOND, userDefinedTime.get(Calendar.SECOND));
    quartzStartDate.set(Calendar.MILLISECOND, userDefinedTime.get(Calendar.MILLISECOND));
    System.out.println("adapted time for " + TimeZone.getDefault().getID() + ": " + quartzStartDate.getTime().toString());
    return quartzStartDate;
    } else {
    return userDefinedTime;
    }
}

== BEGIN OF UPDATE 2012-01-24 ==

Quartz Based Utility to convert dates based on tz using DateBuilder:

public Calendar convertDateToServerTimeZone(Date dateTime, String timeZone) {
    Calendar userDefinedTime = Calendar.getInstance();
    userDefinedTime.setTime(dateTime);
    if(!TimeZone.getDefault().getID().equalsIgnoreCase(timeZone)) {
      System.out.println("original defined time: " + userDefinedTime.getTime().toString() + " on tz:" + timeZone);

      Date translatedTime = DateBuilder.translateTime(userDefinedTime.getTime(), TimeZone.getDefault(), TimeZone.getTimeZone(timeZone));

      Calendar quartzStartDate = new GregorianCalendar();
      quartzStartDate.setTime(translatedTime);
      System.out.println("adapted time for " + TimeZone.getDefault().getID() + ": " + quartzStartDate.getTime().toString());
      return quartzStartDate;
    } else {
      return userDefinedTime;
    }
}

== END OF UPDATE 2012-01-24 ==

So on my Europe/Rome Quartz server this job are scheduled for starting from Mon Jan 28 02:00:00 CET 2013 to Fri Feb 01 08:00:00 CET 2013 and fire every five minute every day from 01:00AM to 08:55PM

When building your Dates for start and end times also specify the timezone (on the java.util.Calendar, or date format string, or org.quartz.DateBuilder) before instantiating the Date. Then quartz stores the date as milliseconds since January 1, 1970 in UTC in that particular timezone - and hence when the server's timezone changes, the trigger is not affected.

Hoarfrost answered 16/1, 2013 at 8:22 Comment(2)
I am using user timezone in cron schedulebuilder. but how do you handle daylight savings in such case. I have question posted here. #47108048Zoophyte
"When building your Dates for start and end times also specify the timezone" if i specify for example startAt() 2 a.m CET (central european time) and after dailight saving it switch to CEST(central european summer time). Will it start 2.am CEST?Creeps
W
1

Date doesn't carry any TZ data, and Daylight Savings Time is actually its own TZ (EST is Easter Standard Time, EDT is Eastern Daylight Savings Time). The only thing that may pe an issue is that some places, like Phoenix Arizona, do not recognize DST. Any time you need TZ data preserved, Calendar is the way to go.

Wish answered 11/1, 2013 at 13:52 Comment(0)
C
0

You can use ZonedDateTime from java8/java8+, In that way you will not need to explicitly convert the given time into server specific time:

protected static Trigger createCronTrigger(String triggerName, ZonedDateTime startTime, String cronExpression, int misFireInstruction, ZoneId timeZone) {
    Trigger trigger = TriggerBuilder.newTrigger()
        .withIdentity(triggerName)
        .startAt(Date.from(startTime.toInstant())).withSchedule(CronScheduleBuilder.cronSchedule(cronExpression).inTimeZone(TimeZone.getTimeZone(timeZone)).withMisfireHandlingInstructionDoNothing())
        .build();
    return trigger;
}

you can read more about ZonedDateTime from Here

Cardamom answered 24/6, 2020 at 12:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.