1. 概述

在这个教程中,我们将探讨Java中两种不同的方法来计算两个日期之间的工作日数量。我们会介绍一种使用Stream的可读版本,以及一种虽然不太易读但更高效的不进行循环的方法。

2. 使用Stream的全搜索

首先,我们来看看如何使用Stream来实现。我们的计划是遍历两个日期之间的每一天,并计算工作日

long getWorkingDaysWithStream(LocalDate start, LocalDate end){
    return start.datesUntil(end)
      .map(LocalDate::getDayOfWeek)
      .filter(day -> !Arrays.asList(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY).contains(day))
      .count();
}

我们首先利用LocalDatedatesUntil()方法,它返回从开始日期(包含)到结束日期(不包含)的所有日期的Stream

接着,我们使用map()LocalDategetDayOfWeek()方法将每个日期转换为一天。例如,这会将2023年1月10日变为星期三。

然后,我们通过检查它们是否符合DaysOfWeek枚举来过滤掉所有周末。最后,由于我们知道剩下的都是工作日,我们可以计数剩余的天数。

这种方法并不快,因为我们不得不逐日查看。但它易于理解,并且提供了在需要时轻松添加额外检查或处理的机会。

3. 不进行循环的高效搜索

另一种选择是不遍历所有日子,而是应用我们对一周内各天规则的理解。这里我们需要几个步骤,并需要处理一些边缘情况。

3.1. 设置初始日期

首先,我们定义一个类似之前的方法签名:

long getWorkingDaysWithoutStream(LocalDate start, LocalDate end)

处理这些日期的第一步是排除开始和结束日期的周末。对于开始日期,如果它是周末,我们就取下一个周一。我们还会用一个布尔值记录这一点:

boolean startOnWeekend = false;
if(start.getDayOfWeek().getValue() > 5){
    start = start.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
    startOnWeekend = true;
}

这里我们使用了TemporalAdjusters类,特别是其next()方法,可以让我们跳到指定日期后的下一个特定日期。

然后,我们可以对结束日期做同样的事情,如果它是周末,我们就取给定日期前的最后一个周五。这次我们将使用TemporalAdjusters.previous()来获取我们想要的日期在给定日期之前的第一个出现:

boolean endOnWeekend = false;
if(end.getDayOfWeek().getValue() > 5){
    end = end.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
    endOnWeekend = true;
}

3.2. 考虑边缘情况

这已经为我们带来了潜在的边缘情况:如果开始日期是周六,结束日期是周日。在这种情况下,我们的开始日期将是周一,而结束日期将是之前的周五。开始日期在结束日期之后没有意义,所以我们可以通过一个快速检查来处理这种情况:

if(start.isAfter(end)){
    return 0;
}

我们还需要处理另一个边缘情况,这就是为什么我们要记录开始和结束在周末。这取决于我们如何计数天数。例如,如果我们计算同一周内的周二到周五,我们会说它们之间有三天。同样,我们也会说周六到下个周六之间有五个工作日。但是,如果我们把开始和结束日期移到周一和周五,现在就变成了四天。为了抵消这一点,我们可以在需要时简单地添加一天:

long addValue = startOnWeekend || endOnWeekend ? 1 : 0;

3.3. 最终计算

我们现在可以计算开始和结束之间的总周数了。为此,我们将使用ChronoUnitbetween()方法。这个方法根据指定的单位(在我们的情况下是WEEKS)计算两个Temporal对象之间的时间

long weeks = ChronoUnit.WEEKS.between(start, end);

最后,我们可以使用到目前为止收集的所有信息来获取工作日总数的最终值:

return ( weeks * 5 ) + ( end.getDayOfWeek().getValue() - start.getDayOfWeek().getValue() ) + addValue;

这里的步骤首先是将周数乘以每周的工作日数。我们还没有考虑到非完整周的情况,所以需要加上一周开始到结束日期之间的额外天数。最后,我们加上周末开始或结束的调整。

4. 总结

在这篇文章中,我们探讨了计算两个日期之间工作日数量的两种方法。

首先,我们看到了如何使用Stream逐日检查。这种方法提供了简洁性和可读性,但牺牲了效率。

第二种方法是应用我们对一周内各天的规则,无需循环就能得出结果。这种方法提供了效率,但牺牲了可读性和维护性。

如往常一样,示例代码的完整实现可在GitHub上找到。