ISO-недели

Понятие ISO-недели

Стандарт ISO 8601 определяет систему недель, которая отличается от привычного календарного деления года. Основные правила:

  • неделя начинается с понедельника;
  • первая неделя года — та, которая содержит первый четверг года;
  • каждая ISO-неделя имеет номер от 1 до 52 или 53;
  • ISO-год может не совпадать с календарным годом для дат в начале января и конце декабря.

Например:

  • 2021-01-01 относится к 53-й ISO-неделе 2020 года;
  • 2022-01-03 уже принадлежит первой ISO-неделе 2022 года.

В Luxon работа с ISO-неделями встроена напрямую в объект DateTime.


Получение номера ISO-недели

Свойство weekNumber возвращает номер недели по ISO 8601.

import { DateTime } from "luxon";

const dt = DateTime.fromISO("2024-03-15");

console.log(dt.weekNumber);

Результат:

11

Получение ISO-года

Для ISO-недель используется отдельное понятие года — weekYear.

const dt = DateTime.fromISO("2021-01-01");

console.log(dt.weekYear);

Результат:

2020

Хотя календарный год даты — 2021, ISO-неделя относится к предыдущему ISO-году.


Получение дня недели

Luxon использует ISO-нумерацию дней:

День Значение
Понедельник 1
Вторник 2
Среда 3
Четверг 4
Пятница 5
Суббота 6
Воскресенье 7

Свойство weekday:

const dt = DateTime.now();

console.log(dt.weekday);

Формирование даты из ISO-недели

Luxon позволяет создавать дату через ISO-компоненты:

  • weekYear
  • weekNumber
  • weekday
const dt = DateTime.fromObject({
  weekYear: 2024,
  weekNumber: 20,
  weekday: 1
});

console.log(dt.toISODate());

Результат:

2024-05-13

Это понедельник 20-й ISO-недели 2024 года.


Создание даты без weekday

Если weekday не указан, Luxon использует понедельник.

const dt = DateTime.fromObject({
  weekYear: 2024,
  weekNumber: 1
});

console.log(dt.toISODate());

Результат:

2024-01-01

Начало и конец ISO-недели

Методы startOf() и endOf() поддерживают единицу измерения week.

Начало недели

const dt = DateTime.fromISO("2024-03-14");

console.log(dt.startOf("week").toISO());

Результат:

2024-03-11T00:00:00.000+03:00

Конец недели

console.log(dt.endOf("week").toISO());

Результат:

2024-03-17T23:59:59.999+03:00

В Luxon неделя всегда начинается с понедельника в соответствии с ISO.


Переход между ISO-неделями

Следующая неделя

const nextWeek = dt.plus({ weeks: 1 });

console.log(nextWeek.toISODate());

Предыдущая неделя

const prevWeek = dt.minus({ weeks: 1 });

console.log(prevWeek.toISODate());

Разница между календарным годом и ISO-годом

Особенно важно учитывать различия в последних и первых днях года.

const dt = DateTime.fromISO("2020-12-31");

console.log(dt.year);
console.log(dt.weekYear);
console.log(dt.weekNumber);

Результат:

2020
2020
53

Другой пример:

const dt2 = DateTime.fromISO("2021-01-01");

console.log(dt2.year);
console.log(dt2.weekYear);
console.log(dt2.weekNumber);

Результат:

2021
2020
53

Получение количества недель в году

Свойство weeksInWeekYear показывает, сколько ISO-недель содержит ISO-год.

const dt = DateTime.fromISO("2020-06-01");

console.log(dt.weeksInWeekYear);

Результат:

53

Некоторые ISO-годы содержат 53 недели.


Проверка 53-й недели

const dt = DateTime.fromObject({
  weekYear: 2020,
  weekNumber: 53,
  weekday: 5
});

console.log(dt.toISODate());

Результат:

2021-01-01

53-я неделя 2020 ISO-года продолжается уже в календарном 2021 году.


Перебор дней ISO-недели

const start = DateTime.fromObject({
  weekYear: 2024,
  weekNumber: 10,
  weekday: 1
});

for (let i = 0; i < 7; i++) {
  const day = start.plus({ days: i });

  console.log(day.toISODate(), day.weekday);
}

Результат:

2024-03-04 1
2024-03-05 2
2024-03-06 3
2024-03-07 4
2024-03-08 5
2024-03-09 6
2024-03-10 7

Форматирование ISO-недель

Luxon поддерживает форматирование через toFormat().

Номер ISO-недели

const dt = DateTime.now();

console.log(dt.toFormat("WW"));

Пример результата:

21

ISO-год недели

console.log(dt.toFormat("kkkk"));

День недели

console.log(dt.toFormat("c"));

Формирование сложного ISO-представления

