time-tracking-eltex/backend/utils/timeCalculator.js
Fovway 2186808c35 modified: README.md
Исправлено отображение дробных цифр:   backend/utils/timeCalculator.js
	modified:   frontend/README.md
2025-10-13 23:18:01 +07:00

134 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Утилиты для расчета рабочих часов
/**
* Проверяет, является ли день рабочим (понедельник-пятница)
* @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 Math.round((totalMinutes / 60) * 10) / 10; // Возвращаем часы с округлением до 1 знака после запятой
}
module.exports = {
calculateWorkHours,
isWorkingDay,
timeToMinutes,
minutesToTime,
};