Commit 19d36e0087a2c513160ac7b48c2b41c2460a5ca4

Authored by 王彪总
1 parent bddf5f9d

feat(garden): 优化养护计划时间段计算逻辑并调整接口权限控制

- 在 InspectionPlanSaveReqVO 和 MaintainPlanSaveReqVO 中新增 rateValue 字段长度限制(最大6位)
- 替换原有时间段计算方法 calculate 为 calculateNew,提升周期拆分准确性
- 重写 TimeIntervalFrequencyUtil 工具类中的 calculateNew 方法,支持更灵活的时间段划分
- 新增 convertToDaysNew 方法用于将不同周期单位转换为固定天数
- 注释掉 RoadStreetController 中所有接口的权限校验注解,临时放开访问控制
- 更新测试用例,使用新的 calculateNew 方法进行验证
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/inspectionplan/vo/InspectionPlanSaveReqVO.java
@@ -46,6 +46,7 @@ public class InspectionPlanSaveReqVO { @@ -46,6 +46,7 @@ public class InspectionPlanSaveReqVO {
46 46
47 @Schema(description = "频次值", requiredMode = Schema.RequiredMode.REQUIRED) 47 @Schema(description = "频次值", requiredMode = Schema.RequiredMode.REQUIRED)
48 @NotEmpty(message = "频次值不能为空") 48 @NotEmpty(message = "频次值不能为空")
  49 + @Size(max = 6)
49 private String rateValue; 50 private String rateValue;
50 51
51 @Schema(description = "周期ID: 0:次;1:日/次;2:周/次;3:月/次;4:季/次;5:半年/次;6:年/次", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 52 @Schema(description = "周期ID: 0:次;1:日/次;2:周/次;3:月/次;4:季/次;5:半年/次;6:年/次", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/maintainplan/vo/MaintainPlanSaveReqVO.java
@@ -46,6 +46,7 @@ public class MaintainPlanSaveReqVO { @@ -46,6 +46,7 @@ public class MaintainPlanSaveReqVO {
46 46
47 @Schema(description = "频次值", requiredMode = Schema.RequiredMode.REQUIRED) 47 @Schema(description = "频次值", requiredMode = Schema.RequiredMode.REQUIRED)
48 @NotEmpty(message = "频次值不能为空") 48 @NotEmpty(message = "频次值不能为空")
  49 + @Size(max = 6)
49 private String rateValue; 50 private String rateValue;
50 51
51 @Schema(description = "周期ID: 1:日/次;2:周/次;3:月/次;4:年/次;5:按次;6:季度;7:半年", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 52 @Schema(description = "周期ID: 1:日/次;2:周/次;3:月/次;4:年/次;5:按次;6:季度;7:半年", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/road/RoadStreetController.java
@@ -40,14 +40,14 @@ public class RoadStreetController { @@ -40,14 +40,14 @@ public class RoadStreetController {
40 40
41 @PostMapping("/create") 41 @PostMapping("/create")
42 @Operation(summary = "创建街道") 42 @Operation(summary = "创建街道")
43 - @PreAuthorize("@ss.hasPermission('garden:road-street:create')") 43 +// @PreAuthorize("@ss.hasPermission('garden:road-street:create')")
44 public CommonResult<Long> createRoadStreet(@Valid @RequestBody RoadStreetSaveReqVO createReqVO) { 44 public CommonResult<Long> createRoadStreet(@Valid @RequestBody RoadStreetSaveReqVO createReqVO) {
45 return success(roadStreetService.createRoadStreet(createReqVO)); 45 return success(roadStreetService.createRoadStreet(createReqVO));
46 } 46 }
47 47
48 @PutMapping("/update") 48 @PutMapping("/update")
49 @Operation(summary = "更新街道") 49 @Operation(summary = "更新街道")
50 - @PreAuthorize("@ss.hasPermission('garden:road-street:update')") 50 +// @PreAuthorize("@ss.hasPermission('garden:road-street:update')")
51 public CommonResult<Boolean> updateRoadStreet(@Valid @RequestBody RoadStreetSaveReqVO updateReqVO) { 51 public CommonResult<Boolean> updateRoadStreet(@Valid @RequestBody RoadStreetSaveReqVO updateReqVO) {
52 roadStreetService.updateRoadStreet(updateReqVO); 52 roadStreetService.updateRoadStreet(updateReqVO);
53 return success(true); 53 return success(true);
@@ -56,7 +56,7 @@ public class RoadStreetController { @@ -56,7 +56,7 @@ public class RoadStreetController {
56 @DeleteMapping("/delete") 56 @DeleteMapping("/delete")
57 @Operation(summary = "删除街道") 57 @Operation(summary = "删除街道")
58 @Parameter(name = "id", description = "编号", required = true) 58 @Parameter(name = "id", description = "编号", required = true)
59 - @PreAuthorize("@ss.hasPermission('garden:road-street:delete')") 59 +// @PreAuthorize("@ss.hasPermission('garden:road-street:delete')")
60 public CommonResult<Boolean> deleteRoadStreet(@RequestParam("id") Long id) { 60 public CommonResult<Boolean> deleteRoadStreet(@RequestParam("id") Long id) {
61 roadStreetService.deleteRoadStreet(id); 61 roadStreetService.deleteRoadStreet(id);
62 return success(true); 62 return success(true);
@@ -65,7 +65,7 @@ public class RoadStreetController { @@ -65,7 +65,7 @@ public class RoadStreetController {
65 @DeleteMapping("/delete-list") 65 @DeleteMapping("/delete-list")
66 @Parameter(name = "ids", description = "编号", required = true) 66 @Parameter(name = "ids", description = "编号", required = true)
67 @Operation(summary = "批量删除街道") 67 @Operation(summary = "批量删除街道")
68 - @PreAuthorize("@ss.hasPermission('garden:road-street:delete')") 68 +// @PreAuthorize("@ss.hasPermission('garden:road-street:delete')")
69 public CommonResult<Boolean> deleteRoadStreetList(@RequestParam("ids") List<Long> ids) { 69 public CommonResult<Boolean> deleteRoadStreetList(@RequestParam("ids") List<Long> ids) {
70 roadStreetService.deleteRoadStreetListByIds(ids); 70 roadStreetService.deleteRoadStreetListByIds(ids);
71 return success(true); 71 return success(true);
@@ -74,7 +74,7 @@ public class RoadStreetController { @@ -74,7 +74,7 @@ public class RoadStreetController {
74 @GetMapping("/get") 74 @GetMapping("/get")
75 @Operation(summary = "获得街道") 75 @Operation(summary = "获得街道")
76 @Parameter(name = "id", description = "编号", required = true, example = "1024") 76 @Parameter(name = "id", description = "编号", required = true, example = "1024")
77 - @PreAuthorize("@ss.hasPermission('garden:road-street:query')") 77 +// @PreAuthorize("@ss.hasPermission('garden:road-street:query')")
78 public CommonResult<RoadStreetRespVO> getRoadStreet(@RequestParam("id") Long id) { 78 public CommonResult<RoadStreetRespVO> getRoadStreet(@RequestParam("id") Long id) {
79 RoadStreetDO roadStreet = roadStreetService.getRoadStreet(id); 79 RoadStreetDO roadStreet = roadStreetService.getRoadStreet(id);
80 return success(BeanUtils.toBean(roadStreet, RoadStreetRespVO.class)); 80 return success(BeanUtils.toBean(roadStreet, RoadStreetRespVO.class));
@@ -83,7 +83,7 @@ public class RoadStreetController { @@ -83,7 +83,7 @@ public class RoadStreetController {
83 @GetMapping("/list") 83 @GetMapping("/list")
84 @Operation(summary = "获得街道列表") 84 @Operation(summary = "获得街道列表")
85 @Parameter(name = "busi_line", description = "业务线", required = true, example = "1024") 85 @Parameter(name = "busi_line", description = "业务线", required = true, example = "1024")
86 - @PreAuthorize("@ss.hasPermission('garden:road-street:query')") 86 +// @PreAuthorize("@ss.hasPermission('garden:road-street:query')")
87 public CommonResult<List<RoadStreetRespVO>> getRoadStreet(@RequestParam("busi_line") @NotBlank String busiLine) { 87 public CommonResult<List<RoadStreetRespVO>> getRoadStreet(@RequestParam("busi_line") @NotBlank String busiLine) {
88 List<RoadStreetDO> roadStreet = roadStreetService.getRoadStreetList(busiLine); 88 List<RoadStreetDO> roadStreet = roadStreetService.getRoadStreetList(busiLine);
89 return success(BeanUtils.toBean(roadStreet, RoadStreetRespVO.class)); 89 return success(BeanUtils.toBean(roadStreet, RoadStreetRespVO.class));
@@ -91,7 +91,7 @@ public class RoadStreetController { @@ -91,7 +91,7 @@ public class RoadStreetController {
91 91
92 @GetMapping("/page") 92 @GetMapping("/page")
93 @Operation(summary = "获得街道分页") 93 @Operation(summary = "获得街道分页")
94 - @PreAuthorize("@ss.hasPermission('garden:road-street:query')") 94 +// @PreAuthorize("@ss.hasPermission('garden:road-street:query')")
95 public CommonResult<PageResult<RoadStreetRespVO>> getRoadStreetPage(@Valid RoadStreetPageReqVO pageReqVO) { 95 public CommonResult<PageResult<RoadStreetRespVO>> getRoadStreetPage(@Valid RoadStreetPageReqVO pageReqVO) {
96 PageResult<RoadStreetDO> pageResult = roadStreetService.getRoadStreetPage(pageReqVO); 96 PageResult<RoadStreetDO> pageResult = roadStreetService.getRoadStreetPage(pageReqVO);
97 return success(BeanUtils.toBean(pageResult, RoadStreetRespVO.class)); 97 return success(BeanUtils.toBean(pageResult, RoadStreetRespVO.class));
@@ -99,7 +99,7 @@ public class RoadStreetController { @@ -99,7 +99,7 @@ public class RoadStreetController {
99 99
100 @GetMapping("/export-excel") 100 @GetMapping("/export-excel")
101 @Operation(summary = "导出街道 Excel") 101 @Operation(summary = "导出街道 Excel")
102 - @PreAuthorize("@ss.hasPermission('garden:road-street:export')") 102 +// @PreAuthorize("@ss.hasPermission('garden:road-street:export')")
103 @ApiAccessLog(operateType = EXPORT) 103 @ApiAccessLog(operateType = EXPORT)
104 public void exportRoadStreetExcel(@Valid RoadStreetPageReqVO pageReqVO, 104 public void exportRoadStreetExcel(@Valid RoadStreetPageReqVO pageReqVO,
105 HttpServletResponse response) throws IOException { 105 HttpServletResponse response) throws IOException {
@@ -110,4 +110,4 @@ public class RoadStreetController { @@ -110,4 +110,4 @@ public class RoadStreetController {
110 BeanUtils.toBean(list, RoadStreetRespVO.class)); 110 BeanUtils.toBean(list, RoadStreetRespVO.class));
111 } 111 }
112 112
113 -}  
114 \ No newline at end of file 113 \ No newline at end of file
  114 +}
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/maintainplan/MaintainPlanServiceImpl.java
@@ -124,7 +124,7 @@ public class MaintainPlanServiceImpl implements MaintainPlanService { @@ -124,7 +124,7 @@ public class MaintainPlanServiceImpl implements MaintainPlanService {
124 calculate.add(periodResult); 124 calculate.add(periodResult);
125 planNum = Integer.parseInt(createReqVO.getRateValue()); 125 planNum = Integer.parseInt(createReqVO.getRateValue());
126 }else{ 126 }else{
127 - calculate= TimeIntervalFrequencyUtil.calculate(createReqVO.getBeginTime().toString(), createReqVO.getEndTime().toString(), rateValue, createReqVO.getCycleId()); 127 + calculate= TimeIntervalFrequencyUtil.calculateNew(createReqVO.getBeginTime().toString(), createReqVO.getEndTime().toString(), rateValue, createReqVO.getCycleId());
128 if (CollectionUtils.isEmpty(calculate)) { 128 if (CollectionUtils.isEmpty(calculate)) {
129 log.warn("获取两个时间之间间隔天数错误"); 129 log.warn("获取两个时间之间间隔天数错误");
130 return CommonResult.error(INSPECTION_CYCLE_ERROR); 130 return CommonResult.error(INSPECTION_CYCLE_ERROR);
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/util/timeinterval/TimeIntervalFrequencyUtil.java
@@ -2,6 +2,7 @@ package com.zteits.urbanops.module.garden.util.timeinterval; @@ -2,6 +2,7 @@ package com.zteits.urbanops.module.garden.util.timeinterval;
2 2
3 import java.time.LocalDate; 3 import java.time.LocalDate;
4 import java.time.format.DateTimeFormatter; 4 import java.time.format.DateTimeFormatter;
  5 +import java.time.temporal.ChronoUnit;
5 import java.util.ArrayList; 6 import java.util.ArrayList;
6 import java.util.Collections; 7 import java.util.Collections;
7 import java.util.List; 8 import java.util.List;
@@ -38,6 +39,82 @@ public class TimeIntervalFrequencyUtil { @@ -38,6 +39,82 @@ public class TimeIntervalFrequencyUtil {
38 * @param cycleId 1:日/次;2:周/次;3:月/次;4:年/次;5:按次;6:季度;7:半年 39 * @param cycleId 1:日/次;2:周/次;3:月/次;4:年/次;5:按次;6:季度;7:半年
39 * @return 划分结果列表 40 * @return 划分结果列表
40 */ 41 */
  42 + /**
  43 + * 计算时间段划分结果(基于固定天数换算)
  44 + *
  45 + * @param startStr 开始日期 (yyyy-MM-dd)
  46 + * @param endStr 结束日期 (yyyy-MM-dd)
  47 + * @param freq 频次值,格式 "x/y",其中 x 为每周期次数,y 为周期数量
  48 + * @param cycleId 周期类型:1:日/次;2:周/次;3:月/次;4:年/次;5:按次;6:季度;7:半年
  49 + * @return 时间段划分结果列表,包含每个时间段的开始日期、结束日期和执行次数
  50 + */
  51 + public static List<PeriodResult> calculateNew(String startStr, String endStr, String freq, Integer cycleId) {
  52 + // 解析日期参数
  53 + LocalDate startDate = LocalDate.parse(startStr, FORMATTER);
  54 + LocalDate endDate = LocalDate.parse(endStr, FORMATTER);
  55 +
  56 + // 边界条件:开始日期晚于结束日期时返回空列表
  57 + if (startDate.isAfter(endDate)) {
  58 + return Collections.emptyList();
  59 + }
  60 +
  61 + // 解析频次参数 (格式: "x/y")
  62 + String[] freqParts = freq.split("/", 2);
  63 + if (freqParts.length != 2) {
  64 + throw new IllegalArgumentException(String.format("频次值格式错误,应为 'x/y': %s", freq));
  65 + }
  66 + int timesPerPeriod = Integer.parseInt(freqParts[0]); // 每周期执行次数
  67 + int periodCount = Integer.parseInt(freqParts[1]); // 周期数量
  68 +
  69 + // 计算周期相关天数
  70 + long daysPerPeriod = convertToDaysNew(periodCount, cycleId); // 单个周期的天数
  71 + long totalPeriodDays = daysPerPeriod * periodCount; // 总周期天数
  72 +
  73 + List<PeriodResult> resultList = new ArrayList<>();
  74 + LocalDate segmentStart = startDate;
  75 +
  76 + // 循环划分时间段
  77 + while (!segmentStart.isAfter(endDate)) {
  78 + // 计算当前时间段的理论结束日期(segmentStart + totalPeriodDays - 1)
  79 + LocalDate segmentEnd = safePlusDays(segmentStart, totalPeriodDays - 1);
  80 +
  81 + // 如果理论结束日期超过总结束日期,则截断
  82 + if (segmentEnd.isAfter(endDate)) {
  83 + segmentEnd = endDate;
  84 + }
  85 +
  86 + // 计算当前时间段的实际天数(包含开始和结束日期)
  87 + long daysInSegment = ChronoUnit.DAYS.between(segmentStart, segmentEnd) + 1;
  88 +
  89 + // 计算当前时间段的执行次数:(实际天数/周期天数) * 每周期次数,向上取整
  90 + double proportion = (double) daysInSegment / daysPerPeriod;
  91 + int executionCount = (int) Math.ceil(proportion * timesPerPeriod);
  92 +
  93 + // 添加当前时间段结果
  94 + resultList.add(new PeriodResult(segmentStart, segmentEnd, executionCount));
  95 +
  96 + // 计算下一个时间段的开始日期(当前结束日期的次日)
  97 + LocalDate nextSegmentStart = segmentEnd.plusDays(1);
  98 +
  99 + // 如果下一个开始日期超过总结束日期,则退出循环
  100 + if (nextSegmentStart.isAfter(endDate)) {
  101 + break;
  102 + }
  103 +
  104 + segmentStart = nextSegmentStart;
  105 + }
  106 +
  107 + return resultList;
  108 + }
  109 + /**
  110 + * 计算时间段划分结果(基于固定天数换算)
  111 + *
  112 + * @param startStr 开始日期 (yyyy-MM-dd)
  113 + * @param endStr 结束日期 (yyyy-MM-dd)
  114 + * @param freq 频次值,格式 "x/y"
  115 + * @param cycleId 1:日/次;2:周/次;3:月/次;4:年/次;5:按次;6:季度;7:半年
  116 + * @return 划分结果列表
  117 + */
41 public static List<PeriodResult> calculate(String startStr, String endStr, String freq, Integer cycleId) { 118 public static List<PeriodResult> calculate(String startStr, String endStr, String freq, Integer cycleId) {
42 LocalDate start = LocalDate.parse(startStr, FORMATTER); 119 LocalDate start = LocalDate.parse(startStr, FORMATTER);
43 LocalDate end = LocalDate.parse(endStr, FORMATTER); 120 LocalDate end = LocalDate.parse(endStr, FORMATTER);
@@ -77,7 +154,36 @@ public class TimeIntervalFrequencyUtil { @@ -77,7 +154,36 @@ public class TimeIntervalFrequencyUtil {
77 154
78 return results; 155 return results;
79 } 156 }
80 - 157 + /**
  158 + * 将 y 个单位转换为固定天数
  159 + * TIME(0, "次"),
  160 + * DAY(1, "次/日"),
  161 + * WEEK(2, "次/周"),
  162 + * MONTH(3, "次/月"),
  163 + * QUARTER(4, "次/季"),
  164 + * SEMIANNUAL(5, "次/半年"),
  165 + * YEAR(6, "次/年");
  166 + */
  167 + private static long convertToDaysNew(int y, Integer cycleId) {
  168 + switch (cycleId) {
  169 + case 1: // DAY
  170 + return 1;
  171 + case 2: // WEEK
  172 + return (long) 7;
  173 + case 3: // MONTH
  174 + return (long) 30;
  175 + case 6: // YEAR
  176 + return (long) 365;
  177 + case 0: // TIME
  178 + return 9999; // 按次:将整个时间段视为1个周期
  179 + case 4: // QUARTER
  180 + return (long) 90; // 1季度=3个月=90天
  181 + case 5: // SEMIANNUAL
  182 + return (long) 180; // 半年=6个月=180天
  183 + default:
  184 + throw new IllegalArgumentException("无效的周期ID: " + cycleId);
  185 + }
  186 + }
81 /** 187 /**
82 * 将 y 个单位转换为固定天数 188 * 将 y 个单位转换为固定天数
83 * TIME(0, "次"), 189 * TIME(0, "次"),
@@ -123,52 +229,56 @@ public class TimeIntervalFrequencyUtil { @@ -123,52 +229,56 @@ public class TimeIntervalFrequencyUtil {
123 229
124 // ====================== 测试用例 ====================== 230 // ====================== 测试用例 ======================
125 public static void main(String[] args) { 231 public static void main(String[] args) {
126 - //1:日/次;2:周/次;3:月/次;4:年/次  
127 - System.out.println("=== 示例1:每2天3次(即:3/2),按日 ===2025-12-01 2025-12-31");  
128 - List<PeriodResult> r1 = calculate("2025-12-01", "2025-12-31", "3/2", 1);  
129 - r1.forEach(System.out::println);  
130 - // 输出:  
131 - // 2025-01-01 2025-01-03 1次  
132 - // 2025-01-04 2025-01-06 1次  
133 - // 2025-01-07 2025-01-07 1次  
134 - System.out.println("\n=== 示例2:每2周3次(即:3/2)(14天),按周 ===2025-12-01 2025-12-10");  
135 - List<PeriodResult> r2 = calculate("2025-12-01", "2025-12-10", "3/2", 2); 232 +
  233 + List<PeriodResult> r2 = calculateNew("2026-01-01", "2026-02-15", "7/1", 2);
136 r2.forEach(System.out::println); 234 r2.forEach(System.out::println);
137 - // 每2周 = 14天  
138 - // 2025-01-01 ~ 2025-01-14  
139 - // 2025-01-15 ~ 2025-01-28  
140 - // 2025-01-29 ~ 2025-02-08(剩余11天)  
141 - System.out.println("\n=== 示例3:每2个月4次(即:4/2)(30天),按月 === 2025-09-15 2025-12-31");  
142 - List<PeriodResult> r3 = calculate("2025-09-15", "2025-12-31", "4/2", 3);  
143 - r3.forEach(System.out::println);  
144 - // 每1月 = 30天  
145 - // 2025-01-01 ~ 2025-01-30  
146 - // 2025-01-31 ~ 2025-02-28(30天?→ 实际是29天,但按30天算)  
147 - // 2025-03-01 ~ 2025-03-15(不足)  
148 -  
149 - System.out.println("\n=== 示例4:每2年1次(即:1/2)(730天),按年 ===2025-01-01 2030-01-01");  
150 - List<PeriodResult> r4 = calculate("2025-01-01", "2030-01-01", "1/2", 4);  
151 - r4.forEach(System.out::println);  
152 - // 每2年 = 730天  
153 - // 2025-01-01 ~ 2025-12-26(+729d)  
154 - // 2025-12-27 ~ 2027-12-21  
155 - // 2027-12-22 ~ 2029-12-16  
156 - // 2029-12-17 ~ 2030-01-01(最后一段)  
157 - System.out.println("\n=== 示例5:每1年4次(即:4/1),按年(测试)=== 2023-09-15 2025-01-31");  
158 - List<PeriodResult> r5 = calculate("2023-09-15", "2025-01-31", "4/1", 4);  
159 - r5.forEach(System.out::println);  
160 -  
161 - // 新增示例:按次、季度、半年  
162 - System.out.println("\n=== 示例6:按次单位,整个时间段算1个周期 === 2025-01-01 2025-12-31");  
163 - List<PeriodResult> r6 = calculate("2025-01-01", "2025-12-31", "2/1", 5);  
164 - r6.forEach(System.out::println);  
165 -  
166 - System.out.println("\n=== 示例7:每1季度2次(即:2/1),按季度(90天)=== 2025-01-01 2025-12-31");  
167 - List<PeriodResult> r7 = calculate("2025-01-01", "2025-12-31", "2/1", 6);  
168 - r7.forEach(System.out::println);  
169 -  
170 - System.out.println("\n=== 示例8:每1半年3次(即:3/1),按半年(180天)=== 2025-01-01 2025-12-31");  
171 - List<PeriodResult> r8 = calculate("2025-01-01", "2025-12-31", "3/1", 7);  
172 - r8.forEach(System.out::println); 235 +
  236 +// //1:日/次;2:周/次;3:月/次;4:年/次
  237 +// System.out.println("=== 示例1:每2天3次(即:3/2),按日 ===2025-12-01 2025-12-31");
  238 +// List<PeriodResult> r1 = calculate("2025-12-01", "2025-12-31", "3/2", 1);
  239 +// r1.forEach(System.out::println);
  240 +// // 输出:
  241 +// // 2025-01-01 2025-01-03 1次
  242 +// // 2025-01-04 2025-01-06 1次
  243 +// // 2025-01-07 2025-01-07 1次
  244 +// System.out.println("\n=== 示例2:每2周3次(即:3/2)(14天),按周 ===2025-12-01 2025-12-10");
  245 +// List<PeriodResult> r2 = calculate("2025-12-01", "2025-12-10", "3/2", 2);
  246 +// r2.forEach(System.out::println);
  247 +// // 每2周 = 14天
  248 +// // 2025-01-01 ~ 2025-01-14
  249 +// // 2025-01-15 ~ 2025-01-28
  250 +// // 2025-01-29 ~ 2025-02-08(剩余11天)
  251 +// System.out.println("\n=== 示例3:每2个月4次(即:4/2)(30天),按月 === 2025-09-15 2025-12-31");
  252 +// List<PeriodResult> r3 = calculate("2025-09-15", "2025-12-31", "4/2", 3);
  253 +// r3.forEach(System.out::println);
  254 +// // 每1月 = 30天
  255 +// // 2025-01-01 ~ 2025-01-30
  256 +// // 2025-01-31 ~ 2025-02-28(30天?→ 实际是29天,但按30天算)
  257 +// // 2025-03-01 ~ 2025-03-15(不足)
  258 +//
  259 +// System.out.println("\n=== 示例4:每2年1次(即:1/2)(730天),按年 ===2025-01-01 2030-01-01");
  260 +// List<PeriodResult> r4 = calculate("2025-01-01", "2030-01-01", "1/2", 4);
  261 +// r4.forEach(System.out::println);
  262 +// // 每2年 = 730天
  263 +// // 2025-01-01 ~ 2025-12-26(+729d)
  264 +// // 2025-12-27 ~ 2027-12-21
  265 +// // 2027-12-22 ~ 2029-12-16
  266 +// // 2029-12-17 ~ 2030-01-01(最后一段)
  267 +// System.out.println("\n=== 示例5:每1年4次(即:4/1),按年(测试)=== 2023-09-15 2025-01-31");
  268 +// List<PeriodResult> r5 = calculate("2023-09-15", "2025-01-31", "4/1", 4);
  269 +// r5.forEach(System.out::println);
  270 +//
  271 +// // 新增示例:按次、季度、半年
  272 +// System.out.println("\n=== 示例6:按次单位,整个时间段算1个周期 === 2025-01-01 2025-12-31");
  273 +// List<PeriodResult> r6 = calculate("2025-01-01", "2025-12-31", "2/1", 5);
  274 +// r6.forEach(System.out::println);
  275 +//
  276 +// System.out.println("\n=== 示例7:每1季度2次(即:2/1),按季度(90天)=== 2025-01-01 2025-12-31");
  277 +// List<PeriodResult> r7 = calculate("2025-01-01", "2025-12-31", "2/1", 6);
  278 +// r7.forEach(System.out::println);
  279 +//
  280 +// System.out.println("\n=== 示例8:每1半年3次(即:3/1),按半年(180天)=== 2025-01-01 2025-12-31");
  281 +// List<PeriodResult> r8 = calculate("2025-01-01", "2025-12-31", "3/1", 7);
  282 +// r8.forEach(System.out::println);
173 } 283 }
174 } 284 }