if ($days_in_this_week < 8) {
И как называется этот восьмой день? Я, честно говоря, не понял, зачем тут лишняя единица. В любом случае, проверять надо не верхнюю границу (всё равно выйти за неё не позволит условие продолжения цикла), а нижнюю - что в последней строке уже что-то есть, что она не пустая:
if ($days_in_this_week > 1) {
Но вообще, пустая строка в конце - это вовсе не косяк. Сколько строк всего может быть? - от четырёх (не високосный февраль, начинающийся в понедельник) до шести (месяц из 30 дней начинается в воскресенье, из 31 дня в воскресенье или субботу). Как-то не единообразно. Пусть всегда будет шесть строк, чтобы размер календаря не зависел от месяца. Так что отступаем от начала месяца назад до ближайшего понедельника, затем выводим 42 дня (шесть недель):
function drawCalendar($year, $month) {
$date = DateTime::createFromFormat('Y-m-d', "$year-$month-1");
$day = DateInterval::createFromDateString('1 day');
$weekdays = [ 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс' ];
do {
$date->sub($day);
} while ($date->format('w') !== '1');
$days = [];
for ($i = 0; $i < 42; $i++, $date->add($day)) {
$m = intval($date->format('m'));
$days[] = $m === $month ? intval($date->format('d')) : ' ';
}
return "
<table cellpadding=\"5\" cellspacing=\"5\" border=\"1\">
<thead>
<tr class=\"b-calendar__row\">".implode('', array_map(function($n) { return "
<th class=\"b-calendar__head\">$n</th>"; }, $weekdays))."
</tr>
</thead>
<tbody>".implode('', array_map(function($week) { return "
<tr class=\"b-calendar__row\">".implode('', array_map(function($n, $i) { return "
<td class=\"b-calendar__day ".($i > 4 ? 'b-calendar__weekend' : '')."\">
$n
</td>"; }, $week, array_keys($week)))."
</tr>"; }, array_chunk($days, 7)))."
</tbody>
</table>";
}
echo drawCalendar(2018, 1);