如何按月份拆分日期并计算多项目月度支出总和?技术实现问询
嘿,这俩问题其实是一套逻辑——核心就是把跨月的时间区间拆成单个月份的片段,再分别计算每个月的对应数值。我来给你一步步捋清楚怎么实现,尤其是你要的月度支出计算函数:
核心思路:拆分跨月区间到单个月份
不管是拆分日期到各月做每日总和,还是计算月度支出,第一步都是把一个横跨多个月份的起止日期,拆成每个月份内的子区间。比如一个项目从2024-03-25到2024-05-10,就得拆成3.25-3.31、4.1-4.30、5.1-5.10这三个子区间,再针对每个子区间计算对应的数据。
具体实现步骤(针对月度支出计算)
咱们分三步来写这个函数:
1. 先写个辅助方法:拆分日期区间为月度子区间
这个方法负责把任意的startDate和endDate拆成每个月的起止日期片段,返回每个月份对应的子区间。用Java 8+的java.time包来处理日期会比旧的Calendar方便很多,示例代码如下:
import java.time.LocalDate; import java.time.Month; import java.util.ArrayList; import java.util.List; import java.util.AbstractMap.SimpleEntry; private List<SimpleEntry<Month, LocalDate[]>> splitDateRangeIntoMonths(LocalDate start, LocalDate end) { List<SimpleEntry<Month, LocalDate[]>> monthIntervals = new ArrayList<>(); LocalDate currentStart = start; while (!currentStart.isAfter(end)) { // 获取当前月份的最后一天 LocalDate currentEnd = currentStart.withDayOfMonth(currentStart.lengthOfMonth()); // 如果当前月份最后一天超过了项目结束日期,就用项目结束日期作为子区间的终点 if (currentEnd.isAfter(end)) { currentEnd = end; } // 把当前月份和对应的子区间存起来 monthIntervals.add(new SimpleEntry<>(currentStart.getMonth(), new LocalDate[]{currentStart, currentEnd})); // 跳到下一个月的第一天,继续循环 currentStart = currentEnd.plusDays(1); } return monthIntervals; }
2. 遍历每个项目,计算单个项目的月度支出
对每个项目,先拆分它的时间区间,然后针对每个月份的子区间:
- 调用你现有的工具类计算该子区间内的工作日天数(注意要让
getNumWorkingDays支持任意的起止日期,而不只是项目的全局起止) - 遍历每个员工的每日承诺时长,计算该员工在这个月的支出:
工作日天数 × 每日时长 × 员工时薪(这里假设你的Employee类有getHourlyRate()方法来获取时薪,如果没有的话,你得补充这个属性或者调整计算逻辑) - 把每个月份的支出累加到全局的预算Map里
3. 整合所有项目的月度支出
初始化一个Map(如果需要按月份顺序返回,就用TreeMap;不需要的话用HashMap就行),然后把所有项目的月度支出都加进去。
完整的计算函数代码
把上面的逻辑整合起来,就是你要的函数了:
import java.time.Month; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.AbstractMap.SimpleEntry; public Map<Month, Integer> calculateMonthlyBudget(List<Project> projects) { // 初始化月度预算Map,用HashMap默认无序,要有序就换TreeMap Map<Month, Integer> monthlyBudget = new HashMap<>(); for (Project project : projects) { // 把项目的起止日期拆成月度子区间 List<SimpleEntry<Month, LocalDate[]>> monthIntervals = splitDateRangeIntoMonths( project.getStartDate(), // 假设Project有getStartDate()返回LocalDate project.getEndDate() // 假设Project有getEndDate()返回LocalDate ); for (SimpleEntry<Month, LocalDate[]> interval : monthIntervals) { Month currentMonth = interval.getKey(); LocalDate[] monthDates = interval.getValue(); LocalDate monthStart = monthDates[0]; LocalDate monthEnd = monthDates[1]; // 计算这个子区间的工作日天数 int workingDaysInMonth = utils.Calendar.getNumWorkingDays(monthStart, monthEnd); if (workingDaysInMonth <= 0) { continue; // 这个月没有工作日,跳过计算 } // 遍历每个员工,计算他们在这个月的支出 for (Map.Entry<Employee, Integer> empEntry : project.getCommittedHoursPerDay().entrySet()) { Employee employee = empEntry.getKey(); int dailyHours = empEntry.getValue(); int hourlyRate = employee.getHourlyRate(); // 员工的时薪 // 计算该员工在当前月份的支出 int empMonthlyExpense = workingDaysInMonth * dailyHours * hourlyRate; // 累加到月度预算里,没有的话就从0开始加 monthlyBudget.put( currentMonth, monthlyBudget.getOrDefault(currentMonth, 0) + empMonthlyExpense ); } } } return monthlyBudget; }
一些需要注意的细节
- 如果你的
Project类里的startDate和endDate是用旧的java.util.Date,记得先转换成LocalDate,比如:LocalDate.from(date.toInstant().atZone(ZoneId.systemDefault())) - 确保
utils.Calendar.getNumWorkingDays能接受LocalDate类型的参数,如果原来的方法是用Calendar,可以重载一个新的方法,或者把LocalDate转换成Calendar再调用 - 如果需要返回的月度预算按月份顺序排列(1月到12月),把
HashMap换成TreeMap就可以,TreeMap会自动按Month枚举的顺序排序 - 边界情况已经处理:项目起止在同一个月、项目跨多个月、某个月没有工作日等
内容的提问来源于stack exchange,提问作者noobianov
相关产品推荐
相关产品推荐

