← Назад к вопросам

Как реализовать календарь?

2.0 Middle🔥 112 комментариев
#JavaScript Core

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как реализовать календарь

Это практический вопрос, который проверяет навыки работы с датами, компонентов и логики. Объясню пошагово реализацию календаря с React.

1. Понимание логики календаря

// Календарь должен отображать:
// 1. Месяц и год
// 2. Дни недели
// 3. Все дни месяца
// 4. Дни предыдущего и следующего месяца для полноты сетки

const date = new Date(2024, 0); // Январь 2024

const year = date.getFullYear();      // 2024
const month = date.getMonth();         // 0 (январь)
const firstDay = new Date(year, month, 1);      // Первый день месяца
const lastDay = new Date(year, month + 1, 0);   // Последний день месяца

const firstDayOfWeek = firstDay.getDay();  // День недели первого дня (0 = вс, 6 = сб)
const daysInMonth = lastDay.getDate();      // Количество дней в месяце

console.log(`В январе ${year} ${daysInMonth} дней, начинается с дня ${firstDayOfWeek}`);

2. Базовый компонент календаря

import React, { useState } from 'react';

function Calendar() {
  const [currentDate, setCurrentDate] = useState(new Date(2024, 0, 1));
  
  const year = currentDate.getFullYear();
  const month = currentDate.getMonth();
  
  // Получить первый день недели месяца (0 = вс, 1 = пн, ...)
  const firstDay = new Date(year, month, 1).getDay();
  // Получить количество дней в месяце
  const daysInMonth = new Date(year, month + 1, 0).getDate();
  // Получить количество дней предыдущего месяца
  const daysInPrevMonth = new Date(year, month, 0).getDate();
  
  // Создать массив для отрисовки
  const days = [];
  
  // Добавить дни предыдущего месяца
  for (let i = firstDay - 1; i >= 0; i--) {
    days.push({
      day: daysInPrevMonth - i,
      isCurrentMonth: false,
      date: new Date(year, month - 1, daysInPrevMonth - i)
    });
  }
  
  // Добавить дни текущего месяца
  for (let i = 1; i <= daysInMonth; i++) {
    days.push({
      day: i,
      isCurrentMonth: true,
      date: new Date(year, month, i)
    });
  }
  
  // Добавить дни следующего месяца для полноты
  const remainingDays = 42 - days.length; // 6 недель * 7 дней
  for (let i = 1; i <= remainingDays; i++) {
    days.push({
      day: i,
      isCurrentMonth: false,
      date: new Date(year, month + 1, i)
    });
  }
  
  const handlePrevMonth = () => {
    setCurrentDate(new Date(year, month - 1));
  };
  
  const handleNextMonth = () => {
    setCurrentDate(new Date(year, month + 1));
  };
  
  const monthName = currentDate.toLocaleString('default', { month: 'long' });
  
  return (
    <div className="calendar">
      <div className="header">
        <button onClick={handlePrevMonth}>Назад</button>
        <h2>{monthName} {year}</h2>
        <button onClick={handleNextMonth}>Вперед</button>
      </div>
      
      <div className="weekdays">
        {['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'].map(day => (
          <div key={day} className="weekday">{day}</div>
        ))}
      </div>
      
      <div className="days-grid">
        {days.map((dayObj, index) => (
          <button
            key={index}
            className={`day ${
              dayObj.isCurrentMonth ? 'current-month' : 'other-month'
            } ${isToday(dayObj.date) ? 'today' : ''}`}
          >
            {dayObj.day}
          </button>
        ))}
      </div>
    </div>
  );
}

function isToday(date) {
  const today = new Date();
  return (
    date.getDate() === today.getDate() &&
    date.getMonth() === today.getMonth() &&
    date.getFullYear() === today.getFullYear()
  );
}

export default Calendar;

3. CSS стили

.calendar {
  max-width: 400px;
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 20px;
  font-family: Arial, sans-serif;
}

.calendar .header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.calendar .header h2 {
  margin: 0;
  font-size: 20px;
  min-width: 150px;
  text-align: center;
}

.calendar .header button {
  padding: 8px 12px;
  border: none;
  background: #f0f0f0;
  cursor: pointer;
  border-radius: 4px;
}

.calendar .header button:hover {
  background: #e0e0e0;
}

.calendar .weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 8px;
  margin-bottom: 8px;
  font-weight: bold;
  text-align: center;
}

.calendar .weekday {
  padding: 8px 0;
  font-size: 12px;
  color: #666;
}

.calendar .days-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 8px;
}

