Даты и часовые пояса в интерфейсе
В каталоге может появиться дата публикации или день, к которому читатель планирует завершить курс. Внешне обе величины похожи на дату, однако смысл различается. Публикация произошла в определённый момент, а план «закончить 11 октября» может обозначать календарный день без единого часа для всех читателей.
Снимок advanced/lesson-28 рассматривает эти два договора отдельно. Основные четыре карточки не получают новые обязательные поля: временные значения используются в самостоятельном модуле. Учебная программа не запускалась; ожидаемый вывод зависит от явно указанной зоны или UTC, а не от придуманного замера пользовательской среды.
Момент времени
Для публикации используем строку с явным смещением UTC:
const publishedAt = new Date('2026-10-11T09:00:00Z');
console.log(publishedAt.toISOString());
Ожидаемая ISO-строка содержит тот же момент с миллисекундами: 2026-10-11T09:00:00.000Z. Буква Z задаёт UTC. Объект Date представляет временную величину, а не сохраняет собственную именованную зону «Минск» внутри каждой записи.
Один момент может отображаться как разные часы в разных зонах. Для Минска указанное время соответствует полудню при действующем правиле зоны, а для UTC — девяти утра. Выбор формата отображения рассмотрим через Intl далее. Пока важно, что данные момента не меняются от оформления.
Не передавайте в Date произвольную русскую строку вроде «11 октября 2026». Поддержка разбора нестандартных форматов не является нужным контрактом приложения. Сервер или файл должны отдавать однозначный формат, а человек должен видеть отдельно оформленную подпись.
Поведение временной величины и стандартных строк разбора описано в MDN о Date. Мы не используем экспериментальные API и не полагаемся на неуказанную версию браузера для нового способа даты.
Календарный день
Строка 2026-10-11 может обозначать именно день плана. Если превратить её в момент полуночи и затем показать в другой зоне, календарная подпись способна сдвинуться. Поэтому сначала определяем смысл поля, а не автоматически создаём объект Date для любого значения с дефисами.
В нашем примере календарная дата хранится как строка и при необходимости разбирается на год, месяц и день. Это не вычисленная отметка времени и не обещание часа отправки напоминания. Для напоминания дополнительно нужны локальное время, зона и политика неоднозначных часов.
export function parseCalendarDate(text) {
const match = /^([1-9]\d{3})-(\d{2})-(\d{2})$/.exec(text);
if (!match) throw new TypeError('Ожидалась дата YYYY-MM-DD с годом 1000–9999');
const year = Number(match[1]);
const month = Number(match[2]);
const day = Number(match[3]);
const probe = new Date(Date.UTC(year, month - 1, day));
if (probe.getUTCFullYear() !== year || probe.getUTCMonth() !== month - 1 ||
probe.getUTCDate() !== day) {
throw new TypeError('Несуществующая календарная дата');
}
return { year, month, day };
}
Это полная функция снимка. Регулярное выражение сначала проверяет форму, но не доказывает существование дня. Конструктор времени умеет нормализовать выходящие за пределы компоненты, поэтому после построения сравниваем полученные значения с исходными. Нормализованное 30 февраля не должно быть принято как введённая допустимая дата.
Год ограничен диапазоном 1000–9999. Это собственное ограничение примера, которое также избегает особого поведения малых годов некоторых числовых конструкторов Date. Для исторического архива понадобится иной явно описанный договор. Не расширяйте диапазон без повторного разбора проверки.
UTC для проверки, а не назначения дня
Вспомогательный объект probe строится в UTC только для проверки календарных компонентов. Мы не объявляем его моментом выполнения плана. Результат функции — отдельные числа, а исходная календарная строка остаётся полезным форматом данных.
Индексы месяцев числового API начинаются с нуля. Поэтому в Date.UTC передаётся month - 1, а при сравнении используется то же преобразование. Поле месяца строки имеет привычные значения от одного до двенадцати. Эта маленькая граница часто создаёт ошибку на месяц раньше нужного дня.
Сравнение UTC-компонентов делает проверку независимой от локальной зоны компьютера. Если использовать локальный конструктор и локальные getters без объяснения, результат мог бы зависеть от правил перехода времени. Для текущей задачи зона вообще не является частью календарной даты, поэтому не привносим её случайно.
Неудачный разбор
При неизвестной строке объект Date может стать невалидным. Некоторые операции дадут NaN, а форматирование определёнными методами может вызвать исключение. Поэтому нельзя считать успешное создание экземпляра доказательством принятого момента. На внешней границе проверяется формат и пригодность значения.
Для строгого поля публикации полезно заранее договориться о строке с явным UTC или смещением. Наш пример использует известную константу, поэтому не изображает универсальный валидатор всех ISO-вариантов. Если источник допускает разные точности и зоны, нужно определить разрешённые формы отдельно.
Дата окончания курса не должна вычисляться прибавлением фиксированных миллисекунд к местной полуночи без анализа задачи. Продолжительность двадцать четыре часа и следующий календарный день могут различаться при переходах зоны. Здесь мы только разделяем модель; арифметику расписаний лучше изучать самостоятельным результатом, а не прятать внутри оформления.
Наблюдения на знакомых данных
Ожидаемый разбор 2026-10-11 возвращает год 2026, месяц 10 и день 11. Для 2026-02-30 ожидается наш отказ, потому что нормализованные компоненты не совпадут. Для неверного разделителя или отсутствующего нуля ожидается отказ формы до построения вспомогательной даты.
Вручную рассмотрите границы високосного февраля, последний день месяца и следующую дату. Само регулярное выражение пропустит некоторые несуществующие сочетания, а сравнение компонентов должно их обнаружить. Этот опыт проверяет две разные ступени, поэтому изменяйте значения по одному.
Пограничные даты и предметный смысл
Сравните два примера февраля: 2024-02-29 должен пройти нашу проверку, а 2025-02-29 — получить отказ. Форма записи у них одинакова, поэтому регулярное выражение не решает вопрос календаря. При втором значении вспомогательный объект нормализуется в следующий месяц, и обратное сравнение обнаруживает несовпадение. Такой разбор объясняет, зачем нужны обе ступени проверки, а не просто дополнительная ветка «на всякий случай».
Аналогично месяц тринадцать и нулевой день не принимаются, хотя числовые части удалось прочитать. Нам нужен введённый календарный день, а не удобный конструктору способ перейти к соседней дате. Нормализация полезна при явно заданной арифметике компонентов, но она не является подтверждением корректного внешнего поля.
В моменте публикации смещение является частью однозначной записи. Строки с разными локальными часами могут обозначать один момент, если их смещения согласованы. При сравнении публикаций используйте временную величину, а не лексикографический порядок произвольно оформленных подписей. У календарных дней без времени, наоборот, не нужно изобретать часовое смещение ради хранения: последовательность дат определяется календарным договором.
Представьте курс, который опубликован вечером UTC. В части зон его местный день уже следующий. Это не ошибка данных: читатели наблюдают один момент через разные правила отображения. Если редакционная дата должна быть одинаковой на сайте независимо от посетителя, выбирается фиксированная зона или отдельное поле календарного дня. Нельзя одновременно обещать локальное время каждому и неизменную календарную подпись без уточнения.
Точный час для зоны зависит от актуальных правил, которыми располагает среда. Учебный формат фиксирует идентификатор и момент, но не заменяет обновляемую базу зон браузера. Следующий урок создаёт форматтер с явной зоной, чтобы зависимость была видимой. При переносе в приложение заранее решите, что именно показывается: момент события, редакционный день или личный срок читателя.
Наконец, не публикуйте дату «сегодня» как постоянное поле без смысла. Дата примера фиксирована и должна давать воспроизводимое объяснение независимо от дня чтения. Следующий урок покажет форматирование чисел и момента через Intl, сохраняя данные неизменными и явно выбирая язык и часовой пояс представления.