const dt = DateTime.now();

const result = dt.toFormat("kkkk-'W'WW-c");

console.log(result);

Пример:

2026-W21-6

Формат соответствует ISO 8601:

  • kkkk — ISO-год;
  • WW — ISO-неделя;
  • c — ISO-день недели.

Парсинг ISO-недельной даты

Luxon умеет разбирать ISO-недельные строки.

const dt = DateTime.fromFormat(
  "2024-W12-5",
  "kkkk-'W'WW-c"
);

console.log(dt.toISODate());

Результат:

2024-03-22

Работа с диапазонами недель

Для диапазонов удобно использовать Interval.

import { DateTime, Interval } from "luxon";

const start = DateTime.now().startOf("week");
const end = DateTime.now().endOf("week");

const interval = Interval.fromDateTimes(start, end);

console.log(interval.toString());

Сравнение недель

Проверка принадлежности одной неделе

function sameWeek(a, b) {
  return (
    a.weekYear === b.weekYear &&
    a.weekNumber === b.weekNumber
  );
}

const a = DateTime.fromISO("2024-03-12");
const b = DateTime.fromISO("2024-03-15");

console.log(sameWeek(a, b));

Результат:

true

Получение всех недель месяца

const start = DateTime.fromISO("2024-03-01");
const end = start.endOf("month");

const weeks = new Set();

let current = start;

while (current <= end) {
  weeks.add(`${current.weekYear}-${current.weekNumber}`);
  current = current.plus({ days: 1 });
}

console.log([...weeks]);

Генерация календарной сетки ISO-недель

const start = DateTime.fromISO("2024-01-01").startOf("week");

for (let week = 0; week < 4; week++) {
  const currentWeek = start.plus({ weeks: week });

  console.log(
    "Неделя:",
    currentWeek.weekNumber
  );

  for (let day = 0; day < 7; day++) {
    const currentDay = currentWeek.plus({ days: day });

    console.log(currentDay.toISODate());
  }
}

Ошибки при работе с ISO-неделями

Смешивание year и weekYear

Неправильно:

DateTime.fromObject({
  year: 2024,
  weekNumber: 10
});

Правильно:

DateTime.fromObject({
  weekYear: 2024,
  weekNumber: 10
});

ISO-компоненты должны использоваться совместно.


Использование воскресенья как первого дня

Luxon придерживается ISO 8601, поэтому:

dt.startOf("week")

всегда возвращает понедельник.


Игнорирование 53-й недели

Некоторые годы содержат 53 недели:

console.log(
  DateTime.fromISO("2020-01-01").weeksInWeekYear
);

Результат:

53

Практический пример: отчёты по ISO-неделям

function getReportKey(date) {
  return `${date.weekYear}-W${String(
    date.weekNumber
  ).padStart(2, "0")}`;
}

const dt = DateTime.fromISO("2024-05-15");

console.log(getReportKey(dt));

Результат:

2024-W20

Практический пример: поиск начала квартала по ISO-неделям

const dt = DateTime.now();

const quarterStart = dt
  .startOf("quarter")
  .startOf("week");

console.log(quarterStart.toISODate());

Практический пример: фильтрация данных по ISO-неделям

const records = [
  {
    date: "2024-03-12",
    value: 10
  },
  {
    date: "2024-03-15",
    value: 20
  },
  {
    date: "2024-03-25",
    value: 30
  }
];

const targetWeek = 11;

const filtered = records.filter(record => {
  const dt = DateTime.fromISO(record.date);

  return dt.weekNumber === targetWeek;
});

console.log(filtered);

Практический пример: группировка по ISO-неделям

const sales = [
  { date: "2024-03-11", amount: 100 },
  { date: "2024-03-12", amount: 150 },
  { date: "2024-03-18", amount: 200 }
];

const grouped = {};

for (const item of sales) {
  const dt = DateTime.fromISO(item.date);

  const key = `${dt.weekYear}-W${dt.weekNumber}`;

  if (!grouped[key]) {
    grouped[key] = [];
  }

  grouped[key].push(item);
}

console.log(grouped);

Практический пример: вычисление номера недели между двумя датами

const start = DateTime.fromISO("2024-01-01");
const end = DateTime.fromISO("2024-03-01");

const diff = end.diff(start, "weeks");

console.log(diff.weeks);

Практический пример: расписание на ISO-неделю

function createWeekSchedule(weekYear, weekNumber) {
  const days = [];

  const start = DateTime.fromObject({
    weekYear,
    weekNumber,
    weekday: 1
  });

  for (let i = 0; i < 7; i++) {
    days.push(
      start.plus({ days: i }).toISODate()
    );
  }

  return days;
}

console.log(
  createWeekSchedule(2024, 15)
);