Вопрос: Как правильнее сделать такой функционал?

Лучший ответ:
Можно как-то так:
$schedule = [
  'weekdays' => [
    1 => [[0, 24]],
    2 => [[0, 24]],
    ...
    7 => [[0, 6], [8, 24]]
  ],
  'dates' => [
    '2020-12-31' => [[0, 6], [8, 17]],
    '2021-01-01' => [[0, 0]],
    '2020-01-02' => [[10, 23]],
    ...
  ]
];
function isWorkTime($schedule) {
  $date = date('Y-m-d');
  $weekday = intval(date('N'));
  $hour = intval(date('H'));
  $daySchedule = $schedule['weekdays'][$weekday];
  if (array_key_exists($date, $schedule['dates']]) {
    $daySchedule = $schedule['dates'][$date];
  }
  foreach ($daySchedule as $workTime) {
    if ($hour >= $workTime[0] && $hour < $workTime[1]) {
      return true;
    }
  }
  return false;
}

Худший ответ:
Основная фишка задачи в том, что вы указали даты, разбили прямую времени на диапазоны и прокомментировали лишь часть из них. А еще есть другие диапазоны. Например, между 17 часов 31 дек и 0 часов 1 янв, который в задаче не указан и если не указать на него ЗАКРЫТО, то так дойдем до диапазона по умолчанию и получим ОТКРЫТО.

Я сделал двухуровневую конфигурацию - сперва выбираем широкий диапазон, задаем в нем состояние по умолчанию. Второй уровень диапазонов позволяет переопределить значение по умолчанию.

код<?php

header('Content-type: text/plain');

?>31 Декабря с 8:00 до 17:00
  1 Января выходной день
  Со 2 января по 8 Января 10:00 до 23:00
  9 Января с 11:00 до 23:00

<?
$dtList = [
      ['12/31', '12/31', false, [['08:00', '17:00', true]]], // открыто
      ['01/01', '01/02', false, []], // закрыто (пустой диапазон времени)
      ['01/02', '01/08', false, [['10:00', '23:00', true]]],
      ['01/09', '01/09', false, [['11:00', '23:00', true]]],
      ['01/01', '12/31', true, [['06:00', '08:00', false]]],
];

$dt = new dt('2020-12-28');
$finish = (new dt('2021-01-14'))->getTimestamp();

do {
    $res = false;
    foreach ($dtList as [$a1, $b1, $def, $subList]) {
        $a1 = $dt->_clone($a1);
        $b1 = $dt->_clone($b1);
        if (h::testTimeRange($dt, $a1, $b1)) {
            foreach($subList as [$a2, $b2, $c2]){
                $a2 = $dt->_clone($a2);
                $b2 = $dt->_clone($b2);
                if (h::testTimeRange($dt, $a2, $b2)) {
                  $res = $c2;
                  break 2; // выход с обоих циклов
                }
            }
            $res = $def; // первый уровень сработал, а второй не имеет подх. диапазона, значит значение по умолчанию для перв. уровня
            break;
        }
    }
    echo "\n ", $dt->format(), $res ? ' open' : ' close';
    $dt->modify('+'.rand(1, 1).' hour');
} while ($dt->getTimestamp() < $finish);

