Quartz Calendar 日历的使用
quartz引擎为我们提供了日历的功能,让我们可以自己定义一个时间段,可以控制触发器在这个时间段内触发或者不触发,比如可以设置节假日,工作时间早8晚5等等。
下面这张表是quartz为我们提供的所有日历组件,我们可以根据具体需求选择使用,并且还支持扩展。
AnnualCalendar | This implementation of the Calendar excludes a set of days of the year. |
BaseCalendar | This implementation of the Calendar may be used (you don't have to) as a base class for more sophisticated one's. |
CronCalendar | This implementation of the Calendar excludes the set of times expressed by a given CronExpression . |
DailyCalendar | This implementation of the Calendar excludes (or includes - see below) a specified time range each day. |
HolidayCalendar | This implementation of the Calendar stores a list of holidays (full days that are excluded from scheduling). |
MonthlyCalendar | This implementation of the Calendar excludes a set of days of the month. |
WeeklyCalendar | This implementation of the Calendar excludes a set of days of the week. |
一、AnnualCalendar
This implementation of the Calendar excludes a set of days of the year. You may use it to exclude bank holidays which are on the same date every year
年度日历,这个日历可以设置一组日期的集合,当与trigger关联后,trigger在设定的这些日期之内都不会触发。
// Add the holiday calendar to the schedule
AnnualCalendar holidays = new AnnualCalendar();
//这里设置日期 年月日
//注意这里的一个坑(研究了大变天才发现坑在这里),就是月份要比实际月份少一个月
//2014,7,21 实际是2014年8月21日
Calendar calendar = new GregorianCalendar(2014, 7, 21);
holidays.setDayExcluded(calendar, true);
// tell the schedule about our holiday calendar
sched.addCalendar("holidays", holidays, false, false);
触发器每隔一小时触发一次,并与"holidays"关联,当日期到达上面设置的日期的时候,触发器不触发。
SimpleTrigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInHours(1)
.repeatForever())
.modifiedByCalendar("holidays")
.build();
看完上面例子的同学是不是认为 日历设置成2014-07-21 只有在2014年才生效,这里也是一个坑(官方汶上上有说明(上面红色)),一定要注意这里设置的年是没有意义的。它会在每一年的这个日期都生效
下面是它计算时间的源码,看到没,并没有计算设置的年份。
public boolean isDayExcluded(java.util.Calendar day) {
if (day == null) {
throw new IllegalArgumentException(
"Parameter day must not be null");
}
// Check baseCalendar first
if (! super.isTimeIncluded(day.getTime().getTime())) {
return true;
}
int dmonth = day.get(java.util.Calendar.MONTH);
int dday = day.get(java.util.Calendar.DAY_OF_MONTH);
if (dataSorted == false) {
Collections.sort(excludeDays, new CalendarComparator());
dataSorted = true;
}
Iterator<java.util.Calendar> iter = excludeDays.iterator();
while (iter.hasNext()) {
java.util.Calendar cl = (java.util.Calendar) iter.next();
// remember, the list is sorted
if (dmonth < cl.get(java.util.Calendar.MONTH)) {
return false;
}
if (dday != cl.get(java.util.Calendar.DAY_OF_MONTH)) {
continue;
}
if (dmonth != cl.get(java.util.Calendar.MONTH)) {
continue;
}
return true;
}
return false;
}
二、BaseCalendar
This implementation of the Calendar may be used (you don't have to) as a base class for more sophisticated one's.
它是一个基类,为上面表格中的各种日历提供了一些基础的方法,如果我们需要自定义日期可以继承它,正常情况我们使用quartz为我们提供好的日历就足够了。
三、CronCalendar
This implementation of the Calendar excludes the set of times expressed by a given CronExpression. For example, you could use this calendar to exclude all but business hours (8AM - 5PM) every day using the expression "* * 0-7,18-23 ? * *".
Cron表达式日历,可以写一个表达式来排除一个时间范围,比如可以设置为排除所有的时间,但是工作时间除外,也就是 在早8点-晚5点触发,其他时间暂停。
CronCalendar calendar = new CronCalendar("* * 0-7,18-23 ? * *");
sched.addCalendar("business", calendar, false, false);
四、DailyCalendar
This implementation of the Calendar excludes (or includes - see below) a specified time range each day. For example, you could use this calendar to exclude business hours (8AM - 5PM) every day. Each DailyCalendar only allows a single time range to be specified, and that time range may not cross daily boundaries (i.e. you cannot specify a time range from 8PM - 5AM). If the property invertTimeRange is false (default), the time range defines a range of times in which triggers are not allowed to fire. If invertTimeRange is true, the time range is inverted – that is, all times outside the defined time range are excluded.
Note when using DailyCalendar, it behaves on the same principals as, for example, WeeklyCalendar. WeeklyCalendar defines a set of days that are excluded every week. Likewise, DailyCalendar defines a set of times that are excluded every day.
时间范围日历,定义一个时间范围,可以让触发器在这个时间范围内触发,或者在这个时间范围内不触发,每一个DailyCalendar的实例只能设置一次时间范围,并且这个时间范围不能超过一天的边界,比如你不能定义一个时间范围是(晚上8点至第二天早上5点),如果invertTimeRange这个属性等于false(默认),那么定义的时间范围内触发器不会触发,相反如果invertTimeRange=true 那么只有在这个时间范围内触发器才会触发,这个时间范围以外的时间都被排除。
定义一个时间范围 s=当前时间 e=当前时间+10秒钟。
然后创建一个触发器立即触发,时间间隔3秒,无限重复。
invertTimeRange=true 这个触发器会在s-e这个时间范围内触发,invertTimeRange=false 这个触发器会在s-e这个时间范围之外触发。
DailyCalendar的构造参数也支持String new DailyCalendar("20:57:00", "20:59:00"),还有其他很多方式可以参见官方文档。
Calendar s = Calendar.getInstance();
s.setTime(new Date());
Calendar e = Calendar.getInstance();
e.setTime(futureDate(10, IntervalUnit.SECOND));
DailyCalendar dailyCalendar = new DailyCalendar(s, e);
//DailyCalendar dailyCalendar = new DailyCalendar("20:57:00", "20:59:00");
dailyCalendar.setInvertTimeRange(true);
sched.addCalendar("dailyCalendar", dailyCalendar, false, false);
SimpleTrigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.modifiedByCalendar("dailyCalendar")
.build();
五、HolidayCalendar
This implementation of the Calendar stores a list of holidays (full days that are excluded from scheduling).
The implementation DOES take the year into consideration, so if you want to exclude July 4th for the next 10 years, you need to add 10 entries to the exclude list.
该日历与AnnualCalendar一致,区别就是设置的year是有效的,也就是说如果你希望在未来的10年中 7月4日这天 这个日历生效,那么你需要添加10个日期,分别是 2014-7-4 ,2015-7-4...... 2024-7-4 这样才行。
HolidayCalendar holidays = new HolidayCalendar();
Calendar calendar = new GregorianCalendar(2014, 7, 21);
holidays.addExcludedDate(calendar.getTime());
sched.addCalendar("holidays", holidays, false, false);
下面是查询排除日期的算法,getStartOfDayJavaCalendar是把分秒时毫秒都去掉 只留年月日 然后判断日期是否排除
@Override public boolean isTimeIncluded(long timeStamp) { if (super.isTimeIncluded(timeStamp) == false) { return false; } Date lookFor = getStartOfDayJavaCalendar(timeStamp).getTime(); return !(dates.contains(lookFor)); }
六、MonthlyCalendar
This implementation of the Calendar excludes a set of days of the month. You may use it to exclude every first day of each month for example. But you may define any day of a month.
月日历,你可以定义一个月当中的若干天,例如你可以设置每个月的第一天触发器不进行触发,当然你还可以定义一个月当中的任何一天。
下面例子给出每个月2,3,4号不触发的日历
MonthlyCalendar monthlyCalendar = new MonthlyCalendar();
monthlyCalendar.setDayExcluded(2, true);
monthlyCalendar.setDayExcluded(3, true);
monthlyCalendar.setDayExcluded(4, true);
sched.addCalendar("monthlyCalendar", monthlyCalendar, false, false);
七、WeeklyCalendar
This implementation of the Calendar excludes a set of days of the week. You may use it to exclude weekends for example. But you may define any day of the week. By default it excludes SATURDAY and SUNDAY.
星期日历,可以定义在一个星期当中的星期几几几 是不触发的日期,例如你可以定义么每个周末(星期天)触发器不触发,你也可以定义一周当中的任何一天或是几天。默认情况SATURDAY ,SUNDAY 这两天是没排除的。
下面的例子设置了每个星期四触发器不触发,并且默认情况周六和周天也是不触发的,这个是默认设置。如果需要周六周日也触发,那么把它清掉就可以了(weeklyCalendar.setDayExcluded(Calendar.SATURDAY , false)像这样)。一个需要注意的地方就是传入参数不能直接写数字星期几,因为老外的日子计算的与我们不一样,需要传入(java.util.Calendar)的常量字段,这样才准确。
WeeklyCalendar weeklyCalendar = new WeeklyCalendar();
weeklyCalendar.setDayExcluded(Calendar.THURSDAY, true);
sched.addCalendar("weeklyCalendar", weeklyCalendar, false, false);
八、组合日历的使用
上面的例子都是每一个触发器(trigger)关联一个日历的例子,我们在构建触发器的时候通过.modifiedByCalendar("日历的key")关联一个注册到引擎当中的日历,这种情况已经能够满足我们大部分的需求。
但是系统的需求往往是复杂多变的,假设有这样一种情况,需要一个触发器在 每周一到周五,早8点-晚晚5点 每隔1小时执行,那么该如何使用日历呢?
其实我们不用日历,使用一个CronTrigger也是可以搞定的,我们这里只不过是抛砖引玉而已。
那让我们来写一个组合日历使用的例子:
DailyCalendar dailyCalendar = new DailyCalendar("8:00:00", "17:00:00");
dailyCalendar.setInvertTimeRange(false);
WeeklyCalendar weeklyCalendar = new WeeklyCalendar(dailyCalendar);
sched.addCalendar("weeklyCalendar", weeklyCalendar, false, false);
我们写一个时间间隔的日历dailyCalendar,将其作为参数传递给weeklyCalendar就可以了,这样引擎在计算日历日期的时候会先判断dailyCalendar的时间范围,然后再判断weeklyCalendar是时间范围,当条件都满足的是否,触发器才会被触发,我们分析一下源码:
@Override public boolean isTimeIncluded(long timeStamp) { if (excludeAll == true) { return false; } // Test the base calendar first. Only if the base calendar not already // excludes the time/date, continue evaluating this calendar instance. if (super.isTimeIncluded(timeStamp) == false) { return false; } java.util.Calendar cl = createJavaCalendar(timeStamp); int wday = cl.get(java.util.Calendar.DAY_OF_WEEK); return !(isDayExcluded(wday)); }
我们发现它首先调用 if (super.isTimeIncluded(timeStamp) == false) { return false; } 奥秘就在这里,我们继续看。
public boolean isTimeIncluded(long timeStamp) { if (timeStamp <= 0) { throw new IllegalArgumentException( "timeStamp must be greater 0"); } if (baseCalendar != null) { if (baseCalendar.isTimeIncluded(timeStamp) == false) { return false; } } return true; }
这里先判断了baseCalendar,这个对象就是在构造参数传递进去的dailyCalendar , 也就是它先试用dailyCalendar 进行日期计算,然后自己在计算,这样就完成了日历的组合使用。
最后在补充说明一下往quartz的引擎中注册日历的方法。
addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers)
这个方法有四个参数
1、calName 日历的名字,在构建触发器时通过modifiedByCalendar("")这里使用。
2、calendar 日历对象。
3、replace 当日历已经存在的情况下是否替换,true=替换, false=不替换 如果不替换还出现重复的情况会抛出异常。
4、updateTriggers 这个参数比较重要,它的意思是当一个已经存在与调度引擎中的触发器,并且已经引用了一个日历,比如:一个(触发器A)关联了一个日历,这个日历过滤每个星期日。
现在过了一段时间这个日历更新了(星期六也过滤),那么这个属性是用来指示触发器是否使用新的日历。不然的话(触发器A)仍然使用旧版本的日历,如果在有新添加到引擎中的触发器才会使用新日历。
我们看一下源码:RAMJobStore 还有其它的Store原理也都一样。
public void storeCalendar(String name, Calendar calendar, boolean replaceExisting, boolean updateTriggers) throws ObjectAlreadyExistsException { calendar = (Calendar) calendar.clone(); synchronized (lock) { Object obj = calendarsByName.get(name); if (obj != null && replaceExisting == false) { throw new ObjectAlreadyExistsException( "Calendar with name '" + name + "' already exists."); } else if (obj != null) { calendarsByName.remove(name); } calendarsByName.put(name, calendar); if(obj != null && updateTriggers) { Iterator<TriggerWrapper> trigs = getTriggerWrappersForCalendar(name).iterator(); while (trigs.hasNext()) { TriggerWrapper tw = trigs.next(); OperableTrigger trig = tw.getTrigger(); boolean removed = timeTriggers.remove(tw); trig.updateWithNewCalendar(calendar, getMisfireThreshold()); if(removed) { timeTriggers.add(tw); } } } } }