Календарь с возможностью выбора времени

Библиотека Pikaday изначально предназначена для выбора даты и не содержит встроенного механизма работы со временем. Расширение функциональности до «дата + время» реализуется через надстройку над существующим календарём: добавление UI-компонентов, синхронизацию состояния и форматирование результата.

Основная идея заключается в разделении ответственности:

  • календарь отвечает за выбор даты;
  • дополнительный слой отвечает за выбор времени;
  • единая модель данных объединяет обе части в один объект Date.

Такой подход позволяет сохранить стабильность базовой библиотеки и избежать её модификации.


Подключение Pikaday и базовая инициализация

Базовая инициализация календаря остаётся неизменной и служит отправной точкой для дальнейшего расширения.

const picker = new Pikaday({
    field: document.getElementById('datepicker'),
    format: 'DD.MM.YYYY',
    onSelect: function(date) {
        console.log(date);
    }
});

На этом этапе выбирается только дата. Объект Date содержит время, но оно всегда равно 00:00:00, что создаёт основу для дальнейшего расширения логики.


Добавление интерфейса выбора времени

Расширение функциональности реализуется через дополнительную разметку, которая либо внедряется внутрь контейнера календаря, либо отображается рядом с ним.

Типичная структура включает:

  • выпадающий список часов;
  • выпадающий список минут;
  • (опционально) секунды;
  • кнопка подтверждения или автоматическая синхронизация.

Пример HTML-структуры:

<div class="time-picker">
    <select id="hours"></select>
    <select id="minutes"></select>
</div>

Заполнение значений производится динамически:

const hours = document.getElementById('hours');
const minutes = document.getElementById('minutes');

for (let i = 0; i < 24; i++) {
    const option = document.createElement('option');
    option.value = i;
    option.textContent = String(i).padStart(2, '0');
    hours.appendChild(option);
}

for (let i = 0; i < 60; i++) {
    const option = document.createElement('option');
    option.value = i;
    option.textContent = String(i).padStart(2, '0');
    minutes.appendChild(option);
}

Синхронизация даты и времени в едином объекте

Ключевая задача расширения заключается в корректном объединении даты, выбранной в календаре, и времени из дополнительных контролов.

function mergeDateTime(date, hours, minutes) {
    const result = new Date(date);
    result.setHours(hours);
    result.setMinutes(minutes);
    result.setSeconds(0);
    result.setMilliseconds(0);
    return result;
}

Интеграция с календарём:

picker.on('select', function() {
    const date = picker.getDate();

    const h = parseInt(hours.value, 10);
    const m = parseInt(minutes.value, 10);

    const merged = mergeDateTime(date, h, m);

    console.log('Итоговое значение:', merged);
});

Такой подход гарантирует, что любое изменение времени автоматически отражается в итоговой дате.


Обновление значения при изменении времени

Для обеспечения реактивности требуется обработка событий изменения элементов времени.

function updateFromTimeChange() {
    const date = picker.getDate();
    if (!date) return;

    const h = parseInt(hours.value, 10);
    const m = parseInt(minutes.value, 10);

    const updated = mergeDateTime(date, h, m);

    picker.setDate(updated, true);
}

hours.addEventListener('change', updateFromTimeChange);
minutes.addEventListener('change', updateFromTimeChange);

Параметр true в setDate предотвращает повторный вызов событий выбора даты и снижает риск циклических обновлений.


Форматирование значения даты и времени

В зависимости от требований проекта форматирование может выполняться либо через стандартные методы Date, либо через сторонние библиотеки.

Пример форматирования через нативные средства:

function formatDateTime(date) {
    const dd = String(date.getDate()).padStart(2, '0');
    const mm = String(date.getMonth() + 1).padStart(2, '0');
    const yyyy = date.getFullYear();

    const hh = String(date.getHours()).padStart(2, '0');
    const mi = String(date.getMinutes()).padStart(2, '0');

    return `${dd}.${mm}.${yyyy} ${hh}:${mi}`;
}

При использовании временных библиотек возможна более гибкая работа с форматами и часовыми поясами, но принцип объединения данных остаётся неизменным.


Обработка события onSelect и централизованное состояние

Событие onSelect выступает центральной точкой синхронизации состояния календаря и времени.

const state = {
    date: null
};

const picker = new Pikaday({
    field: document.getElementById('datepicker'),
    onSelect: function(date) {
        state.date = date;

        const h = parseInt(hours.value, 10);
        const m = parseInt(minutes.value, 10);

        state.date = mergeDateTime(date, h, m);

        document.getElementById('datepicker').value =
            formatDateTime(state.date);
    }
});

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


Обновление состояния при программном изменении даты

При установке значения извне важно синхронизировать не только календарь, но и элементы времени.

function setDateTime(date) {
    picker.setDate(date, true);

    hours.value = date.getHours();
    minutes.value = date.getMinutes();

    document.getElementById('datepicker').value =
        formatDateTime(date);
}

Это предотвращает рассинхронизацию интерфейса при загрузке данных из сервера или восстановлении состояния формы.


Ограничения и особенности реализации

Расширение Pikaday до режима «дата + время» накладывает ряд технических ограничений:

  • отсутствие встроенной поддержки времени требует ручной синхронизации;
  • возможны расхождения при работе с часовыми поясами;
  • необходимо самостоятельно управлять форматированием и локализацией;
  • сложные сценарии (например, интервалы времени) требуют дополнительной логики поверх базовой модели.

Дополнительно следует учитывать, что календарный компонент остаётся независимым от временной логики, поэтому любые изменения в одном слое должны явно отражаться в другом через контролируемые функции синхронизации.