import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class WorkdayCalculator {
    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    
    // 使用Set提高查询效率
    private final Set<LocalDate> holidays;
    private final Set<LocalDate> workWeekends;
    
    // 缓存已计算的日期结果
    private final Map<LocalDate, Boolean> workdayCache = new ConcurrentHashMap<>();
    
    public WorkdayCalculator(List<String> holidayList, List<String> workWeekendList) {
        this.holidays = parseDateStrings(holidayList);
        this.workWeekends = parseDateStrings(workWeekendList);
    }
    
    private Set<LocalDate> parseDateStrings(List<String> dateStrings) {
        return dateStrings.stream()
                .map(str -> LocalDate.parse(str, DATE_FORMAT))
                .collect(Collectors.toSet());
    }
    
    /**
     * 计算n个工作日后的日期
     * @param startDate 开始日期
     * @param workdays 工作日数量
     * @return 目标日期
     */
    public LocalDate calculateWorkdayAfter(LocalDate startDate, int workdays) {
        LocalDate current = startDate;
        int remaining = workdays;
        
        while (remaining > 0) {
            current = current.plusDays(1);
            if (isWorkday(current)) {
                remaining--;
            }
        }
        return current;
    }
    
    /**
     * 计算两个日期之间的工作日和节假日天数
     * @param start 开始日期
     * @param end 结束日期
     * @return 包含工作日和节假日天数的Map
     */
    public Map<String, Long> countWorkdaysAndHolidays(LocalDate start, LocalDate end) {
        long workdays = 0;
        long holidays = 0;
        
        LocalDate current = start;
        while (!current.isAfter(end)) {
            if (isWorkday(current)) {
                workdays++;
            } else {
                holidays++;
            }
            current = current.plusDays(1);
        }
        
        return Map.of(
            "workdays", workdays,
            "holidays", holidays
        );
    }
    
    /**
     * 判断是否是工作日
     */
    public boolean isWorkday(LocalDate date) {
        return workdayCache.computeIfAbsent(date, d -> {
            // 优先检查是否是调休的工作日
            if (workWeekends.contains(d)) {
                return true;
            }
            // 检查是否是节假日
            if (holidays.contains(d)) {
                return false;
            }
            // 检查是否是周末
            DayOfWeek dayOfWeek = d.getDayOfWeek();
            return dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY;
        });
    }
    
    /**
     * 计算两个日期时间之间的时间差
     */
    public String formatDurationBetween(LocalDateTime start, LocalDateTime end) {
        Duration duration = Duration.between(start, end);
        long hours = duration.toHours();
        long minutes = duration.minusHours(hours).toMinutes();
        return String.format("%d小时%d分钟", hours, minutes);
    }
    
    // 转换方法保持兼容性
    public static LocalDate toLocalDate(Date date) {
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    }
    
    public static LocalDateTime toLocalDateTime(Date date) {
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    }
    
    public static Date toDate(LocalDate localDate) {
        return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
    }
}

这个WorkdayCalculator类是一个工作日计算工具,用于处理与工作日相关的各种计算,包括判断某天是否是工作日、计算工作日后的日期、统计两个日期之间的工作日和节假日天数等。

主要功能方法

calculateWorkdayAfter - 计算n个工作日后的日期

public LocalDate calculateWorkdayAfter(LocalDate startDate, int workdays) {
    LocalDate current = startDate;
    int remaining = workdays;
    
    while (remaining > 0) {
        current = current.plusDays(1);
        if (isWorkday(current)) {
            remaining--;
        }
    }
    return current;
}

这个方法:

  1. 从开始日期开始,逐天向后检查
  2. 遇到工作日时减少剩余工作日计数
  3. 直到剩余工作日为0时返回当前日期

countWorkdaysAndHolidays - 统计两个日期间的工作日和节假日天数

public Map<String, Long> countWorkdaysAndHolidays(LocalDate start, LocalDate end) {
    long workdays = 0;
    long holidays = 0;
    
    LocalDate current = start;
    while (!current.isAfter(end)) {
        if (isWorkday(current)) {
            workdays++;
        } else {
            holidays++;
        }
        current = current.plusDays(1);
    }
    
    return Map.of(
        "workdays", workdays,
        "holidays", holidays
    );
}

这个方法:

  1. 初始化工作日和节假日计数器
  2. 从开始日期到结束日期逐天检查
  3. 使用isWorkday方法判断是否是工作日
  4. 返回包含统计结果的Map

 isWorkday - 判断是否是工作日

public boolean isWorkday(LocalDate date) {
    return workdayCache.computeIfAbsent(date, d -> {
        // 优先检查是否是调休的工作日
        if (workWeekends.contains(d)) {
            return true;
        }
        // 检查是否是节假日
        if (holidays.contains(d)) {
            return false;
        }
        // 检查是否是周末
        DayOfWeek dayOfWeek = d.getDayOfWeek();
        return dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY;
    });
}

这个方法:

  1. 首先检查缓存中是否有该日期的判断结果
  2. 如果没有,则计算:
    • 先检查是否是调休工作日(周末但需要上班)
    • 再检查是否是节假日
    • 最后检查是否是普通周末
  3. 将结果存入缓存并返回

formatDurationBetween - 计算两个日期时间之间的时间差

public String formatDurationBetween(LocalDateTime start, LocalDateTime end) {
    Duration duration = Duration.between(start, end);
    long hours = duration.toHours();
    long minutes = duration.minusHours(hours).toMinutes();
    return String.format("%d小时%d分钟", hours, minutes);
}

这个方法:

  1. 计算两个时间点之间的Duration
  2. 提取小时部分
  3. 从剩余时间中提取分钟部分
  4. 返回格式化的字符串(如"3小时15分钟")

日期转换方法

public static LocalDate toLocalDate(Date date) {
    return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}

public static LocalDateTime toLocalDateTime(Date date) {
    return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
}

public static Date toDate(LocalDate localDate) {
    return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
}

这些方法用于新旧日期API之间的转换:

  • toLocalDate:将旧版Date转换为LocalDate
  • toLocalDateTime:将旧版Date转换为LocalDateTime
  • toDate:将LocalDate转换为旧版Date

总结

这个WorkdayCalculator类提供了完整的工作日计算功能,主要特点包括:

  1. 支持节假日和调休工作日的自定义配置
  2. 使用缓存提高重复计算的性能
  3. 线程安全的设计(使用ConcurrentHashMap)
  4. 提供了多种日期时间转换方法
  5. 支持工作日计算、工作日统计等功能

使用示例:

List<String> holidays = Arrays.asList("2023-10-01", "2023-10-02");
List<String> workWeekends = Arrays.asList("2023-09-30");
WorkdayCalculator calculator = new WorkdayCalculator(holidays, workWeekends);

// 计算5个工作日后的日期
LocalDate start = LocalDate.of(2023, 9, 25);
LocalDate result = calculator.calculateWorkdayAfter(start, 5);

// 统计两个日期间的工作日和节假日
Map<String, Long> counts = calculator.countWorkdaysAndHolidays(
    LocalDate.of(2023, 9, 1),
    LocalDate.of(2023, 9, 30)
);

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