mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
修复cron无法生成小月的最后一天的bug;
This commit is contained in:
parent
03a92830a6
commit
b270bc1420
@ -11,14 +11,19 @@ import java.util.List;
|
|||||||
* @author Looly
|
* @author Looly
|
||||||
*/
|
*/
|
||||||
public class DayOfMonthMatcher extends BoolArrayMatcher {
|
public class DayOfMonthMatcher extends BoolArrayMatcher {
|
||||||
|
/**
|
||||||
|
* 是否是查询最后一天,即“L”
|
||||||
|
*/
|
||||||
|
private final boolean isLast;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
*
|
*
|
||||||
* @param intValueList 匹配的日值
|
* @param intValueList 匹配的日值
|
||||||
*/
|
*/
|
||||||
public DayOfMonthMatcher(List<Integer> intValueList) {
|
public DayOfMonthMatcher(List<Integer> intValueList, boolean isLast) {
|
||||||
super(intValueList);
|
super(intValueList);
|
||||||
|
this.isLast = isLast;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,4 +55,9 @@ public class DayOfMonthMatcher extends BoolArrayMatcher {
|
|||||||
private static boolean isLastDayOfMonth(int value, int month, boolean isLeapYear) {
|
private static boolean isLastDayOfMonth(int value, int month, boolean isLeapYear) {
|
||||||
return value == Month.getLastDay(month - 1, isLeapYear);
|
return value == Month.getLastDay(month - 1, isLeapYear);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isLast() {
|
||||||
|
return isLast;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package cn.hutool.cron.pattern.matcher;
|
package cn.hutool.cron.pattern.matcher;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.core.date.Month;
|
||||||
import cn.hutool.cron.pattern.Part;
|
import cn.hutool.cron.pattern.Part;
|
||||||
|
|
||||||
import java.time.Year;
|
import java.time.Year;
|
||||||
@ -184,14 +186,23 @@ public class PatternMatcher {
|
|||||||
// 周不参与计算
|
// 周不参与计算
|
||||||
i--;
|
i--;
|
||||||
continue;
|
continue;
|
||||||
}
|
} else if (i == Part.DAY_OF_MONTH.ordinal()
|
||||||
|
&& matchers[i] instanceof DayOfMonthMatcher
|
||||||
|
&& ((DayOfMonthMatcher) matchers[i]).isLast()) {
|
||||||
|
int newMonth = newValues[Part.MONTH.ordinal()];
|
||||||
|
int newYear = newValues[Part.YEAR.ordinal()];
|
||||||
|
nextValue = Month.of(newMonth - 1).getLastDay(DateUtil.isLeapYear(newYear));
|
||||||
|
} else {
|
||||||
nextValue = matchers[i].nextAfter(values[i]);
|
nextValue = matchers[i].nextAfter(values[i]);
|
||||||
|
}
|
||||||
if (nextValue > values[i]) {
|
if (nextValue > values[i]) {
|
||||||
// 此部分正常获取新值,结束循环,后续的部分置最小值
|
// 此部分正常获取新值,结束循环,后续的部分置最小值
|
||||||
newValues[i] = nextValue;
|
newValues[i] = nextValue;
|
||||||
i--;
|
i--;
|
||||||
break;
|
break;
|
||||||
} else if (nextValue < values[i]) {
|
} else if (nextValue < values[i]) {
|
||||||
|
// 回退前保存最新值
|
||||||
|
newValues[i] = nextValue;
|
||||||
// 此部分下一个值获取到的值产生回退,回到上一个部分,继续获取新值
|
// 此部分下一个值获取到的值产生回退,回到上一个部分,继续获取新值
|
||||||
i++;
|
i++;
|
||||||
nextValue = -1;// 标记回退查找
|
nextValue = -1;// 标记回退查找
|
||||||
@ -208,8 +219,15 @@ public class PatternMatcher {
|
|||||||
// 周不参与计算
|
// 周不参与计算
|
||||||
i++;
|
i++;
|
||||||
continue;
|
continue;
|
||||||
}
|
} else if (i == Part.DAY_OF_MONTH.ordinal()
|
||||||
|
&& matchers[i] instanceof DayOfMonthMatcher
|
||||||
|
&& ((DayOfMonthMatcher) matchers[i]).isLast()) {
|
||||||
|
int newMonth = newValues[Part.MONTH.ordinal()];
|
||||||
|
int newYear = newValues[Part.YEAR.ordinal()];
|
||||||
|
nextValue = Month.of(newMonth - 1).getLastDay(DateUtil.isLeapYear(newYear));
|
||||||
|
} else {
|
||||||
nextValue = matchers[i].nextAfter(values[i] + 1);
|
nextValue = matchers[i].nextAfter(values[i] + 1);
|
||||||
|
}
|
||||||
if (nextValue > values[i]) {
|
if (nextValue > values[i]) {
|
||||||
newValues[i] = nextValue;
|
newValues[i] = nextValue;
|
||||||
i--;
|
i--;
|
||||||
@ -234,9 +252,17 @@ public class PatternMatcher {
|
|||||||
Part part;
|
Part part;
|
||||||
for (int i = 0; i <= toPart; i++) {
|
for (int i = 0; i <= toPart; i++) {
|
||||||
part = Part.of(i);
|
part = Part.of(i);
|
||||||
|
if (part == Part.DAY_OF_MONTH
|
||||||
|
&& get(part) instanceof DayOfMonthMatcher
|
||||||
|
&& ((DayOfMonthMatcher) get(part)).isLast()) {
|
||||||
|
int newMonth = values[Part.MONTH.ordinal()];
|
||||||
|
int newYear = values[Part.YEAR.ordinal()];
|
||||||
|
values[i] = Month.of(newMonth - 1).getLastDay(DateUtil.isLeapYear(newYear));
|
||||||
|
} else {
|
||||||
values[i] = getMin(part);
|
values[i] = getMin(part);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取表达式部分的最小值
|
* 获取表达式部分的最小值
|
||||||
|
@ -32,6 +32,10 @@ import java.util.List;
|
|||||||
public class PartParser {
|
public class PartParser {
|
||||||
|
|
||||||
private final Part part;
|
private final Part part;
|
||||||
|
/**
|
||||||
|
* 是否是查询最后一天
|
||||||
|
*/
|
||||||
|
private boolean isLastDay;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建解析器
|
* 创建解析器
|
||||||
@ -69,6 +73,7 @@ public class PartParser {
|
|||||||
return new AlwaysTrueMatcher();
|
return new AlwaysTrueMatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isLastDay = false;
|
||||||
final List<Integer> values = parseArray(value);
|
final List<Integer> values = parseArray(value);
|
||||||
if (values.isEmpty()) {
|
if (values.isEmpty()) {
|
||||||
throw new CronException("Invalid part value: [{}]", value);
|
throw new CronException("Invalid part value: [{}]", value);
|
||||||
@ -76,7 +81,7 @@ public class PartParser {
|
|||||||
|
|
||||||
switch (this.part) {
|
switch (this.part) {
|
||||||
case DAY_OF_MONTH:
|
case DAY_OF_MONTH:
|
||||||
return new DayOfMonthMatcher(values);
|
return new DayOfMonthMatcher(values, isLastDay);
|
||||||
case YEAR:
|
case YEAR:
|
||||||
return new YearValueMatcher(values);
|
return new YearValueMatcher(values);
|
||||||
default:
|
default:
|
||||||
@ -265,6 +270,7 @@ public class PartParser {
|
|||||||
*/
|
*/
|
||||||
private int parseAlias(String name) throws CronException {
|
private int parseAlias(String name) throws CronException {
|
||||||
if ("L".equalsIgnoreCase(name)) {
|
if ("L".equalsIgnoreCase(name)) {
|
||||||
|
isLastDay = true;
|
||||||
// L表示最大值
|
// L表示最大值
|
||||||
return part.getMax();
|
return part.getMax();
|
||||||
}
|
}
|
||||||
|
@ -97,4 +97,135 @@ public class CronPatternNextMatchTest {
|
|||||||
final Calendar calendar = pattern.nextMatchAfter(time.toCalendar());
|
final Calendar calendar = pattern.nextMatchAfter(time.toCalendar());
|
||||||
Assert.assertEquals("2022-04-09 01:01:01", DateUtil.date(calendar).toString());
|
Assert.assertEquals("2022-04-09 01:01:01", DateUtil.date(calendar).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLastDayOfMonthForEveryMonth1() {
|
||||||
|
DateTime date = DateUtil.parse("2023-01-08 07:44:16");
|
||||||
|
DateTime result = DateUtil.parse("2023-01-31 03:02:01");
|
||||||
|
// 匹配所有月,生成每个月的最后一天
|
||||||
|
CronPattern pattern = new CronPattern("1 2 3 L * ?");
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
Calendar calendar = pattern.nextMatchAfter(date.toCalendar());
|
||||||
|
date = DateUtil.date(calendar);
|
||||||
|
Assert.assertEquals(date, result);
|
||||||
|
// 加一秒
|
||||||
|
date = date.offset(DateField.SECOND, 1);
|
||||||
|
|
||||||
|
// 移动到下一个月的最后一天
|
||||||
|
result = result.offset(DateField.DAY_OF_MONTH, 1);
|
||||||
|
int lastDayOfMonth = DateUtil.getLastDayOfMonth(result);
|
||||||
|
result.setField(DateField.DAY_OF_MONTH, lastDayOfMonth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLastDayOfMonthForEveryMonth2() {
|
||||||
|
DateTime date = DateUtil.parse("2023-03-08 07:44:16");
|
||||||
|
DateTime result = DateUtil.parse("2023-03-31 03:02:01");
|
||||||
|
// 匹配所有月,生成每个月的最后一天
|
||||||
|
CronPattern pattern = new CronPattern("1 2 3 L * ?");
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
Calendar calendar = pattern.nextMatchAfter(date.toCalendar());
|
||||||
|
date = DateUtil.date(calendar);
|
||||||
|
Assert.assertEquals(date, result);
|
||||||
|
// 加一秒
|
||||||
|
date = date.offset(DateField.SECOND, 1);
|
||||||
|
|
||||||
|
// 移动到下一个月的最后一天
|
||||||
|
result = result.offset(DateField.DAY_OF_MONTH, 1);
|
||||||
|
int lastDayOfMonth = DateUtil.getLastDayOfMonth(result);
|
||||||
|
result.setField(DateField.DAY_OF_MONTH, lastDayOfMonth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLastDayOfMonthForEveryYear1() {
|
||||||
|
DateTime date = DateUtil.parse("2023-01-08 07:44:16");
|
||||||
|
DateTime result = DateUtil.parse("2023-02-28 03:02:01");
|
||||||
|
// 匹配每一年2月的最后一天
|
||||||
|
CronPattern pattern = new CronPattern("1 2 3 L 2 ?");
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
Calendar calendar = pattern.nextMatchAfter(date.toCalendar());
|
||||||
|
date = DateUtil.date(calendar);
|
||||||
|
Assert.assertEquals(date, result);
|
||||||
|
// 加一秒
|
||||||
|
date = date.offset(DateField.SECOND, 1);
|
||||||
|
|
||||||
|
// 移动到下一年的最后一天
|
||||||
|
result = result.offset(DateField.YEAR, 1);
|
||||||
|
int lastDayOfMonth = DateUtil.getLastDayOfMonth(result);
|
||||||
|
result.setField(DateField.DAY_OF_MONTH, lastDayOfMonth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLastDayOfMonthForEveryYear2() {
|
||||||
|
DateTime date = DateUtil.parse("2022-03-08 07:44:16");
|
||||||
|
DateTime result = DateUtil.parse("2023-02-28 03:02:01");
|
||||||
|
// 匹配每一年2月的最后一天
|
||||||
|
CronPattern pattern = new CronPattern("1 2 3 L 2 ?");
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
Calendar calendar = pattern.nextMatchAfter(date.toCalendar());
|
||||||
|
date = DateUtil.date(calendar);
|
||||||
|
Assert.assertEquals(date, result);
|
||||||
|
// 加一秒
|
||||||
|
date = date.offset(DateField.SECOND, 1);
|
||||||
|
|
||||||
|
// 移动到下一年的最后一天
|
||||||
|
result = result.offset(DateField.YEAR, 1);
|
||||||
|
int lastDayOfMonth = DateUtil.getLastDayOfMonth(result);
|
||||||
|
result.setField(DateField.DAY_OF_MONTH, lastDayOfMonth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEveryHour() {
|
||||||
|
DateTime date = DateUtil.parse("2022-02-28 07:44:16");
|
||||||
|
DateTime result = DateUtil.parse("2022-02-28 08:02:01");
|
||||||
|
// 匹配每一年2月的最后一天
|
||||||
|
CronPattern pattern = new CronPattern("1 2 */1 * * ?");
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
Calendar calendar = pattern.nextMatchAfter(date.toCalendar());
|
||||||
|
date = DateUtil.date(calendar);
|
||||||
|
Assert.assertEquals(date, result);
|
||||||
|
// 加一秒
|
||||||
|
date = date.offset(DateField.SECOND, 1);
|
||||||
|
|
||||||
|
// 移动到下一个小时
|
||||||
|
result = result.offset(DateField.HOUR_OF_DAY, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLastDayOfMonthForEveryHour() {
|
||||||
|
DateTime date = DateUtil.parse("2023-01-28 07:44:16");
|
||||||
|
DateTime result = DateUtil.parse("2023-01-31 00:00:00");
|
||||||
|
// 匹配每一年2月的最后一天
|
||||||
|
CronPattern pattern = new CronPattern("0 0 */1 L * ?");
|
||||||
|
for (int i = 0; i < 400; i++) {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
Calendar calendar = pattern.nextMatchAfter(date.toCalendar());
|
||||||
|
date = DateUtil.date(calendar);
|
||||||
|
Assert.assertEquals(date, result);
|
||||||
|
// 加一秒
|
||||||
|
date = date.offset(DateField.SECOND, 1);
|
||||||
|
|
||||||
|
// 移动到下一个小时
|
||||||
|
DateTime t = result.setMutable(false).offset(DateField.HOUR_OF_DAY, 1);
|
||||||
|
if (t.dayOfMonth() != result.dayOfMonth()) {
|
||||||
|
// 移动到下个月最后一天的开始
|
||||||
|
result = result.offset(DateField.DAY_OF_MONTH, 1);
|
||||||
|
int lastDayOfMonth = DateUtil.getLastDayOfMonth(result);
|
||||||
|
result = result.setField(DateField.DAY_OF_MONTH, lastDayOfMonth);
|
||||||
|
result = DateUtil.beginOfDay(result);
|
||||||
|
} else {
|
||||||
|
result = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user