// Утилиты для расчета рабочих часов /** * Проверяет, является ли день рабочим (понедельник-пятница) * @param {Date} date - Дата для проверки * @returns {boolean} true если рабочий день */ function isWorkingDay(date) { const day = date.getDay(); // 0 = воскресенье, 1 = понедельник, ..., 6 = суббота return day >= 1 && day <= 5; // понедельник-пятница } /** * Преобразует время в формате HH:MM в минуты от начала дня * @param {string} time - Время в формате HH:MM * @returns {number} Минуты от начала дня */ function timeToMinutes(time) { if (!time) return 0; const [hours, minutes] = time.split(":").map(Number); return hours * 60 + minutes; } /** * Преобразует минуты от начала дня в формат HH:MM * @param {number} minutes - Минуты от начала дня * @returns {string} Время в формате HH:MM */ function minutesToTime(minutes) { const hours = Math.floor(minutes / 60); const mins = minutes % 60; return `${hours.toString().padStart(2, "0")}:${mins .toString() .padStart(2, "0")}`; } /** * Рассчитывает рабочие часы между двумя датами/временами * Рабочий день: 09:00 - 18:00 * Обед: 12:00 - 13:00 (не учитывается в расчетах) * Только рабочие дни (понедельник-пятница) * * @param {string} startDate - Дата начала (YYYY-MM-DD) * @param {string} startTime - Время начала (HH:MM) * @param {string} endDate - Дата окончания (YYYY-MM-DD) или null для открытой записи * @param {string} endTime - Время окончания (HH:MM) или null для открытой записи * @returns {number} Количество рабочих часов */ function calculateWorkHours(startDate, startTime, endDate, endTime) { const startDateTime = new Date(`${startDate}T${startTime || "09:00"}`); const endDateTime = endDate && endTime ? new Date(`${endDate}T${endTime}`) : null; // Если нет даты окончания - возвращаем 0 (открытая запись) if (!endDateTime) { return 0; } // Если дата окончания раньше даты начала - ошибка if (endDateTime < startDateTime) { throw new Error("Дата окончания не может быть раньше даты начала"); } const WORK_START = 9 * 60; // 09:00 в минутах const WORK_END = 18 * 60; // 18:00 в минутах const LUNCH_START = 12 * 60; // 12:00 в минутах const LUNCH_END = 13 * 60; // 13:00 в минутах let totalMinutes = 0; let currentDate = new Date(startDate); // Проходим по каждому дню от начала до окончания while ( currentDate <= (endDateTime ? new Date(endDate) : new Date(startDate)) ) { if (isWorkingDay(currentDate)) { const dayStart = new Date(currentDate); dayStart.setHours(0, 0, 0, 0); const dayEnd = new Date(currentDate); dayEnd.setHours(23, 59, 59, 999); // Определяем время начала работы в этот день let workStartMinutes = WORK_START; if (currentDate.toDateString() === new Date(startDate).toDateString()) { // Первый день - берем время начала работы const startMinutes = startDateTime.getHours() * 60 + startDateTime.getMinutes(); workStartMinutes = Math.max(startMinutes, WORK_START); } // Определяем время окончания работы в этот день let workEndMinutes = WORK_END; if ( endDateTime && currentDate.toDateString() === new Date(endDate).toDateString() ) { // Последний день - берем время окончания работы const endMinutes = endDateTime.getHours() * 60 + endDateTime.getMinutes(); workEndMinutes = Math.min(endMinutes, WORK_END); } // Если время окончания раньше времени начала в этот день - пропускаем if (workEndMinutes > workStartMinutes) { let dayMinutes = workEndMinutes - workStartMinutes; // Вычитаем обед, если он попадает в рабочий интервал if (workStartMinutes < LUNCH_END && workEndMinutes > LUNCH_START) { const lunchOverlapStart = Math.max(workStartMinutes, LUNCH_START); const lunchOverlapEnd = Math.min(workEndMinutes, LUNCH_END); if (lunchOverlapEnd > lunchOverlapStart) { dayMinutes -= lunchOverlapEnd - lunchOverlapStart; } } totalMinutes += Math.max(0, dayMinutes); } } // Переходим к следующему дню currentDate.setDate(currentDate.getDate() + 1); } return totalMinutes / 60; // Возвращаем часы } module.exports = { calculateWorkHours, isWorkingDay, timeToMinutes, minutesToTime, };