package com.walker.scheduler;
|
|
import com.walker.infrastructure.utils.DateUtils;
|
import com.walker.infrastructure.utils.StringUtils;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
|
import java.text.DateFormat;
|
import java.text.SimpleDateFormat;
|
import java.util.ArrayList;
|
import java.util.Date;
|
import java.util.List;
|
|
/**
|
* 调度器时间设置选项定义
|
* @author shikeying
|
* @date 2015年12月24日
|
*
|
*/
|
public class Option {
|
|
protected transient final Logger logger = LoggerFactory.getLogger(this.getClass());
|
|
private static final DateFormat whippletreeTimeFormat = new SimpleDateFormat("yyyy MM dd HH mm");
|
|
private PeriodType periodType = PeriodType.NONE;
|
|
private TimeType timeType = TimeType.EXACTLY;
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// 以下属性是'精确时间点'设置
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
private int hour = 0;
|
private int day = 1;
|
private int month = 1;
|
private int year = 2015;
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// 以下属性是'时间段'设置
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
private List<Integer[]> timeRanges = new ArrayList<Integer[]>(2);
|
|
// 内部计数器,在周期性调度中使用
|
private boolean isCycleTask = true; // 是否周期性调度器,运行一次也算周期性
|
|
/**
|
* 是否周期性任务,如果是返回true
|
* @return
|
*/
|
public boolean isCycleTask() {
|
return isCycleTask;
|
}
|
|
// private int currentTaskHour = 0; // 周期性,当前执行到的小时,记录
|
private int currentTaskDay = 0; // 周期性,当前执行到的天,记录
|
private int currentTaskMonth = 0; // 周期性,当前执行到的月,记录
|
private int currentTaskYear = 0; // 周期性,当前执行到的年,记录
|
|
public int getCurrentTaskDay() {
|
return currentTaskDay;
|
}
|
|
public int getCurrentTaskMonth() {
|
return currentTaskMonth;
|
}
|
|
public int getCurrentTaskYear() {
|
return currentTaskYear;
|
}
|
|
public String getTimeRangesValue() {
|
StringBuilder s = new StringBuilder();
|
for(Integer[] vals : timeRanges){
|
s.append(vals[0]);
|
s.append(",");
|
s.append(vals[1]);
|
}
|
return s.toString();
|
}
|
|
public Option(){}
|
|
public TimeType getTimeType() {
|
return timeType;
|
}
|
|
public PeriodType getPeriodType() {
|
return periodType;
|
}
|
|
public void setTimeType(TimeType timeType) {
|
this.timeType = timeType;
|
}
|
|
public void setPeriodType(PeriodType periodType) {
|
this.periodType = periodType;
|
if(periodType == PeriodType.DAY
|
|| periodType == PeriodType.WEEK
|
|| periodType == PeriodType.MONTH
|
|| periodType == PeriodType.YEAR
|
|| periodType == PeriodType.NONE){
|
this.isCycleTask = true;
|
} else {
|
this.isCycleTask = false;
|
}
|
}
|
|
/**
|
* 设置定时任务的精确时间
|
* @param year
|
* @param month
|
* @param day
|
* @param hour
|
*/
|
public void setExactlyTime(int year, int month, int day, int hour){
|
this.year = year;
|
this.month = month;
|
this.day = day;
|
this.hour = hour;
|
|
this.currentTaskYear = year;
|
this.currentTaskMonth = month;
|
this.currentTaskDay = day;
|
// this.currentTaskHour = hour;
|
|
// 2024-01-18,修复:如果已经过了今天时间点的任务,如:凌晨2点,则需要自动切换到下一个时间。
|
// 否则出现任务永远无法执行情况。
|
if(this.isCycleTask){
|
Option.TimeObject timeObj = this.isAvailableTime(System.currentTimeMillis());
|
int currentHour = DateUtils.getCurrentHour();
|
int currentDay = DateUtils.getCurrentYearMonthDay()[2];
|
if(this.periodType == PeriodType.DAY){
|
if(currentHour > hour){
|
// 今天已经过了时间点,自动切换到下一次
|
this.scheduleToNext(timeObj);
|
logger.info("今天已经过了时间点,自动切换到下一次,option={}", this);
|
}
|
|
} else if (this.periodType == PeriodType.MONTH) {
|
if(currentDay > day){
|
this.scheduleToNext(timeObj);
|
logger.info("本月当前日期{}已超过设置日期{},自动切换到下一次,option={}", currentDay, day, this);
|
} else if(currentDay == day && (currentHour > hour)){
|
this.scheduleToNext(timeObj);
|
logger.info("本月日期已到达,但由于时间已过期,因此任务会在下个月执行,option={}", this);
|
}
|
|
} else if (this.periodType == PeriodType.YEAR) {
|
int currentMonth = DateUtils.getCurrentYearMonthDay()[1];
|
if(currentMonth > month){
|
this.scheduleToNext(timeObj);
|
logger.info("本年当前月份{}已超过设置月份{},自动切换到下一次,option={}", currentMonth, month, this);
|
} else if (currentMonth == month && (currentDay > day)) {
|
this.scheduleToNext(timeObj);
|
logger.info("本年当前月份相同,但当前日期{}已超过设置日期{},自动切换到下一次,option={}", currentDay, day, this);
|
} else if(currentMonth == month && (currentDay == day) && currentHour > hour){
|
this.scheduleToNext(timeObj);
|
logger.info("本年当前月份日期相同,但当前时间{}已超过设置时间{},自动切换到下一次,option={}", currentHour, hour, this);
|
}
|
}
|
}
|
}
|
|
/**
|
* 设置定时任务的,时间段范围,可以有多个
|
*/
|
public void setRangeTime(List<Integer[]> timeRanges){
|
if(timeRanges == null || timeRanges.size() == 0){
|
throw new IllegalArgumentException("设置的时间范围数据必须存在");
|
}
|
for(Integer[] r : timeRanges){
|
if(r.length != 2){
|
throw new IllegalArgumentException("设置时间范围集合中,每个数组表示一个范围:开始钟点、结束钟点。如:12~13");
|
}
|
}
|
this.timeRanges = timeRanges;
|
}
|
|
/**
|
* 系统给定的时间,是否满足(调度任务)设定时间的要求</p>
|
* 该方法返回的是对象<code>TimeObject</code>,里面有状态信息。<br>
|
* timeObject.isAvailable();返回了是否有效时间。
|
* @param currentTime
|
* @return
|
*/
|
public TimeObject isAvailableTime(long currentTime){
|
return this.doCheckAvailable(currentTime);
|
}
|
|
/**
|
* 系统给定的时间,是否满足(调度任务)设定时间的要求
|
* @param currentTime
|
* @return
|
*/
|
public boolean isAvailable(long currentTime){
|
return doCheckAvailable(currentTime).isAvailable();
|
}
|
|
/**
|
* 是周期性调度时,调用该方法切换到下一个时间点。
|
* @param currentTimeObj
|
*/
|
public void scheduleToNext(TimeObject currentTimeObj){
|
if(this.isCycleTask){
|
if(this.periodType == PeriodType.DAY || this.periodType == PeriodType.NONE){
|
int[] nextDateInfo = DateUtils.getNextDay(currentTimeObj.getYear(), currentTimeObj.getMonth(), currentTimeObj.getDay(), 1);
|
this.currentTaskYear = nextDateInfo[0];
|
this.currentTaskMonth = nextDateInfo[1];
|
this.currentTaskDay = nextDateInfo[2];
|
|
} else if(this.periodType == PeriodType.MONTH){
|
// 每个月的:几号、几点执行
|
this.currentTaskYear = currentTimeObj.year;
|
if(this.currentTaskMonth < 12){
|
this.currentTaskMonth++;
|
} else {
|
this.currentTaskMonth = 1;
|
}
|
|
} else if(this.periodType == PeriodType.YEAR){
|
this.currentTaskYear++;
|
|
} else {
|
throw new UnsupportedOperationException();
|
}
|
}
|
}
|
|
private TimeObject doCheckAvailable(long currentTime){
|
TimeObject timeObj = getTimeInfo(currentTime);
|
boolean result = false;
|
|
if(this.isCycleTask){
|
// 周期类型调度,必须精确到年月日时
|
if(this.periodType == PeriodType.DAY){
|
if(this.currentTaskDay == timeObj.day && this.hour == timeObj.hour){
|
result = true;
|
}
|
|
} else if(this.periodType == PeriodType.WEEK){
|
throw new UnsupportedOperationException("还未实现按周调度");
|
|
} else if(this.periodType == PeriodType.MONTH){
|
if(this.currentTaskMonth == timeObj.month && this.day == timeObj.day && this.hour == timeObj.hour){
|
result = true;
|
}
|
|
} else if(this.periodType == PeriodType.YEAR){
|
if(this.currentTaskYear == timeObj.year && this.month == timeObj.month && this.day == timeObj.day && this.hour == timeObj.hour){
|
result = true;
|
}
|
|
} else if(this.periodType == PeriodType.NONE){
|
if(this.year == timeObj.year && this.month == timeObj.month && this.day == timeObj.day && this.hour == timeObj.hour){
|
result = true;
|
}
|
|
} else {
|
throw new UnsupportedOperationException();
|
}
|
} else {
|
// 采集类型调度
|
result = this.doAnalizeHourMatch(timeObj);
|
}
|
timeObj.setAvailable(result);
|
return timeObj;
|
|
/**
|
if(this.periodType == PeriodType.DAY){
|
// 直接比较时间
|
result = this.doAnalizeHourMatch(timeObj);
|
|
} else if(this.periodType == PeriodType.WEEK){
|
throw new UnsupportedOperationException("还未实现按周调度");
|
|
} else if(this.periodType == PeriodType.MONTH){
|
// 比较日期是否对应
|
if(timeObj.isSameDay(day)){
|
result = this.doAnalizeHourMatch(timeObj);
|
}
|
} else if(this.periodType == PeriodType.YEAR){
|
// 比较月份、日期是否对应
|
if(timeObj.isSameDay(month, day)){
|
result = this.doAnalizeHourMatch(timeObj);
|
}
|
} else {
|
// 不重复,仅执行一次,年月日都比较
|
// if(timeObj.isSameDay(year, month, day)){
|
// result = this.doAnalizeHourMatch(timeObj);
|
// }
|
//改为立即执行
|
result = true;
|
}
|
timeObj.setAvailable(result);
|
return timeObj;
|
**/
|
}
|
|
private boolean doAnalizeHourMatch(TimeObject timeObj){
|
if(this.timeType == TimeType.EXACTLY){
|
if(timeObj.getHour() >= this.hour){
|
logger.debug("超过设定时间,执行任务");
|
return true;
|
}
|
} else {
|
// 时间段判断
|
for(Integer[] r : timeRanges){
|
if(timeObj.getHour() >= r[0] && timeObj.getHour() <= r[1]){
|
logger.debug("------匹配了时间段:" + r[0]);
|
return true;
|
}
|
}
|
}
|
return false;
|
}
|
|
private TimeObject getTimeInfo(long currentTime){
|
String showDate = whippletreeTimeFormat.format(new Date(currentTime));
|
// String[] showDateArray = showDate.split(" ");
|
String[] showDateArray = showDate.split(StringUtils.CHAR_SPACE);
|
TimeObject timeObj = new TimeObject(Integer.parseInt(showDateArray[0])
|
, Integer.parseInt(showDateArray[1])
|
, Integer.parseInt(showDateArray[2])
|
, Integer.parseInt(showDateArray[3]));
|
// logger.debug(timeObj);
|
return timeObj;
|
}
|
|
/**
|
* 执行周期定义
|
* @author shikeying
|
* @date 2015年12月23日
|
*
|
*/
|
public enum PeriodType {
|
NONE{
|
public String getIndex(){
|
return PERIOD_TYPE_ONCE;
|
}
|
}
|
, DAY{
|
public String getIndex(){
|
return PERIOD_TYPE_CYCLE_DAY;
|
}
|
}
|
, WEEK{
|
public String getIndex(){
|
return PERIOD_TYPE_CYCLE_WEEK;
|
}
|
}
|
, MONTH{
|
public String getIndex(){
|
return PERIOD_TYPE_CYCLE_MONTH;
|
}
|
}
|
, YEAR{
|
public String getIndex(){
|
return PERIOD_TYPE_CYCLE_YEAR;
|
}
|
}, FOREVER {
|
public String getIndex(){
|
return PERIOD_TYPE_FOREVER;
|
}
|
};
|
|
public String getIndex(){
|
throw new AbstractMethodError();
|
}
|
|
public static PeriodType getObject(String index){
|
if(index.equals(PERIOD_TYPE_ONCE)){
|
return NONE;
|
} else if(index.equals(PERIOD_TYPE_CYCLE_DAY)){
|
return DAY;
|
} else if(index.equals(PERIOD_TYPE_CYCLE_WEEK)){
|
return WEEK;
|
} else if(index.equals(PERIOD_TYPE_CYCLE_MONTH)){
|
return MONTH;
|
} else if(index.equals(PERIOD_TYPE_CYCLE_YEAR)){
|
return YEAR;
|
} else if(index.equals(PERIOD_TYPE_FOREVER)){
|
return FOREVER;
|
} else {
|
throw new IllegalArgumentException();
|
}
|
}
|
|
public static final String PERIOD_TYPE_ONCE = "none"; // 只执行一次,即:任务已发布就执行
|
public static final String PERIOD_TYPE_CYCLE_DAY = "day"; // 周期性:每天某个时间(或范围)
|
public static final String PERIOD_TYPE_CYCLE_WEEK = "week"; // 周期性:每周某个时间(或范围)
|
public static final String PERIOD_TYPE_CYCLE_MONTH = "month";// 周期性:每月某个时间(或范围)
|
public static final String PERIOD_TYPE_CYCLE_YEAR = "year"; // 周期性:每年某个时间(或范围)
|
public static final String PERIOD_TYPE_FOREVER = "forever"; // 采集类型:永远循环执行,需要设置间隔时间等参数
|
}
|
|
/**
|
* 执行时间类型定义
|
* @author shikeying
|
* @date 2015年12月23日
|
*
|
*/
|
public enum TimeType {
|
/**
|
* 精确时间点
|
*/
|
EXACTLY{
|
public String getIndex(){
|
return "exactly";
|
}
|
}
|
|
/**
|
* 时间段范围
|
*/
|
, RANGE{
|
public String getIndex(){
|
return "ranges";
|
}
|
};
|
|
public String getIndex(){
|
throw new AbstractMethodError();
|
}
|
|
public static TimeType getObject(String index){
|
if(index.equals("exactly")){
|
return EXACTLY;
|
} else {
|
return RANGE;
|
}
|
}
|
}
|
|
public class TimeObject{
|
private int year = 2015;
|
private int month = 1;
|
private int day = 1;
|
private int hour = 1;
|
|
// 标识,当前的时间对象是否有效的调度时间
|
// 在执行循环时,增加该属性能判断是否切换了日期
|
// 2016-01-26 时克英
|
private boolean available = false;
|
|
/**
|
* 标识,当前的时间对象是否有效的调度时间
|
* @return
|
*/
|
public boolean isAvailable() {
|
return available;
|
}
|
public void setAvailable(boolean available) {
|
this.available = available;
|
}
|
public int getYear() {
|
return year;
|
}
|
public int getMonth() {
|
return month;
|
}
|
public int getDay() {
|
return day;
|
}
|
public int getHour() {
|
return hour;
|
}
|
public TimeObject(int year, int month, int day, int hour){
|
this.year = year;
|
this.month = month;
|
this.day = day;
|
this.hour = hour;
|
}
|
|
@Override
|
public String toString(){
|
return new StringBuilder().append("[year=").append(year)
|
.append(", month=").append(month)
|
.append(", day=").append(day)
|
.append(", hour=").append(hour)
|
.append("]").toString();
|
}
|
|
public boolean isSameDay(int year, int month, int day){
|
if(this.year == year && this.month == month
|
&& this.day == day){
|
return true;
|
}
|
return false;
|
}
|
|
public boolean isSameDay(int month, int day){
|
if(this.month == month && this.day == day){
|
return true;
|
}
|
return false;
|
}
|
|
public boolean isSameDay(int day){
|
if(this.day == day){
|
return true;
|
}
|
return false;
|
}
|
}
|
|
@Override
|
public String toString(){
|
return new StringBuilder("[periodType=").append(this.periodType)
|
.append(", timeType=").append(this.timeType)
|
.append(", timeRanges=").append(this.timeRanges)
|
.append(", isCycleTask=").append(this.isCycleTask)
|
.append(", setYearMonthDayHour=").append(this.year).append("-").append(this.month).append("-").append(this.day).append("-").append(this.hour)
|
.append(", nextTime=").append(this.currentTaskYear).append("-").append(this.currentTaskMonth).append("-").append(this.currentTaskDay)
|
.append("]").toString();
|
}
|
|
public static void main(String[] args){
|
/**
|
long test = System.currentTimeMillis();
|
Option option = new Option();
|
// option.setExactlyTime(2015, 12, 23, 17);
|
|
List<Integer[]> timeRanges = new ArrayList<Integer[]>(2);
|
timeRanges.add(new Integer[]{9,10});
|
timeRanges.add(new Integer[]{12,13});
|
option.setTimeType(TimeType.RANGE);
|
option.setRangeTime(timeRanges);
|
// 设置每天执行
|
// option.setPeriodType(PeriodType.DAY);
|
// option.setExactlyTime(0, 0, 0, 0);
|
// 设置每月执行
|
// option.setPeriodType(PeriodType.MONTH);
|
// option.setExactlyTime(0, 0, 24, 0);
|
// 设置每年执行
|
option.setPeriodType(PeriodType.YEAR);
|
option.setExactlyTime(0, 12, 24, 0);
|
option.isAvailable(test);
|
**/
|
testCycleDay();
|
testCycleMonth();
|
}
|
|
private static void testCycleDay(){
|
Option option = new Option();
|
option.setPeriodType(PeriodType.DAY);
|
option.setTimeType(Option.TimeType.EXACTLY);
|
option.setExactlyTime(2023, 12, 31, 11);
|
|
TimeObject timeObj = option.isAvailableTime(System.currentTimeMillis());
|
System.out.println("第一次调用结果:" + timeObj.isAvailable());
|
|
option.scheduleToNext(timeObj);
|
System.out.println("切换后,调用结果:" + option.isAvailable(System.currentTimeMillis()));
|
System.out.println(option.currentTaskYear + "年" + option.currentTaskMonth + "月" + option.currentTaskDay + "日");
|
}
|
|
private static void testCycleMonth(){
|
Option option = new Option();
|
option.setPeriodType(PeriodType.MONTH);
|
option.setExactlyTime(2019, 1, 2, 16);
|
|
TimeObject timeObj = option.isAvailableTime(System.currentTimeMillis());
|
System.out.println("第一次调用结果:" + timeObj.isAvailable());
|
|
option.scheduleToNext(timeObj);
|
System.out.println("切换后,调用结果:" + option.isAvailable(System.currentTimeMillis()));
|
System.out.println(option.currentTaskMonth);
|
}
|
|
}
|