shikeying
2024-01-19 54ea586ece304ef2569a345c9b26b2a9b9702c8a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
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);
    }
 
}