.calendar .day {
  aspect-ratio: 1;
  border: none;
  background: white;
  cursor: pointer;
  border-radius: 4px;
  font-size: 14px;
  transition: all 0.2s;
}

.calendar .day:hover {
  background: #f0f0f0;
}

.calendar .day.other-month {
  color: #ccc;
  cursor: default;
}

.calendar .day.other-month:hover {
  background: white;
}

.calendar .day.today {
  background: #007bff;
  color: white;
  font-weight: bold;
}

4. Расширенная версия с выбором дат

function CalendarWithSelection() {
  const [currentDate, setCurrentDate] = useState(new Date(2024, 0, 1));
  const [selectedDates, setSelectedDates] = useState([]);
  
  const handleDateClick = (date) => {
    const dateString = date.toISOString().split('T')[0];
    
    if (selectedDates.includes(dateString)) {
      setSelectedDates(selectedDates.filter(d => d !== dateString));
    } else {
      setSelectedDates([...selectedDates, dateString]);
    }
  };
  
  // остальной код календаря...
  
  return (
    <div>
      {/* компонент календаря */}
      <div className="selected-dates">
        <p>Выбранные даты:</p>
        <ul>
          {selectedDates.sort().map(date => (
            <li key={date}>{date}</li>
          ))}
        </ul>
      </div>
    </div>
  );
}

5. Интеграция с date-fns (библиотека для работы с датами)

import {
  startOfMonth,
  endOfMonth,
  eachDayOfInterval,
  format,
  addMonths,
  isSameDay,
  isToday
} from 'date-fns';
import { ru } from 'date-fns/locale';

function CalendarWithDateFns() {
  const [currentMonth, setCurrentMonth] = useState(new Date());
  
  const firstDay = startOfMonth(currentMonth);
  const lastDay = endOfMonth(currentMonth);
  const days = eachDayOfInterval({ start: firstDay, end: lastDay });
  
  // Добавить дни для полной недели
  const firstDayOfWeek = firstDay.getDay();
  const allDays = [
    ...Array(firstDayOfWeek).fill(null).map((_, i) => 
      new Date(firstDay.getTime() - (firstDayOfWeek - i) * 86400000)
    ),
    ...days,
  ];
  
  // Добавить дни для полных 6 недель
  while (allDays.length % 7 !== 0) {
    allDays.push(new Date(allDays[allDays.length - 1].getTime() + 86400000));
  }
  
  return (
    <div className="calendar">
      <div className="header">
        <button onClick={() => setCurrentMonth(addMonths(currentMonth, -1))}>
          Назад
        </button>
        <h2>{format(currentMonth, 'LLLL yyyy', { locale: ru })}</h2>
        <button onClick={() => setCurrentMonth(addMonths(currentMonth, 1))}>
          Вперед
        </button>
      </div>
      
      <div className="days-grid">
        {allDays.map((date, index) => (
          <div
            key={index}
            className={`day ${
              !date || isSameDay(date, firstDay) ? 'current-month' : 'other-month'
            } ${isToday(date) ? 'today' : ''}`}
          >
            {date ? format(date, 'd') : ''}
          </div>
        ))}
      </div>
    </div>
  );
}

6. Важные моменты при реализации

// 1. Учитывать временные зоны
const date = new Date();
const utcDate = new Date(date.getTime() + date.getTimezoneOffset() * 60000);

// 2. Корректная работа с getDay()
// getDay() возвращает: 0 = вс, 1 = пн, ..., 6 = сб
const dayOfWeek = new Date(2024, 0, 1).getDay(); // 1 (пн)

// 3. Проверка на високосный год
function isLeapYear(year) {
  return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}

// 4. Форматирование дат
const options = { year: 'numeric', month: 'long', day: 'numeric' };
const formatted = new Date().toLocaleDateString('ru-RU', options);
// Output: "3 апреля 2024 г."

7. Рекомендации для production

  1. Используй библиотеку date-fns или day.js - избегай ошибок с датами
  2. Кэшируй вычисления - календарь генерируется один раз при монтировании
  3. Мемоизируй компоненты дней - если их много
  4. Поддерживай доступность - ARIA labels, keyboard navigation
  5. Локализацию - разные форматы дат в разных языках
  6. Тестирование - проверь граничные даты (конец февраля, високосные годы)

Заключение

Реализация календаря требует понимания работы с датами в JavaScript, логики генерации сетки дней и правильного отображения. В production используй проверенные библиотеки и тщательно тестируй граничные случаи.

Как реализовать календарь? | PrepBro