Calculate Number of Weekdays Between Two Dates in Java – 用 Java 计算两个日期之间的工作日数

最后修改: 2024年 1月 24日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll look at two different methods in Java for calculating the number of weekdays between two dates. We’ll look at a readable version using Streams and a less readable but more efficient option that doesn’t loop at all.

在本教程中,我们将学习 Java 中计算两个日期之间工作日数的两种不同方法。我们将了解使用 Streams 的可读版本,以及可读性较低但效率更高的完全不循环的选项。

2. Full Search Using Streams

2.使用进行全面搜索

First, let’s see how we can do this with Streams. The plan is to loop over every day between our two dates and count the weekdays:

首先,让我们看看如何使用 Streams 来实现这一目标。计划是在两个日期之间的每一天循环并计算工作日

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

To start we’ve utilized LocalDate‘s datesUntil() method. This method returns a Stream of all the dates from the start (inclusive) to the end date (exclusive).

首先,我们使用了 LocalDate datesUntil() 方法。该方法返回一个从开始日期(包含)到结束日期(不包含)的所有日期的 Stream 方法。

Next, we’ve used map() and LocalDate‘s getDayOfWeek() to transform each date into a day. For example, this would change 10-01-2023 to Wednesday.

接下来,我们使用 map()LocalDategetDayOfWeek() 将每个日期转换为一天。例如,这将把 10-01-2023 改为星期三。

Following that, we filter out all the weekend days by checking them against the DaysOfWeek enum. Finally, we can count up the days left, as we know these will all be weekdays.

然后,我们对照 DaysOfWeek enum 过滤掉所有周末天数。最后,我们可以计算剩下的天数,因为我们知道这些天数都是工作日。

This method isn’t the quickest, as we have to look at it every single day. However, it’s easily understandable and offers the opportunity to easily put in extra checks or processing if needed.

这种方法并不是最快的,因为我们每天都要查看它。不过,它很容易理解,并提供了在需要时轻松进行额外检查或处理的机会。

3. Efficient Search Without Looping

3.不循环的高效搜索

The other option we have is to not loop over all the days, but instead, apply the rules we know about the days of the week. There are several steps we need here, and a few edge cases to take care of.

我们的另一个选择是不循环所有的天数,而是应用我们所知道的关于一周中的天数的规则。这里我们需要几个步骤,还要处理一些边缘情况。

3.1. Setting up Initial Dates

3.1.设置初始日期

To start, we’ll define our method signature which will be a lot like our previous one:

首先,我们要定义方法签名,它与之前的签名非常相似:

long getWorkingDaysWithoutStream(LocalDate start, LocalDate end)

The first step in processing these dates is to exclude any weekends at the start and end. So for the start date, if it’s a weekend we’ll take the following Monday. We’ll also track the fact that we did this with a boolean:

处理这些日期的第一步是排除开始和结束日期中的任何周末。因此,对于开始日期,如果是周末,我们将取下周一。我们还将使用 boolean 来跟踪我们这样做的事实:

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

We’ve used the TemporalAdjusters class here, specifically its next() method which lets us jump to the next specified day.

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

We can then do the same for the end date – if it’s a weekend, we take the previous Friday. This time we’ll use TemporalAdjusters.previous() to take us to the first occurrence of the day we want before the given date:

然后,我们可以对结束日期做同样的处理–如果是周末,我们就取前一个星期五。这一次,我们将使用 TemporalAdjusters.previous() 来查找给定日期之前出现的第一个日期:

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

3.2. Accounting for Edge Cases

3.2.边缘情况核算

This already presents us with a potential edge case, if we start on Saturday and end on Sunday. In that case, our start date will now be Monday, and the end date the Friday before. It doesn’t make sense for the start to be after the end, so we can cover this potential use case with a quick check:

如果我们的开始日期是星期六,而结束日期是星期日,这就给我们带来了一个潜在的边缘情况。在这种情况下,我们的开始日期将是星期一,而结束日期则是前一个星期五。开始日期在结束日期之后是不合理的,因此我们可以通过快速检查来解决这个潜在的用例:

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

We also need to cover another edge case which is why we kept track of starting and ending on a weekend. This is optional and depends on how we want to count the days. For example, if we counted between a Tuesday and Friday in the same week we’d say there are three days between them.

我们还需要涵盖另一种边缘情况,这就是为什么我们要跟踪周末的开始和结束时间。这是可选项,取决于我们想如何计算天数。例如,如果我们计算同一星期的星期二和星期五之间的天数,我们会说它们之间有三天。

We’d also say there are five weekdays between a Saturday and the following Saturday. However, if we move the start and end days to Monday and Friday as we’re doing here, that now counts as four days. So to counteract that we can simply add a day if required:

我们还可以说,从一个星期六到下一个星期六之间有五个工作日。但是,如果我们像这里一样,将开始日和结束日分别移到星期一和星期五,那么现在算作四天。因此,如果需要的话,我们可以简单地增加一天来抵消这种情况:

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

3.3. Final Calculations

3.3.最终计算

We’re now in a position to calculate the total amount of weeks between the start and end. For this, we’ll use ChronoUnit’between() method. This method calculates the time between two Temporal objects, in the specified unit which is WEEKS in our case:

现在,我们可以计算开始和结束之间的总周数。为此,我们将使用 ChronoUnit 的 between() 方法。该方法以指定的单位计算两个 Temporal 对象之间的时间,在我们的例子中,单位是 WEEKS

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

Finally, we can use everything we’ve gathered so far to get our final value for the number of weekdays:

最后,我们可以利用迄今为止收集到的所有信息,得出工作日数的最终值: <br

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

The steps here are firstly to multiply the number of weeks by the number of weekdays per week. We haven’t accounted for non-whole weeks yet so we add on the extra days between the start day of the week and the end day of the week. To finish we add the adjustment for starting or finishing on a weekend.

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

4. Conclusion

4.结论

In this article, we’ve looked at two options for calculating the number of weekdays between two dates.

在本文中,我们介绍了计算两个日期之间工作日数的两种方法。

First, we saw how to use a Stream and check each day individually. This method offers simplicity and readability at the expense of efficiency.

首先,我们了解了如何使用 Stream 并逐日检查。这种方法简单易读,但却牺牲了效率。

The second option is to apply the rules we know about the days of the week to figure it out without a loop. This offers efficiency at the expense of readability and maintainability.

第二种方法是应用我们所知道的星期规则,不需要循环就能算出星期。这样可以提高效率,但却牺牲了可读性和可维护性。

As always, the full code for the examples is available over on GitHub.

与往常一样,这些示例的完整代码可在 GitHub 上获取。