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