Cron解析工具优化,解决day-of-month使用L时会跳过非31天的月份问题;

3.4.0-release
xuxueli 3 days ago
parent 905f8f05cb
commit 5b1cd833d7

@ -2814,7 +2814,7 @@ public void execute() {
- 13、【优化】执行器名称长度调整最长支持64字符
- 14、【优化】执行器注册表主键调整为long数据类型防止大规模执行器集群注册数据溢出
- 15、【优化】任务参数长度调整最长支持2048字符
- 16、【优化】Cron解析工具优化解决day-of-month使用L时会跳过非31天的月份问题
数据库升级脚本:
```

@ -1,4 +1,22 @@
package com.xxl.job.admin.scheduler.cron;
/*
* All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
* Copyright IBM Corp. 2024, 2025
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Borrowed from quartz v2.5.2
*/
import java.io.Serializable;
import java.text.ParseException;
@ -63,7 +81,7 @@ import java.util.TreeSet;
* <tr>
* <td><code>Month</code></td>
* <td>&nbsp;</td>
* <td><code>0-11 or JAN-DEC</code></td>
* <td><code>1-12 or JAN-DEC</code></td>
* <td>&nbsp;</td>
* <td><code>, - * /</code></td>
* </tr>
@ -186,8 +204,6 @@ import java.util.TreeSet;
* @author Sharada Jambula, James House
* @author Contributions from Mads Henderson
* @author Refactoring from CronTrigger to CronExpression by Aaron Craven
*
* Borrowed from quartz v2.5.0
*/
public final class CronExpression implements Serializable, Cloneable {
@ -649,6 +665,10 @@ public final class CronExpression implements Serializable, Cloneable {
addToSet(ALL_SPEC_INT, -1, incr, type);
return i;
} else if (c == 'L') {
if(type < DAY_OF_MONTH)
throw new ParseException("'L' not expected in seconds, minutes or hours fields.", i);
i++;
if (type == DAY_OF_WEEK) {
addToSet(7, 7, 0, type);
@ -705,15 +725,15 @@ public final class CronExpression implements Serializable, Cloneable {
private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
if (incr > 59 && (type == SECOND || type == MINUTE)) {
throw new ParseException("Increment > 60 : " + incr, idxPos);
throw new ParseException("Increment >= 60 : " + incr, idxPos);
} else if (incr > 23 && (type == HOUR)) {
throw new ParseException("Increment > 24 : " + incr, idxPos);
throw new ParseException("Increment >= 24 : " + incr, idxPos);
} else if (incr > 31 && (type == DAY_OF_MONTH)) {
throw new ParseException("Increment > 31 : " + incr, idxPos);
throw new ParseException("Increment >= 31 : " + incr, idxPos);
} else if (incr > 7 && (type == DAY_OF_WEEK)) {
throw new ParseException("Increment > 7 : " + incr, idxPos);
throw new ParseException("Increment >= 7 : " + incr, idxPos);
} else if (incr > 12 && (type == MONTH)) {
throw new ParseException("Increment > 12 : " + incr, idxPos);
throw new ParseException("Increment >= 12 : " + incr, idxPos);
}
}
@ -1293,15 +1313,20 @@ public final class CronExpression implements Serializable, Cloneable {
day = -1;
}
}
boolean needAdvance = false;
if (smallestDay.isPresent()) {
if (day == -1 || smallestDay.get() < day) {
day = smallestDay.get();
needAdvance = true;
}
} else if (day == -1) {
day = 1;
mon++;
needAdvance = true;
}
if (day != t || mon != tmon) {
if (needAdvance && (day != t || mon != tmon)) {
cl.set(Calendar.SECOND, 0);
cl.set(Calendar.MINUTE, 0);
cl.set(Calendar.HOUR_OF_DAY, 0);
@ -1311,6 +1336,7 @@ public final class CronExpression implements Serializable, Cloneable {
// are 1-based
continue;
}
} else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
if (lastDayOfWeek) { // are we looking for the last XXX day of
// the month?
@ -1523,12 +1549,53 @@ public final class CronExpression implements Serializable, Cloneable {
}
/**
* NOT YET IMPLEMENTED: Returns the time before the given time
* Returns the time before the given time
* that the <code>CronExpression</code> matches.
*
* @param endTime a time for which the previous
* matching time is returned
* @return the previous matching time before the given end time,
* or null if there are no previous matching times
*/
public Date getTimeBefore(Date endTime) {
// FUTURE_TODO: implement QUARTZ-423
return null;
// the current implementation is not a direct calculation, but rather
// uses getTimeAfter with a binary search to find the previous match time
long end = endTime.getTime();
long min = 0; // the epoch date is the minimum supported by this class
long max = end;
// check if it's satisfiable at all
Date date = new Date(min);
Date after = getTimeAfter(date);
if (after == null || after.getTime() >= end)
return null; // there are no after-times before end
// from this point forward min's time-after is always less than end,
// and max's time-after is always equal to or greater than end
// so we just need to shrink the interval until they meet.
// optimization - perform inverse binary search to find a tighter lower bound
long interval = 60 * 60 * 1000; // start with a reasonable interval
while (interval < max) {
date.setTime(max - interval);
after = getTimeAfter(date);
if (after != null && after.getTime() < max) {
min = date.getTime(); // found a closer min
break;
}
interval *= 2;
}
// perform a regular binary search to find the earliest moment
// whose time-after is equal to or greater than the end time -
// this moment is the previous match time itself
while (max - min > 1000) { // we can stop at 1 second resolution
long mid = (min + max) >>> 1;
date.setTime(mid);
after = getTimeAfter(date);
if (after != null && after.getTime() < end)
min = mid;
else
max = mid;
}
date.setTime(max - max % 1000); // round to second
return date;
}
/**
@ -1585,7 +1652,7 @@ public final class CronExpression implements Serializable, Cloneable {
final int lastDay = getLastDayOfMonth(mon, year);
// For "L", "L-1", etc.
int smallestDay = Optional.ofNullable(set.ceiling(LAST_DAY_OFFSET_END - (lastDay - day)))
final int smallestDay = Optional.ofNullable(set.ceiling(LAST_DAY_OFFSET_END - (lastDay - day)))
.map(d -> d - LAST_DAY_OFFSET_START + 1)
.orElse(Integer.MAX_VALUE);
@ -1593,10 +1660,14 @@ public final class CronExpression implements Serializable, Cloneable {
SortedSet<Integer> st = set.subSet(day, LAST_DAY_OFFSET_START);
// make sure we don't over-run a short month, such as february
if (!st.isEmpty() && st.first() < smallestDay && st.first() <= lastDay) {
smallestDay = st.first();
return Optional.of(st.first());
}
return smallestDay == Integer.MAX_VALUE ? Optional.empty() : Optional.of(smallestDay);
if (smallestDay == Integer.MAX_VALUE) {
return Optional.empty();
} else {
return Optional.of(smallestDay + lastDay - LAST_DAY_OFFSET_START + 1);
}
}
private void readObject(java.io.ObjectInputStream stream)

Loading…
Cancel
Save