const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; export function academicCalendarWeekNumber(academicYearStartIso, dateIso) { const academicYearStart = parseIsoDate(academicYearStartIso); const date = parseIsoDate(dateIso); const daysFromYearStart = (date.timestamp - academicYearStart.timestamp) / MILLISECONDS_PER_DAY; if (!Number.isInteger(daysFromYearStart) || daysFromYearStart < 0) { throw new RangeError('Дата ячейки не может быть раньше начала учебного года'); } const daysBeforeYearStartInFirstWeek = academicYearStart.dayOfWeek - 1; return Math.floor((daysBeforeYearStartInFirstWeek + daysFromYearStart) / 7) + 1; } function parseIsoDate(value) { const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(value)); if (!match) { throw new TypeError('Ожидалась дата в формате ГГГГ-ММ-ДД'); } const [, yearText, monthText, dayText] = match; const year = Number(yearText); const month = Number(monthText); const day = Number(dayText); const timestamp = Date.UTC(year, month - 1, day); const date = new Date(timestamp); if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) { throw new TypeError('Ожидалась дата в формате ГГГГ-ММ-ДД'); } const jsDayOfWeek = date.getUTCDay(); return { timestamp, dayOfWeek: jsDayOfWeek === 0 ? 7 : jsDayOfWeek }; }