При желании, см. Оптимизированное решение о грубой силе в конце
Вот реализация без перебора для вычисления дней недели (пн-пт) в месяце.
Используется YearMonth
вместо 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));
}
Test
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) и dow
(день недели первого дня). месяца, 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;
}