Java Quartz调度程序跨时区

12

我的服务器使用欧洲/罗马时区 -这也是服务器上的默认时区-,我需要根据用户的时区安排任务。因此,如果生活在夏威夷时间(Pacific/Honolulu)时区的用户为他所在地球的区域安排每天22:00pm触发的CronTrigger,我已找到了以下解决方案:

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

在我的服务器上,这个任务从“我的”下一天的上午09:00开始

除了保持时区更新(即时区更新工具)之外,还需要考虑特定的问题吗?

如果我想为上一个任务定义.startAt()和.endAt(),这种日期格式可以吗?使用这种方法是否安全考虑到可能存在的夏令时?

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();

或者我只需要使用以下方式设置开始和结束:

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();

那么在“夏威夷时间”定义的日期中,工作是否正确开始/结束?

感谢您提前提供的每一条建议。

3个回答

13

我认为我找到了解决方案,测试过并在没有证明其他情况下工作 ;)

回收 我的服务器在特定时区运行(即欧洲/罗马)

如果太平洋/檀香山时区的用户想要安排一个从2013年1月27日星期日下午3:00开始,到2013年1月31日星期四晚上9:00结束,每五分钟触发一次的工作,从下午2:00开始到晚上10:55(0 0/5 14-22 * * ?),则正确的方法如下:

  • 将用户时区设置为CronScheduleBuilder的inTimeZone方法
  • 通过将起始日期和结束日期从Pacific/Honolulu转换为Europe/Rome来适应服务器时间

示例代码:

// 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();

根据时区转换日期的实用工具:

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;
    }
}

== 2012年1月24日更新开始 ==

使用DateBuilder创建的基于Quartz的实用程序,用于根据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);

      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;
    }
}

== 2012年1月24日更新结束 ==

在我的 Europe/Rome Quartz 服务器上,这个任务计划从2013年1月28日02:00:00 CET开始,到2013年2月1日08:00:00 CET结束,并且每天从01:00 AM到08:55 PM之间每五分钟触发一次。

在构建开始和结束时间的日期时,请务必在实例化日期之前指定时区(在 java.util.Calendar、日期格式字符串或 org.quartz.DateBuilder 中)。然后,Quartz 会将该日期以 UTC 自 1970 年 1 月 1 日起的毫秒数存储在该特定时区中,因此当服务器的时区更改时,触发器不会受到影响。


我正在使用用户时区在cron schedulebuilder中。但是在这种情况下,如何处理夏令时?我在这里发布了问题。https://dev59.com/Rabja4cB1Zd3GeqPhH52 - brain storm
在构建开始和结束时间的日期时,还要指定时区。例如,如果我指定了startAt()为中欧时间(CET)凌晨2点,而在夏令时后它切换到中欧夏令时(CEST),那么它会在CEST凌晨2点启动吗? - sebaszczen

1

日期不包含任何时区数据,而夏令时实际上是它自己的时区(EST是东部标准时间,EDT是东部夏令时)。唯一可能会出现问题的是一些地方,比如亚利桑那州的凤凰城,不承认夏令时。每当您需要保留时区数据时,日历是最好的选择。


0
您可以在Java8或更高版本中使用ZonedDateTime,这样您就不需要显式地将给定的时间转换为服务器特定时间:
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;
}

你可以从这里 阅读更多关于ZonedDateTime

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接