Комментарии (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
- Используй библиотеку date-fns или day.js - избегай ошибок с датами
- Кэшируй вычисления - календарь генерируется один раз при монтировании
- Мемоизируй компоненты дней - если их много
- Поддерживай доступность - ARIA labels, keyboard navigation
- Локализацию - разные форматы дат в разных языках
- Тестирование - проверь граничные даты (конец февраля, високосные годы)
Заключение
Реализация календаря требует понимания работы с датами в JavaScript, логики генерации сетки дней и правильного отображения. В production используй проверенные библиотеки и тщательно тестируй граничные случаи.