результат31 Декабря с 8:00 до 17:00
  1 Января выходной день
  Со 2 января по 8 Января 10:00 до 23:00
  9 Января с 11:00 до 23:00

 28.12.2020 00:00 open
 28.12.2020 03:00 open
 28.12.2020 05:00 open
 28.12.2020 08:00 open
 28.12.2020 09:00 open
 28.12.2020 11:00 open
 28.12.2020 12:00 open
 28.12.2020 13:00 open
 28.12.2020 15:00 open
 28.12.2020 16:00 open
 28.12.2020 18:00 open
 28.12.2020 19:00 open
 28.12.2020 20:00 open
 28.12.2020 23:00 open
 29.12.2020 02:00 open
 29.12.2020 05:00 open
 29.12.2020 06:00 close
 29.12.2020 09:00 open
 29.12.2020 12:00 open
 29.12.2020 15:00 open
 29.12.2020 17:00 open
 29.12.2020 20:00 open
 29.12.2020 22:00 open
 29.12.2020 23:00 open
 30.12.2020 02:00 open
 30.12.2020 03:00 open
 30.12.2020 06:00 close
 30.12.2020 07:00 close
 30.12.2020 10:00 open
 30.12.2020 12:00 open
 30.12.2020 15:00 open
 30.12.2020 17:00 open
 30.12.2020 19:00 open
 30.12.2020 20:00 open
 30.12.2020 23:00 open
 31.12.2020 02:00 close
 31.12.2020 05:00 close
 31.12.2020 07:00 close
 31.12.2020 09:00 close
 31.12.2020 10:00 close
 31.12.2020 13:00 close
 31.12.2020 16:00 close
 31.12.2020 19:00 close
 31.12.2020 20:00 close
 31.12.2020 23:00 close
 01.01.2021 01:00 close
 01.01.2021 02:00 close
 01.01.2021 03:00 close
 01.01.2021 04:00 close
 01.01.2021 06:00 close
 01.01.2021 09:00 close
 01.01.2021 10:00 close
 01.01.2021 11:00 close
 01.01.2021 12:00 close
 01.01.2021 13:00 close
 01.01.2021 15:00 close
 01.01.2021 16:00 close
 01.01.2021 18:00 close
 01.01.2021 20:00 close
 01.01.2021 23:00 close
 02.01.2021 00:00 close
 02.01.2021 03:00 close
 02.01.2021 06:00 close
 02.01.2021 07:00 close
 02.01.2021 10:00 open
 02.01.2021 11:00 open
 02.01.2021 12:00 open
 02.01.2021 13:00 open
 02.01.2021 14:00 open
 02.01.2021 15:00 open
 02.01.2021 16:00 open
 02.01.2021 17:00 open
 02.01.2021 18:00 open
 02.01.2021 20:00 open
 02.01.2021 23:00 close
 03.01.2021 02:00 close
 03.01.2021 04:00 close
 03.01.2021 07:00 close
 03.01.2021 10:00 open
 03.01.2021 12:00 open
 03.01.2021 13:00 open
 03.01.2021 16:00 open
 03.01.2021 17:00 open
 03.01.2021 20:00 open
 03.01.2021 22:00 open
 03.01.2021 23:00 close
 04.01.2021 00:00 close
 04.01.2021 02:00 close
 04.01.2021 05:00 close
 04.01.2021 06:00 close
 04.01.2021 09:00 close
 04.01.2021 12:00 open
 04.01.2021 13:00 open
 04.01.2021 14:00 open
 04.01.2021 15:00 open
 04.01.2021 17:00 open
 04.01.2021 19:00 open
 04.01.2021 22:00 open
 04.01.2021 23:00 close
 05.01.2021 01:00 close
 05.01.2021 02:00 close
 05.01.2021 05:00 close
 05.01.2021 06:00 close
 05.01.2021 08:00 close
 05.01.2021 10:00 open
 05.01.2021 11:00 open
 05.01.2021 14:00 open
 05.01.2021 16:00 open
 05.01.2021 17:00 open
 05.01.2021 20:00 open
 05.01.2021 23:00 close
 06.01.2021 01:00 close
 06.01.2021 03:00 close
 06.01.2021 04:00 close
 06.01.2021 05:00 close
 06.01.2021 08:00 close
 06.01.2021 09:00 close
 06.01.2021 11:00 open
 06.01.2021 13:00 open
 06.01.2021 15:00 open
 06.01.2021 18:00 open
 06.01.2021 20:00 open
 06.01.2021 22:00 open
 07.01.2021 00:00 close
 07.01.2021 01:00 close
 07.01.2021 02:00 close
 07.01.2021 03:00 close
 07.01.2021 06:00 close
 07.01.2021 07:00 close
 07.01.2021 09:00 close
 07.01.2021 10:00 open
 07.01.2021 13:00 open
 07.01.2021 14:00 open
 07.01.2021 16:00 open
 07.01.2021 17:00 open
 07.01.2021 20:00 open
 07.01.2021 23:00 close
 08.01.2021 01:00 open
 08.01.2021 02:00 open
 08.01.2021 04:00 open
 08.01.2021 05:00 open
 08.01.2021 07:00 close
 08.01.2021 09:00 open
 08.01.2021 11:00 open
 08.01.2021 14:00 open
 08.01.2021 17:00 open
 08.01.2021 20:00 open
 08.01.2021 23:00 open
 09.01.2021 00:00 open
 09.01.2021 01:00 open
 09.01.2021 02:00 open
 09.01.2021 05:00 open
 09.01.2021 07:00 close
 09.01.2021 10:00 open
 09.01.2021 12:00 open
 09.01.2021 14:00 open
 09.01.2021 17:00 open
 09.01.2021 20:00 open
 09.01.2021 22:00 open
 09.01.2021 23:00 open
 10.01.2021 01:00 open
 10.01.2021 04:00 open
 10.01.2021 05:00 open
 10.01.2021 07:00 close
 10.01.2021 10:00 open
 10.01.2021 12:00 open
 10.01.2021 14:00 open
 10.01.2021 17:00 open
 10.01.2021 19:00 open
 10.01.2021 20:00 open
 10.01.2021 23:00 open

класс dt<?php
class dt extends DateTime{

  /** Формат отображения даты-времени по умолчанию в игре */
  const F_DEFAULT = 'd.m.Y H:i';

  /**
  * Создает
  *
  * @param mixed $time
  * @param DateTimeZone $timezone
  */
  public static function createRand($time = null, DateTimeZone $timezone = null){
    $time = preg_replace_callback('@/(\d+)-(\d+)/@', function($m){
         return (string)h::rand($m[1], $m[2]);
      }, $time);
    return new self($time, $timezone);
  }

  /**
  * $time допускает указание нескольких доп. параметров для modify() через разделитель |
  *
  * @param mixed $time
  * @param DateTimeZone $timezone
  * @return DateTime
  */
  public function __construct($time = null, DateTimeZone $timezone = null){
    $time = explode('|', $time);
    parent::__construct($time[0], $timezone);
    while($s = next($time)){
      $this->modify($s);
    }
  }

  /**
  * Вывод даты согласно формату
  *
  * @param string Мо умолчнию будет общепринятый в игре вариант F_DEFAULT
  * @return string
  */
  public function format($format = null){
    return parent::format(is_null($format) ? self::F_DEFAULT : $format);
  }

  /**
  * Клонирует текущий объект.
  * @return dt
  */
  public function _clone($modify = null){
    $res = clone $this;
    if(!is_null($modify)){
      $res->modify($modify);
    }
    return $res;
  }

  public function __toString(){
    return $this->format();
  }

}