time-tracking-eltex/backend/utils/timeCalculator.js

134 lines
5.2 KiB
JavaScript

// Утилиты для расчета рабочих часов
/**
* Проверяет, является ли день рабочим (понедельник-пятница)
* @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,
};