From:To:
11¶ **Cal.com, раздел «когда я доступен», ноябрь 2022 — по сей день.**
22
33 Cal.com — открытая замена Calendly: человек выставляет часы, когда он свободен, а гости сами занимают в них слоты. Часы задаются по дням недели: с такого-то до такого-то. Выпадающий список, шаг пятнадцать минут.
44
55 Пока вы работаете днём, всё честно.
66¶ ### Кадр первый: 18 ноября 2022. Четырнадцать минут
77
88 Приходит Ахмед Садман (ahmedsadman) и рассказывает, что работает по ночам — с 22:30 до 01:30 — и что в списке кончается время ([#5587](https://github.com/calcom/cal.com/issues/5587)):
99
1010 > 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.
1111
1212 *В разделе доступности промежуток с 23:46 до 23:59 пропадает впустую: его нечем включить в мои часы.*
1313
1414 Последний пункт списка — 23:45. Дальше суток нет.
1515¶ ### Кадр второй: 8 декабря 2022. Две строки вместо одной
1616
1717 Через три недели Удит Таккар (Udit-takkar) заводит ту же болезнь с другой стороны ([#5929](https://github.com/calcom/cal.com/issues/5929)). Человек хочет быть доступным с 20:00 до 03:00. Приходится писать две строки:
1818
1919 > 8PM to 11:45PM
2020 >
2121 > 12:00 AM to 3:AM
2222
2323 Семь часов работы, записанные как два куска с дырой в четверть часа посредине.
2424¶ ### Кадр третий: 21 декабря 2022. Починка за одиннадцать часов
2525
2626 Эмрис Эл (emrysal) присылает [PR #6137](https://github.com/calcom/cal.com/pull/6137) с названием, которое описывает ровно то, что делает:
2727
2828 > `Add 23:59 as a valid availability select option`
2929
3030 *Добавить 23:59 как допустимый пункт списка доступности.*
3131
3232 Открыт в 05:05, влит в 16:13 того же дня. Четырнадцать потерянных минут возвращены. Задача #5929 закрыта.
3333¶ ### Кадр четвёртый: 7 января 2023. Слота всё равно нет
3434
3535 Через две с половиной недели возвращается Ахмед Садман — тот же человек, что жаловался в ноябре, — и заводит [#6323](https://github.com/calcom/cal.com/issues/6323):
3636
3737 > 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**.
3838
3939 *Мои часы — с 22:30 до 02:00. Значит, должен быть слот в 23:30, встреча шла бы с 23:30 до 00:30. Но приложение не даёт записаться на 23:30. Отмечу, что часы я выставил до 23:59, как позволил тот PR.*
4040
4141 Вот он, поворот. Починка дала последние четырнадцать минут суток — и ничего не изменила. Встреча длится час; начавшись в 23:30, она кончится завтра, а завтра — это другая строка расписания.
4242
4343 Не хватало не пункта в списке. Не хватало того, чтобы день умел кончаться после полуночи.
4444¶ ### Кадр пятый: 26 января 2023. Хотфикс
4545
4646 Питер Рихелен (PeerRich), сооснователь Cal.com, пишет в задачу две строки:
4747
4848 > can you try again? we made a hotfix
4949
5050 *попробуйте ещё раз, мы выкатили хотфикс*
5151
5252 В тот же день приходит ответ со скриншотом:
5353
5454 > Unfortunately, it still doesn't work for me. Not seeing any 11:30 PM option
5555
5656 *К сожалению, у меня по-прежнему не работает. Пункта 23:30 не вижу.*
5757
5858 Задача закрыта и в тот же день открыта заново.
5959¶ ### Кадр шестой: 22 мая 2023. Честная строка
6060
6161 Ещё через четыре месяца тот же PeerRich пишет фразу, ради которой стоит читать чужие задачи:
6262
6363 > 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
6464
6565 *это одновременно и мелочь, и трудно чинится. Мы сейчас переделываем всю страницу записи и вернёмся к этому позже.*
6666
6767 «Мелочь, которую трудно починить» — вот диагноз, и он поставлен верно. Снаружи это один пропущенный пункт в списке. Внутри — предположение, на котором стоит вся выборка слотов: доступность лежит внутри суток.
6868¶ ### Кадр седьмой: 23 ноября 2023. Not planned
6969
7070 Через десять месяцев после открытия и через год после первой жалобы задача закрывается — со статусом `not_planned`.
7171
7272 Не «починено», не «дубликат». Не будем.
7373¶ ### Кадр восьмой: сегодня
7474
75− В коде Cal.com в сентябре 2026-го список времён по-прежнему собирается так (`packages/features/schedules/components/ScheduleComponent.tsx`):
75+ В коде Cal.com в сентябре 2026-го список времён по-прежнему собирается так — [`ScheduleComponent.tsx`, строки 574–590](https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/packages/features/schedules/components/ScheduleComponent.tsx#L574-L590):
7676
7777 ```js
7878 const end = dayjs().utc().endOf("day");
7979 for (
8080 let t = dayjs().utc().startOf("day");
8181 t.isBefore(end);
8282 t = t.add(INCREMENT + (!t.add(INCREMENT).isSame(t, "day") ? -1 : 0), "minutes")
8383 ) { … }
8484 // allow 23:59
8585 options.push({ value: end.toDate().valueOf(), … });
8686 ```
8787
88− Тот самый `-1`: если очередные пятнадцать минут перепрыгнут в следующий день, отнять минуту, чтобы попасть в 23:59. И отдельный `push` под комментарием `// allow 23:59` — пункт, который циклом не получается.
88+ Тот самый `-1`: если очередные пятнадцать минут перепрыгнут в следующий день, отнять минуту, чтобы попасть в 23:59. И отдельный `push` под комментарием `// allow 23:59` на строке 588 — пункт, который циклом не получается.
8989
90− Ниже, у кнопки «добавить промежуток», ещё два шрама:
90+ Ниже, у кнопки «добавить промежуток», ещё два шрама — [строки 630–643](https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/packages/features/schedules/components/ScheduleComponent.tsx#L630-L643):
9191
9292 ```js
9393 const nextRangeEnd =
9494 nextRangeStart.hour() === 23
9595 ? dayjs(nextRangeStart).add(59, "minutes").add(59, "seconds").add(999, "milliseconds")
9696 : dayjs(nextRangeStart).add(1, "hour");
9797 …
9898 end: nextRangeEnd.isAfter(endOfDay) ? endOfDay.toDate() : nextRangeEnd.toDate(),
9999 ```
100100
101101 Двадцать третий час — особый случай; всё, что вылезает за конец суток, подрезается по конец суток. Три года спустя граница на месте, и видно её именно по заплаткам вокруг.
102+
103+ Ссылки закреплены по коммиту `176037d0` — чтобы через месяц они показывали тот же код, а не чужой на том же номере. Репозиторий переименован в `calcom/cal.diy`; старые ссылки на `calcom/cal.com` перенаправляются туда же.
102104¶ ### Цена решения
103105
104106 «Сутки — отрезок от 00:00 до 23:59» — это не строчка, это решение о модели. Заметно оно становится в тот день, когда приходит первый человек, работающий по ночам, и к этому времени на предположении уже стоят выборка слотов, часовые пояса и календарная синхронизация.
105107
106108 Дальше каждая заплатка честно возвращает по кусочку — четырнадцать минут, особый двадцать третий час, подрезка по полуночи — и ни одна не двигает границу.
107109
108110 Отсюда и «мелочь, которую трудно починить»: мелочь — снаружи, трудно — внутри.
109111## Что с этим делать у себя
1101121. Найти, где сутки считаются закрытым отрезком
111113 Ищите сравнение вида `now >= open && now < close` над временем в минутах от полуночи — и всё, что берёт `endOf('day')` как границу диапазона.
112114 why: Пока часы внутри суток, такое условие работает и выглядит правильным. Оно ломается ровно в тот день, когда кто-то заводит смену через полночь, — и ломается молча.
113115 - [ ] Найдено условие или его отсутствие подтверждено
114116 - [ ] Проверено, что даёт `open=11:00, close=02:00`: должно получиться «закрыто круглые сутки»
115117 → Cal.com #6323 — availability cannot span past midnight — https://github.com/calcom/cal.com/issues/6323
118+ → Граница суток в коде: ScheduleComponent.tsx#L574-L590 — https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/packages/features/schedules/components/ScheduleComponent.tsx#L574-L590
1161192. Проверить, что говорит интерфейс, а не только что считает функция
117120 Смотрите не на возвращаемое значение, а на строку, которую увидит человек. Поиск ближайшего открытия может пропустить сегодняшний день как прошедший и уверенно сказать «Закрыто · откроется в 11:00», пока заведение работает.
118121 why: Неверный ответ, произнесённый уверенно, дороже ошибки, которую видно. Гость не станет проверять.
119122 - [ ] Прогнан сценарий «часы 11:00–02:00, сейчас 12:00» и записано, что показано человеку
1201233. Решить: поддерживаем ночную смену или отказываем честно
121124 Третьего не дано. Либо день перестаёт быть закрытым отрезком в расчёте, либо такие часы не сохраняются и человек читает почему. Заплатка на границе суток — не решение: она возвращает минуты, а не сутки.
122125 why: История Cal.com — про то, чем кончаются заплатки: каждая честно даёт по кусочку, граница остаётся на месте, разговор длится год и закрывается словами not planned.
123126 - [ ] Решение записано в коде рядом с правилом, а не только в голове
127+ → Заплатки вокруг полуночи: ScheduleComponent.tsx#L630-L643 — https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/packages/features/schedules/components/ScheduleComponent.tsx#L630-L643
128+ → Cal.com #5587 — четырнадцать потерянных минут — https://github.com/calcom/cal.com/issues/5587
1241294. Если отказываем — проверить, что запрет стоит не только на записи
125130 Значение могло попасть в базу мимо формы: сидом, миграцией, прямым SQL или до появления запрета. Такой день живёт дальше и молчит — витрина показывает «закрыто», владелец узнаёт от гостя.
126131 why: Запрет на записи ловит только того, кто сегодня нажал «сохранить». Это ровно та же тишина, от которой запрет и защищал, только с другой стороны.
127132 - [ ] Уже сохранённое нарушение видно в форме до всякого сохранения
128133 - [ ] Сказано ПОСЛЕДСТВИЕ («витрина посчитает этот день закрытым»), а не правило: правило видно в самих полях
1291345. Не делать схему чтения строгой заодно с записью
130135 Соблазн причесать две схемы в одну велик, и выбор тут между двумя поломками. Строгое чтение роняет страницу настроек — ту самую, на которой это значение правят.
131136 why: Установка, где такие часы уже лежат, стала бы недоступной целиком, и починить её было бы неоткуда. Отказ на записи приходит в форму, где человек его прочтёт и исправит; отказ на чтении не приходит никуда.
132137 - [ ] Асимметрия закрыта тестом с обеих сторон: запись отказывает, чтение принимает