Search code examples
javacalendarweekdaylocaldate

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


How would I go about getting the number of weekdays (Monday though Friday) in a month with LocalDate? I've never used java.time before so I don't know all of its workings. I've been looking on this site to no avail along with searching for an answer. I also do not want to use any external libraries.

Example: As of this month, April of 2018, there are 21 weekdays. And next month there is 23 weekdays.


Solution

  • If desired, see optimized brute-force solution at the end

    Here is a non-brute-force implementation to calculate week days (Mon-Fri) in a month.

    It uses YearMonth instead of LocalDate, since the day-of-month value is meaningless to the calculation.

    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));
    }
    

    Here is an overload taking a LocalDate, so it's easy to call if that's what you have.

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

    Test

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

    Output

    21
    23
    

    Explanation of Formula

    The formula in the return statement was created by examining the expected return value for every combination of len (number of days in month, 28 - 31) and dow (day-of-week of first day of month, 1=Mon - 7=Sun):

       |  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
    

    Explanation for dow <= 5 (Mon-Fri)

    Initially there are len - 8 weekdays, i.e. we subtract the 4 weekends that always exist in a month.

    As we get to Thursday and Friday, we need to cap that for the 1 or 2 weekend days we lose. If you look at the 31-day row, we cap it at 26 - dow, i.e. for Friday (dow=5) we cap at 21, and for Thursday (dow=4) we cap at 22. For Monday-Wednesday, we also cap, but cap is equal to or higher than initial calculation, so it doesn't matter.

    Capping is done using min(xxx, cap) method, so we get:

    min(len - 8, 26 - dow)
    

    Explanation for dow >= 6 (Sat-Sun)

    You can see a small triangle in the lower-right corner. If we extend that pattern, we get:

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

    As a formula, that is len + dow - 16.

    Comparing that to original grid, numbers bottom out at 20, which is done using max(xxx, bottom) method, so we get:

    max(len + dow - 16, 20)
    

    Conclusion

    Finally we combine the two using ternary conditional operator:

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

    Full Java statement is then:

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

    Brute-Force Solution

    If you prefer a brute-force solution, you can ease it by skipping the first 4 weeks that always exists in a month:

    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;
    }