Cal.com, раздел «когда я доступен», ноябрь 2022 — по сей день.
Cal.com — открытая замена Calendly: человек выставляет часы, когда он свободен, а гости сами занимают в них слоты. Часы задаются по дням недели: с такого-то до такого-то. Выпадающий список, шаг пятнадцать минут.
Пока вы работаете днём, всё честно.
Приходит Ахмед Садман (ahmedsadman) и рассказывает, что работает по ночам — с 22:30 до 01:30 — и что в списке кончается время (#5587):
In the availability section, 11:46 PM to 11:59 PM gets totally wasted as there is no way to include that 15 min slot in my availability.
В разделе доступности промежуток с 23:46 до 23:59 пропадает впустую: его нечем включить в мои часы.
Последний пункт списка — 23:45. Дальше суток нет.
Через три недели Удит Таккар (Udit-takkar) заводит ту же болезнь с другой стороны (#5929). Человек хочет быть доступным с 20:00 до 03:00. Приходится писать две строки:
8PM to 11:45PM
12:00 AM to 3:AM
Семь часов работы, записанные как два куска с дырой в четверть часа посредине.
Эмрис Эл (emrysal) присылает PR #6137 с названием, которое описывает ровно то, что делает:
Add 23:59 as a valid availability select option
Добавить 23:59 как допустимый пункт списка доступности.
Открыт в 05:05, влит в 16:13 того же дня. Четырнадцать потерянных минут возвращены. Задача #5929 закрыта.
Через две с половиной недели возвращается Ахмед Садман — тот же человек, что жаловался в ноябре, — и заводит #6323:
My selected availability is 10:30PM - 2:00AM. Given that, it should have a slot at 11:30PM so the meeting would be on 11:30PM - 12:30AM. But, the application doesn't allow booking at 11:30PM. I should mention that I have selected my availability up to 11:59PM for a day.
Мои часы — с 22:30 до 02:00. Значит, должен быть слот в 23:30, встреча шла бы с 23:30 до 00:30. Но приложение не даёт записаться на 23:30. Отмечу, что часы я выставил до 23:59, как позволил тот PR.
Вот он, поворот. Починка дала последние четырнадцать минут суток — и ничего не изменила. Встреча длится час; начавшись в 23:30, она кончится завтра, а завтра — это другая строка расписания.
Не хватало не пункта в списке. Не хватало того, чтобы день умел кончаться после полуночи.
Питер Рихелен (PeerRich), сооснователь Cal.com, пишет в задачу две строки:
can you try again? we made a hotfix
попробуйте ещё раз, мы выкатили хотфикс
В тот же день приходит ответ со скриншотом:
Unfortunately, it still doesn't work for me. Not seeing any 11:30 PM option
К сожалению, у меня по-прежнему не работает. Пункта 23:30 не вижу.
Задача закрыта и в тот же день открыта заново.
Ещё через четыре месяца тот же PeerRich пишет фразу, ради которой стоит читать чужие задачи:
this is a really low hanging plus hard to fix issue. We're reworking the entire booking page right now and will revisit this later
это одновременно и мелочь, и трудно чинится. Мы сейчас переделываем всю страницу записи и вернёмся к этому позже.
«Мелочь, которую трудно починить» — вот диагноз, и он поставлен верно. Снаружи это один пропущенный пункт в списке. Внутри — предположение, на котором стоит вся выборка слотов: доступность лежит внутри суток.
Через десять месяцев после открытия и через год после первой жалобы задача закрывается — со статусом not_planned.
Не «починено», не «дубликат». Не будем.
В коде Cal.com в сентябре 2026-го список времён по-прежнему собирается так — ScheduleComponent.tsx, строки 574–590:
Тот самый -1: если очередные пятнадцать минут перепрыгнут в следующий день, отнять минуту, чтобы попасть в 23:59. И отдельный push под комментарием // allow 23:59 на строке 588 — пункт, который циклом не получается.
Ниже, у кнопки «добавить промежуток», ещё два шрама — строки 630–643:
Двадцать третий час — особый случай; всё, что вылезает за конец суток, подрезается по конец суток. Три года спустя граница на месте, и видно её именно по заплаткам вокруг.
Ссылки закреплены по коммиту 176037d0 — чтобы через месяц они показывали тот же код, а не чужой на том же номере. Репозиторий переименован в calcom/cal.diy; старые ссылки на calcom/cal.com перенаправляются туда же.
«Сутки — отрезок от 00:00 до 23:59» — это не строчка, это решение о модели. Заметно оно становится в тот день, когда приходит первый человек, работающий по ночам, и к этому времени на предположении уже стоят выборка слотов, часовые пояса и календарная синхронизация.
Дальше каждая заплатка честно возвращает по кусочку — четырнадцать минут, особый двадцать третий час, подрезка по полуночи — и ни одна не двигает границу.
Отсюда и «мелочь, которую трудно починить»: мелочь — снаружи, трудно — внутри.
Ищите сравнение вида now >= open && now < close над временем в минутах от полуночи — и всё, что берёт endOf('day') как границу диапазона.
Смотрите не на возвращаемое значение, а на строку, которую увидит человек. Поиск ближайшего открытия может пропустить сегодняшний день как прошедший и уверенно сказать «Закрыто · откроется в 11:00», пока заведение работает.
Третьего не дано. Либо день перестаёт быть закрытым отрезком в расчёте, либо такие часы не сохраняются и человек читает почему. Заплатка на границе суток — не решение: она возвращает минуты, а не сутки.
Значение могло попасть в базу мимо формы: сидом, миграцией, прямым SQL или до появления запрета. Такой день живёт дальше и молчит — витрина показывает «закрыто», владелец узнаёт от гостя.
Соблазн причесать две схемы в одну велик, и выбор тут между двумя поломками. Строгое чтение роняет страницу настроек — ту самую, на которой это значение правят.