我如何使用 LocalDate 获得一个月中的工作日数量 - Java

How would I get the amount of weekdays in a month with LocalDate - Java

我如何使用 LocalDate 获取一个月中的工作日(周一至周五)天数?我以前从未使用过 java.time 所以我不知道它的所有工作原理。我一直在这个网站上寻找答案,但无济于事。我也不想使用任何外部库。

示例:截至本月,即 2018 年 4 月,有 21 个工作日。下个月有23个工作日。

所以,感谢 Dreamspace 总裁帮我找到了解决方案(尽管这是一种蛮力方式),他说的是:

Create a LocalDate for the first day of the respective month. Loop until the end of the month using ld.plusDays(1) and use ld.getDayOfWeek() to see which DayOfWeek you're dealing with.

这是我发现的工作方式:

public static int businessDaysInMonth(final LocalDate ld) {

    int weekDays = 0;
    LocalDate date = ld.withDayOfMonth(1);
    final int intendedMonthValue = ld.getMonthValue();
    do {
        final DayOfWeek dayOfWeek = date.getDayOfWeek();

        if (dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY) {
            weekDays++;
        }

        date = date.plusDays(1);
    } while (date.getMonthValue() == intendedMonthValue);

    return weekDays;
}

如果需要,请参阅最后的优化暴力解决方案

这是一个非蛮力实现,用于计算 一个月中的工作日(周一至周五)。

它使用 YearMonth instead of LocalDate,因为日期值对计算没有意义。

public static int weekDaysInMonth(YearMonth yearMonth) {
    int len = yearMonth.lengthOfMonth(); // 28-31, supporting leap year
    int dow = yearMonth.atDay(1).getDayOfWeek().getValue(); // 1=Mon, 7=Sun
    return (dow <= 5 ? Math.min(len - 8, 26 - dow) : Math.max(len + dow - 16, 20));
}

这是一个采用 LocalDate 的重载,所以如果你有的话,很容易调用。

public static int weekDaysInMonth(LocalDate date) {
    return weekDaysInMonth(YearMonth.from(date));
}

测试

System.out.println(weekDaysInMonth(LocalDate.parse("2018-04-15"))); // April 15, 2018
System.out.println(weekDaysInMonth(YearMonth.of(2018, 5)));         // May 2018

输出

21
23

公式解释

return 语句中的公式是通过检查 len(月中的天数,28 - 31)和 [= 的每个组合的预期 return 值创建的26=](每月第一天的星期几,1=周一 - 7=周日):

   |  1   2   3   4   5    6   7
   | Mo  Tu  We  Th  Fr   Sa  Su
---+----------------------------
28 | 20  20  20  20  20   20  20
29 | 21  21  21  21  21   20  20
30 | 22  22  22  22  21   20  21
31 | 23  23  23  22  21   21  22

dow <= 5(周一至周五)的解释

最初有 len - 8 个工作日,即我们减去一个月中始终存在的 4 个周末。

到了周四和周五,我们需要限制我们失去的 1 或 2 个周末。如果您查看 31 天的行,我们将其上限设为 26 - dow,即周五 (dow=5) 我们将上限设为 21,而周四 (dow=4) 我们上限为 22。周一到周三,我们也有上限,但上限等于或高于初始计算,所以没关系。

封顶是使用min(xxx, cap)方法完成的,所以我们得到:

min(len - 8, 26 - dow)

dow >= 6(周六至周日)的解释

您可以在右下角看到一个小三角形。如果我们扩展该模式,我们会得到:

   |  4   5   6   7
---+---------------
28 | 16  17  18  19
29 | 17  18  19  20
30 | 18  19  20  21
31 | 19  20  21  22

作为一个公式,即len + dow - 16.

与原始网格相比,数字在 20 处触底,这是使用 max(xxx, bottom) 方法完成的,所以我们得到:

max(len + dow - 16, 20)

结论

最后我们使用三元条件运算符将两者结合起来:

dow <= 5  ?  min(len - 8, 26 - dow)  :  max(len + dow - 16, 20)

完整的 Java 语句是:

return (dow <= 5 ? Math.min(len - 8, 26 - dow) : Math.max(len + dow - 16, 20));

暴力破解

如果您更喜欢强力解决方案,您可以通过跳过一个月中始终存在的前 4 周来简化它:

public static int weekDaysInMonth(LocalDate refDate) {
    LocalDate firstOfMonth = refDate.withDayOfMonth(1);
    LocalDate nextMonth = firstOfMonth.plusMonths(1);
    int days = 20;
    for (LocalDate date = firstOfMonth.plusDays(28); date.isBefore(nextMonth); date = date.plusDays(1))
        if (date.getDayOfWeek().getValue() <= 5) // 1=Mon - 5=Fri, i.e. not 6=Sat and 7=Sun
            days++;
    return days;
}