diff --git a/powerjob-server/powerjob-server-common/src/main/java/tech/powerjob/server/common/utils/TimeUtils.java b/powerjob-server/powerjob-server-common/src/main/java/tech/powerjob/server/common/utils/TimeUtils.java index ea6e388b..2e0b0dd0 100644 --- a/powerjob-server/powerjob-server-common/src/main/java/tech/powerjob/server/common/utils/TimeUtils.java +++ b/powerjob-server/powerjob-server-common/src/main/java/tech/powerjob/server/common/utils/TimeUtils.java @@ -28,6 +28,37 @@ public class TimeUtils { */ private static final long MAX_OFFSET = 5000; + /** + * 根据蔡勒公式计算任意一个日期是星期几 + * @param year 年 + * @param month 月 + * @param day 日 + * @return 中国星期 + */ + public static int calculateWeek(int year, int month, int day) { + if (month == 1) { + month = 13; + year--; + } + if (month == 2) { + month = 14; + year--; + } + int y = year % 100; + int c = year /100 ; + int h = (y + (y / 4) + (c / 4) - (2 * c) + ((26 * (month + 1)) / 10) + day - 1) % 7; + //可能是负值,因此计算除以7的余数之后需要判断是大于等于0还是小于0,如果小于0则将余数加7。 + if (h < 0){ + h += 7; + } + + // 国内理解中星期日为 7 + if (h == 0) { + return 7; + } + return h; + } + public static void check() throws TimeCheckException { NTPUDPClient timeClient = new NTPUDPClient(); diff --git a/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/scheduler/auxiliary/impl/DailyTimeIntervalStrategyHandler.java b/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/scheduler/auxiliary/impl/DailyTimeIntervalStrategyHandler.java index 92fe2629..d6022bc6 100644 --- a/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/scheduler/auxiliary/impl/DailyTimeIntervalStrategyHandler.java +++ b/powerjob-server/powerjob-server-core/src/main/java/tech/powerjob/server/core/scheduler/auxiliary/impl/DailyTimeIntervalStrategyHandler.java @@ -1,20 +1,24 @@ package tech.powerjob.server.core.scheduler.auxiliary.impl; +import com.google.common.collect.Sets; import lombok.Data; -import lombok.Getter; -import lombok.Setter; import lombok.SneakyThrows; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; -import tech.powerjob.common.OmsConstant; import tech.powerjob.common.enums.TimeExpressionType; import tech.powerjob.common.serialize.JsonUtils; +import tech.powerjob.common.utils.CollectionUtils; import tech.powerjob.common.utils.CommonUtils; +import tech.powerjob.server.common.utils.TimeUtils; import tech.powerjob.server.core.scheduler.auxiliary.TimeOfDay; import tech.powerjob.server.core.scheduler.auxiliary.TimingStrategyHandler; import java.io.Serializable; +import java.util.Calendar; import java.util.Date; +import java.util.Optional; import java.util.Set; +import java.util.concurrent.TimeUnit; /** * DailyTimeIntervalStrategyHandler @@ -23,6 +27,11 @@ import java.util.Set; */ public class DailyTimeIntervalStrategyHandler implements TimingStrategyHandler { + /** + * 使用中国人的星期!!! + */ + private static final Set ALL_DAY = Sets.newHashSet(1, 2, 3, 4, 5, 6, 7); + @Override public TimeExpressionType supportType() { return TimeExpressionType.DAILY_TIME_INTERVAL; @@ -41,14 +50,90 @@ public class DailyTimeIntervalStrategyHandler implements TimingStrategyHandler { if (endTime.before(startTime)) { throw new IllegalArgumentException("endTime should after startTime!"); } + + if (StringUtils.isNotEmpty(ep.intervalUnit)) { + TimeUnit.valueOf(ep.intervalUnit); + } } @Override + @SneakyThrows public Long calculateNextTriggerTime(Long preTriggerTime, String timeExpression, Long startTime, Long endTime) { + DailyTimeIntervalExpress ep = JsonUtils.parseObject(timeExpression, DailyTimeIntervalExpress.class); + // 未开始状态下,用起点算调度时间 + if (startTime != null && startTime > System.currentTimeMillis() && preTriggerTime < startTime) { + return calculateInRangeTime(startTime, ep); + } + + // 间隔时间 + TimeUnit timeUnit = Optional.ofNullable(ep.intervalUnit).map(TimeUnit::valueOf).orElse(TimeUnit.SECONDS); + long interval = timeUnit.toMillis(ep.interval); + + Long ret = calculateInRangeTime(preTriggerTime + interval, ep); + if (ret == null || ret <= endTime) { + return ret; + } return null; } + /** + * 计算最近一次在范围中的时间 + * @param time 当前时间基准,可能直接返回该时间作为结果 + * @param ep 表达式 + * @return 最近一次在范围中的时间 + */ + static Long calculateInRangeTime(Long time, DailyTimeIntervalExpress ep) { + + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date(time)); + + int year = calendar.get(Calendar.YEAR); + // 月份 + 1,转为熟悉的 1~12 月 + int month = calendar.get(Calendar.MONTH) + 1; + int day = calendar.get(Calendar.DAY_OF_MONTH); + + // 判断是否符合"日"的执行条件 + int week = TimeUtils.calculateWeek(year, month, day); + Set targetDays = CollectionUtils.isEmpty(ep.daysOfWeek) ? ALL_DAY : ep.daysOfWeek; + // 未包含情况下,将时间改写为符合条件日的 00:00 分,重新开始 loop(这部分应该有性能更优的写法,不过这个调度模式应该很难触发瓶颈,先简单好用的实现) + if (!targetDays.contains(week)) { + simpleSetCalendar(calendar, 0, 0, 0); + Date tomorrowZero = DateUtils.addDays(calendar.getTime(), 1); + return calculateInRangeTime(tomorrowZero.getTime(), ep); + } + + // 范围的开始时间 + TimeOfDay rangeStartTime = TimeOfDay.from(ep.startTimeOfDay); + simpleSetCalendar(calendar, rangeStartTime.getHour(), rangeStartTime.getMinute(), rangeStartTime.getSecond()); + long todayStartTs = calendar.getTimeInMillis(); + + TimeOfDay rangeEndTime = TimeOfDay.from(ep.endTimeOfDay); + simpleSetCalendar(calendar, rangeEndTime.getHour(), rangeEndTime.getMinute(), rangeEndTime.getSecond()); + long todayEndTs = calendar.getTimeInMillis(); + + // 未开始 + if (time < todayStartTs) { + return todayStartTs; + } + + // 范围之间 + if (time <= todayEndTs) { + return time; + } + + // 已结束,重新计算第二天时间 + simpleSetCalendar(calendar, 0, 0, 0); + return calculateInRangeTime(DateUtils.addDays(calendar.getTime(), 1).getTime(), ep); + } + + private static void simpleSetCalendar(Calendar calendar, int h, int m, int s) { + calendar.set(Calendar.SECOND, s); + calendar.set(Calendar.MINUTE, m); + calendar.set(Calendar.HOUR_OF_DAY, h); + calendar.set(Calendar.MILLISECOND, 0); + } + @Data static class DailyTimeIntervalExpress implements Serializable { diff --git a/powerjob-server/powerjob-server-starter/src/test/java/tech/powerjob/server/common/utils/TimeUtilsTest.java b/powerjob-server/powerjob-server-starter/src/test/java/tech/powerjob/server/common/utils/TimeUtilsTest.java new file mode 100644 index 00000000..5603f8f5 --- /dev/null +++ b/powerjob-server/powerjob-server-starter/src/test/java/tech/powerjob/server/common/utils/TimeUtilsTest.java @@ -0,0 +1,53 @@ +package tech.powerjob.server.common.utils; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 时间工具 Test + * + * @author tjq + * @since 2020/5/19 + */ +@Slf4j +class TimeUtilsTest { + + @Test + void calculateWeek() { + int weekOf20230211 = TimeUtils.calculateWeek(2023, 2, 11); + log.info("[TimeUtilsTest] weekOf20230211: {}", weekOf20230211); + assert weekOf20230211 == 6; + + int weekOf20230212 = TimeUtils.calculateWeek(2023, 2, 12); + log.info("[TimeUtilsTest] weekOf20230211: {}", weekOf20230212); + assert weekOf20230212 == 7; + + int weekOf20230401 = TimeUtils.calculateWeek(2023, 4, 1); + log.info("[TimeUtilsTest] weekOf20230401: {}", weekOf20230401); + assert weekOf20230401 == 6; + + + // 618 + int weekOf20230618 = TimeUtils.calculateWeek(2023, 6, 18); + log.info("[TimeUtilsTest] weekOf20230618: {}", weekOf20230618); + assert weekOf20230618 == 7; + + // 双十一 + int weekOf20231111 = TimeUtils.calculateWeek(2023, 11, 11); + log.info("[TimeUtilsTest] weekOf20231111: {}", weekOf20231111); + assert weekOf20231111 == 6; + + // 我发现所有我熟悉的日子都是周末,神器啊.... + + int weekOf20230723 = TimeUtils.calculateWeek(2023, 7, 23); + log.info("[TimeUtilsTest] weekOf20230723: {}", weekOf20230723); + assert weekOf20230723 == 7; + + int weekOf20230218 = TimeUtils.calculateWeek(2023, 2, 18); + log.info("[TimeUtilsTest] weekOf20230218: {}", weekOf20230218); + assert weekOf20230218 == 6; + + } +} \ No newline at end of file diff --git a/powerjob-server/powerjob-server-starter/src/test/java/tech/powerjob/server/core/scheduler/auxiliary/impl/DailyTimeIntervalStrategyHandlerTest.java b/powerjob-server/powerjob-server-starter/src/test/java/tech/powerjob/server/core/scheduler/auxiliary/impl/DailyTimeIntervalStrategyHandlerTest.java new file mode 100644 index 00000000..df6a83c5 --- /dev/null +++ b/powerjob-server/powerjob-server-starter/src/test/java/tech/powerjob/server/core/scheduler/auxiliary/impl/DailyTimeIntervalStrategyHandlerTest.java @@ -0,0 +1,50 @@ +package tech.powerjob.server.core.scheduler.auxiliary.impl; + +import com.google.common.collect.Sets; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * DailyTimeIntervalStrategyHandler + * @author 550w + * @date 2027/02/15 + */ +@Slf4j +class DailyTimeIntervalStrategyHandlerTest { + + @Test + void calculateInRangeTime() { + + // 2023-02-11 22:46:34 + long ts = 1676126794874L; + // 验证范围中 + Long nowTs = DailyTimeIntervalStrategyHandler.calculateInRangeTime(ts, simpleBuild("21:00:00", "23:00:00", null)); + assert nowTs.equals(ts); + + // 时间未到(当天 23:00:00) + Long nextTs = DailyTimeIntervalStrategyHandler.calculateInRangeTime(ts, simpleBuild("23:00:00", "23:15:00", null)); + assert nextTs.equals(1676127600000L); + + // 时间超出(第二天 11:00:00) + Long nextDayStartTs = DailyTimeIntervalStrategyHandler.calculateInRangeTime(ts, simpleBuild("11:00:00", "12:15:00", null)); + assert nextDayStartTs.equals(1676170800000L); + + // 星期不满足(2023-02-15 11:00:00) + Long notTodayStartTs = DailyTimeIntervalStrategyHandler.calculateInRangeTime(ts, simpleBuild("11:00:00", "12:15:00", Sets.newHashSet(3))); + assert notTodayStartTs.equals(1676430000000L); + + } + + private static DailyTimeIntervalStrategyHandler.DailyTimeIntervalExpress simpleBuild(String startTimeOfDay, String endTimeOfDay, Set days) { + DailyTimeIntervalStrategyHandler.DailyTimeIntervalExpress ep = new DailyTimeIntervalStrategyHandler.DailyTimeIntervalExpress(); + ep.setInterval(30L); + ep.setStartTimeOfDay(startTimeOfDay); + ep.setEndTimeOfDay(endTimeOfDay); + ep.setDaysOfWeek(days); + return ep; + } +} \ No newline at end of file