From 5b1cd833d709ab9e59e8153e209a4be9cdca4020 Mon Sep 17 00:00:00 2001 From: xuxueli <931591021@qq.com> Date: Sun, 5 Apr 2026 19:53:05 +0800 Subject: [PATCH] =?UTF-8?q?Cron=E8=A7=A3=E6=9E=90=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=8C=E8=A7=A3=E5=86=B3day-of-month?= =?UTF-8?q?=E4=BD=BF=E7=94=A8L=E6=97=B6=E4=BC=9A=E8=B7=B3=E8=BF=87?= =?UTF-8?q?=E9=9D=9E31=E5=A4=A9=E7=9A=84=E6=9C=88=E4=BB=BD=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/XXL-JOB官方文档.md | 2 +- .../admin/scheduler/cron/CronExpression.java | 101 +++++++++++++++--- 2 files changed, 87 insertions(+), 16 deletions(-) diff --git a/doc/XXL-JOB官方文档.md b/doc/XXL-JOB官方文档.md index 9a406e31..8bffb948 100644 --- a/doc/XXL-JOB官方文档.md +++ b/doc/XXL-JOB官方文档.md @@ -2814,7 +2814,7 @@ public void execute() { - 13、【优化】执行器名称长度调整,最长支持64字符; - 14、【优化】执行器注册表主键调整为long数据类型,防止大规模执行器集群注册数据溢出; - 15、【优化】任务参数长度调整,最长支持2048字符; - +- 16、【优化】Cron解析工具优化,解决day-of-month使用L时会跳过非31天的月份问题; 数据库升级脚本: ``` diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/cron/CronExpression.java index 212178df..65e5b607 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/cron/CronExpression.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/cron/CronExpression.java @@ -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; *
Month0-11 or JAN-DEC1-12 or JAN-DEC, - * /CronExpression 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