package jnpf.util; import jnpf.limsEntity.LimsSxtQuyangJihuaEntity; import lombok.extern.slf4j.Slf4j; import java.time.LocalDate; import java.time.temporal.ChronoUnit; /** * 取样计划周期判断工具类 * */ @Slf4j public class LimsSxtJihuaUtil { /** * 判断计划在指定日期是否应该执行 * * @param plan 计划实体 * @param date 目标日期 * @return true-需要执行,false-不需要执行 */ public static boolean shouldExecuteOnDate(LimsSxtQuyangJihuaEntity plan, LocalDate date) { if (plan == null || date == null) { log.debug("计划或日期为空"); return false; } // 1. 检查删除标志 if (isDeleted(plan)) { log.debug("计划已删除,fId={}", plan.getId()); return false; } // 2. 检查流程状态(2=已通过生效,根据业务调整) if (!isApproved(plan)) { log.debug("计划流程状态未生效,fId={}, state={}", plan.getId(), plan.getFFlowState()); return false; } // 3. 检查是否在计划时间范围内 if (!isInDateRange(plan, date)) { log.debug("计划不在时间范围内,fId={}, date={}", plan.getId(), date); return false; } // 4. 根据周期类型判断 String cycleType = plan.getJihuaZhouqiLeixing(); if (cycleType == null || cycleType.isEmpty()) { log.debug("计划周期类型为空,fId={}", plan.getId()); return false; } int interval = parseInterval(plan.getJihuaZhouqiJiange()); if (interval < 0) { log.debug("计划周期间隔无效,fId={}, interval={}", plan.getId(), interval); return false; } LocalDate startDate = plan.getKaishiShijian().toInstant() .atZone(java.time.ZoneId.systemDefault()).toLocalDate(); return matchCycle(cycleType, startDate, date, interval); } // ==================== 前置检查方法 ==================== /** * 检查是否已删除:f_delete_mark = 1 表示已删除 */ private static boolean isDeleted(LimsSxtQuyangJihuaEntity plan) { Integer deleteMark = plan.getFDeleteMark(); return deleteMark != null && deleteMark == 1; } /** * 检查流程状态是否已通过(2=已通过) */ private static boolean isApproved(LimsSxtQuyangJihuaEntity plan) { Integer flowState = plan.getFFlowState(); return flowState != null && flowState == 2; } /** * 检查是否在计划时间范围内(包含起止日期) */ private static boolean isInDateRange(LimsSxtQuyangJihuaEntity plan, LocalDate date) { LocalDate startDate = plan.getKaishiShijian().toInstant() .atZone(java.time.ZoneId.systemDefault()).toLocalDate(); LocalDate endDate = plan.getJieshuShijian().toInstant() .atZone(java.time.ZoneId.systemDefault()).toLocalDate(); return !date.isBefore(startDate) && !date.isAfter(endDate); } /** * 解析周期间隔 */ private static int parseInterval(String intervalStr) { if (intervalStr == null || intervalStr.trim().isEmpty()) { return -1; } try { return Integer.parseInt(intervalStr.trim()); } catch (NumberFormatException e) { log.error("解析周期间隔失败: {}", intervalStr, e); return -1; } } // ==================== 周期匹配方法 ==================== /** * 根据周期类型匹配 */ private static boolean matchCycle(String cycleType, LocalDate startDate, LocalDate targetDate, int interval) { long daysDiff = ChronoUnit.DAYS.between(startDate, targetDate); switch (cycleType.toLowerCase()) { case "daily": return matchDaily(daysDiff, interval); case "weekly": return matchWeekly(startDate, targetDate, daysDiff, interval); case "monthly": return matchMonthly(startDate, targetDate, interval); case "quarterly": return matchQuarterly(startDate, targetDate, interval); case "yearly": return matchYearly(startDate, targetDate, interval); default: log.warn("未知的周期类型: {}", cycleType); return false; } } // ==================== 各周期具体匹配逻辑 ==================== /** * 每日周期匹配 * 逻辑:距开始日期的天数差 % (间隔 + 1) = 0 * 示例:开始7/14,间隔1 → 7/14, 7/16, 7/18... */ private static boolean matchDaily(long daysDiff, int interval) { return daysDiff % executionStep(interval) == 0; } /** * 每周周期匹配 * 逻辑:星期几相同 + 周数差 % (间隔 + 1) = 0 * 示例:开始7/14(周二),间隔1 → 隔周周二执行 */ private static boolean matchWeekly(LocalDate startDate, LocalDate targetDate, long daysDiff, int interval) { // 星期几相同(LocalDate.DayOfWeek: 周一=1, 周日=7) if (targetDate.getDayOfWeek() != startDate.getDayOfWeek()) { return false; } long weekDiff = daysDiff / 7; return weekDiff % executionStep(interval) == 0; } /** * 每月周期匹配(含月末边界处理) * 逻辑:日期匹配 + 月份差 % (间隔 + 1) = 0 * 示例:开始1/31,间隔1 → 1/31, 3/31, 5/31... */ private static boolean matchMonthly(LocalDate startDate, LocalDate targetDate, int interval) { // 日期匹配(含月末边界) if (!isSameDayOfMonthWithBoundary(startDate, targetDate)) { return false; } long monthDiff = ChronoUnit.MONTHS.between( startDate.withDayOfMonth(1), targetDate.withDayOfMonth(1) ); return monthDiff % executionStep(interval) == 0; } /** * 每季周期匹配(含月末边界处理) * 逻辑:同月同日 + 季度差 % (间隔 + 1) = 0 * 示例:开始1/31,间隔1 → 1/31, 7/31... */ private static boolean matchQuarterly(LocalDate startDate, LocalDate targetDate, int interval) { // 日期匹配(含月末边界) if (!isSameDayOfMonthWithBoundary(startDate, targetDate)) { return false; } long monthDiff = ChronoUnit.MONTHS.between( startDate.withDayOfMonth(1), targetDate.withDayOfMonth(1) ); if (monthDiff % 3 != 0) { return false; } long quarterDiff = monthDiff / 3; return quarterDiff % executionStep(interval) == 0; } /** * 每年周期匹配(含2月29日处理) * 逻辑:月日相同 + 年份差 % (间隔 + 1) = 0 * 示例:开始2024-02-29,间隔1 → 2024-02-29, 2026-02-28... */ private static boolean matchYearly(LocalDate startDate, LocalDate targetDate, int interval) { // 月日匹配(含2月29日处理) if (!isSameMonthAndDayWithLeapYear(startDate, targetDate)) { return false; } long yearDiff = targetDate.getYear() - startDate.getYear(); return yearDiff % executionStep(interval) == 0; } private static long executionStep(int interval) { return (long) interval + 1; } // ==================== 边界处理方法 ==================== /** * 判断日期是否匹配(含月末边界处理) *
* 规则: * - 如果开始日期是当月最后一天 → 目标日期也必须是当月最后一天(自动适配不同月份的天数差异) * - 否则 → 日期必须相同 *
* 示例: * - 开始 1/31 → 匹配 1/31, 2/28, 3/31, 4/30, 5/31... * - 开始 2/28 → 匹配 1/28, 2/28, 3/28, 4/28... * - 开始 2/29 → 匹配 2/29(闰年)或 2/28(非闰年) */ private static boolean isSameDayOfMonthWithBoundary(LocalDate startDate, LocalDate targetDate) { // 如果开始日期是当月最后一天 if (startDate.getDayOfMonth() == startDate.lengthOfMonth()) { // 目标日期也必须是当月最后一天 return targetDate.getDayOfMonth() == targetDate.lengthOfMonth(); } // 否则日期必须相同 return startDate.getDayOfMonth() == targetDate.getDayOfMonth(); } /** * 判断月日是否匹配(含2月29日处理) *
* 规则: * - 开始日期是 2/29 → 目标日期必须是 2/29(闰年)或 2/28(非闰年) * - 否则 → 月日完全相同 *
* 示例: * - 开始 2024-02-29 → 匹配 2024-02-29, 2025-02-28, 2026-02-28, 2028-02-29 * - 开始 2024-03-15 → 匹配 2025-03-15, 2026-03-15... */ private static boolean isSameMonthAndDayWithLeapYear(LocalDate startDate, LocalDate targetDate) { // 如果开始日期是2月29日 if (startDate.getMonthValue() == 2 && startDate.getDayOfMonth() == 29) { // 目标日期必须是2月 if (targetDate.getMonthValue() != 2) { return false; } // 闰年必须是29日,非闰年可以是28日 if (targetDate.isLeapYear()) { return targetDate.getDayOfMonth() == 29; } else { return targetDate.getDayOfMonth() == 28; } } // 普通日期:月日完全相同 return startDate.getMonthValue() == targetDate.getMonthValue() && startDate.getDayOfMonth() == targetDate.getDayOfMonth(); } }