При работе с датами в 1С часто возникает необходимость определить номер недели для той или иной даты. Стандартные функции платформы 1С, такие как Неделя() и НеделяГода(), используют так называемую «сквозную» нумерацию недель. Это означает, что 1 января всегда относится к первой неделе года, а 31 декабря — к последней неделе текущего года, независимо от дня недели, на который эти даты приходятся.
Однако существуют международные стандарты, такие как ISO 8601 (и соответствующий ему ГОСТ ИСО 8601-2001 в Российской Федерации), которые определяют нумерацию недель иначе. В соответствии с этим стандартом, первой неделей года считается та, которая содержит первый четверг текущего года. Это правило также эквивалентно тому, что первая неделя года — это та, которая содержит 4 января. Главный принцип заключается в том, что большая часть дней первой недели (не менее четырех) должна приходиться на новый год.
Такое расхождение приводит к тому, что даты в начале января (например, 1-3 января) могут фактически относиться к последней неделе предыдущего года (52-й или 53-й), а даты в конце декабря (например, 29-31 декабря) — к первой неделе следующего года. Поскольку встроенные функции 1С не поддерживают эту логику, нам потребуется реализовать собственный алгоритм для корректного расчета номера недели по стандарту ISO 8601.
Прежде чем мы перейдем к реализации алгоритмов, давайте подробнее разберем основные правила стандарта ISO 8601, чтобы убедиться в правильности понимания задачи:
Рассмотрим пример, который хорошо иллюстрирует разницу между нумерацией 1С и стандартом ISO 8601. Возьмем дату 01.01.2017:
Неделя(Дата) или НеделяГода(Дата) в 1С: Для 01.01.2017 обе эти функции вернут 1, так как они просто начинают отсчет недель с 1 января текущего года. Это не соответствует требуемому стандарту.Именно из-за таких расхождений нам необходимо разработать или использовать адаптированный алгоритм.
Начнем с относительно простого алгоритма, который пытается скорректировать результат стандартной функции НеделяГода() в 1С. Этот подход учитывает, если первый день года приходится на пятницу, субботу или воскресенье, относя его к предыдущему году. Однако, он не учитывает все нюансы 53-й недели и более сложные пограничные случаи.
Рассмотрим следующий пример кода:
Функция ПолучитьНомерНеделиISO_Простой(Дата) Экспорт
// Получаем номер недели с помощью стандартной функции 1С.
// Она считает 1 января первой неделей года.
ПеременнаяНеделя = НеделяГода(Дата);
// Определяем день недели первого дня текущего года.
// Функция ДеньНедели() в 1С возвращает 1 для понедельника, 7 для воскресенья.
// По стандарту ISO 8601, первая неделя — это та, которая содержит первый четверг.
// Если 1 января приходится на пятницу (5), субботу (6) или воскресенье (7),
// то неделя, содержащая 1 января, по ISO 8601 относится к предыдущему году.
// То есть, если ДеньНедели(НачалоГода(Дата)) > 4, значит, 1 января — это пятница, суббота или воскресенье.
// В этом случае первая неделя по ISO 8601 начинается позже.
Если ДеньНедели(НачалоГода(Дата)) > 4 Тогда
ПеременнаяНеделя = ПеременнаяНеделя - 1;
КонецЕсли;
// Если после корректировки номер недели стал 0,
// это означает, что дата фактически относится к последней неделе предыдущего года.
// В этом простом алгоритме мы делаем предположение, что это всегда 52-я неделя.
// Стоит отметить, что в действительности год может иметь и 53 недели,
// но данный алгоритм не учитывает этот нюанс.
Если ПеременнаяНеделя = 0 Тогда
ПеременнаяНеделя = 52; // Простая аппроксимация для последней недели предыдущего года
КонецЕсли;
Возврат ПеременнаяНеделя;
КонецФункции
Этот алгоритм может дать приблизительный результат для большинства дат, но, как уже упоминалось, он не полностью соответствует стандарту ISO 8601. Он не способен корректно определить 53-ю неделю года и может давать неточные результаты в сложных пограничных случаях перехода между годами, когда неделя фактически относится к другому году.
Более точный и универсальный подход основан на математической формуле, которая напрямую вычисляет номер недели по ISO 8601. Этот метод опирается на определение четверга той недели, в которую попадает искомая дата, и затем вычисляет номер дня в году для этого четверга. Именно год, к которому относится четверг, определяет "недельный год" и, соответственно, номер недели. Этот подход является наиболее часто используемым для программной реализации стандарта и был подтвержден автором темы как рабочий.
Давайте подробно разберем эту формулу и ее реализацию в 1С:
// Функция для определения номера недели по стандарту ISO 8601 для заданной даты
// Принимает параметр: ИсходнаяДата (тип Дата)
// Возвращает: НомерНеделиISO (число)
Функция ПолучитьНомерНеделиISO_Формула(ИсходнаяДата) Экспорт
// 1. Определяем четверг той недели, в которую попадает ИсходнаяДата.
// По стандарту ISO 8601, неделя начинается с понедельника.
// Функция ДеньНедели() в 1С возвращает: 1-понедельник, 2-вторник, ..., 7-воскресенье.
// Нам нужно сместить ИсходнуюДату так, чтобы получить четверг текущей ISO-недели.
// Для этого мы вычисляем разницу между днем недели ИсходнойДаты и четвергом (4).
// Затем мы добавляем (или вычитаем) это количество дней из ИсходнойДаты.
// Например:
// Если ИсходнаяДата = Понедельник (ДеньНедели=1), то разница (1-4) = -3.
// Нам нужно прибавить 3 дня, чтобы получить четверг (Понедельник + 3 дня = Четверг).
// Функция ДобавитьКДате(Дата, ЧастьДаты, Значение) позволяет это сделать.
// Сдвиг = -(ДеньНедели(ИсходнаяДата) - 4)
// Проверим логику сдвига:
// - Если ИсходнаяДата = ПН (ДеньНедели=1): Сдвиг = -(1-4) = -(-3) = 3 дня. (ПН + 3 дня = ЧТ)
// - Если ИсходнаяДата = ВТ (ДеньНедели=2): Сдвиг = -(2-4) = -(-2) = 2 дня. (ВТ + 2 дня = ЧТ)
// - Если ИсходнаяДата = СР (ДеньНедели=3): Сдвиг = -(3-4) = -(-1) = 1 день. (СР + 1 день = ЧТ)
// - Если ИсходнаяДата = ЧТ (ДеньНедели=4): Сдвиг = -(4-4) = -(0) = 0 дней. (ЧТ + 0 дней = ЧТ)
// - Если ИсходнаяДата = ПТ (ДеньНедели=5): Сдвиг = -(5-4) = -(1) = -1 день. (ПТ - 1 день = ЧТ)
// - Если ИсходнаяДата = СБ (ДеньНедели=6): Сдвиг = -(6-4) = -(2) = -2 дня. (СБ - 2 дня = ЧТ)
// - Если ИсходнаяДата = ВС (ДеньНедели=7): Сдвиг = -(7-4) = -(3) = -3 дня. (ВС - 3 дня = ЧТ)
// Таким образом, выражение -(ДеньНедели(ИсходнаяДата) - 4) корректно вычисляет количество дней,
// которое необходимо прибавить к ИсходнойДате, чтобы получить четверг текущей ISO-недели.
ДатаЧетвергаТекущейНедели = ДобавитьКДате(ИсходнаяДата, День, - (ДеньНедели(ИсходнаяДата) - 4));
// 2. Получаем номер дня в году для этого четверга.
// Это критически важно, так как именно год, к которому относится четверг текущей недели,
// определяет "недельный год" по ISO 8601. Функция ДеньГода() возвращает номер дня в году
// начиная с 1 для 1 января.
НомерДняВГодуДляЧетверга = ДеньГода(ДатаЧетвергаТекущейНедели);
// 3. Вычисляем номер недели по ISO 8601.
// Логика следующая:
// - Вычитаем 1 из НомерДняВГодуДляЧетверга, так как ДеньГода() начинает отсчет с 1.
// - Целочисленное деление полученного результата на 7 дает количество полных прошедших недель
// с начала "недельного года" до начала недели, содержащей наш четверг.
// - Прибавляем 1, чтобы получить номер текущей недели (т.е. "первая неделя", "вторая неделя" и т.д.).
НомерНеделиISO = Цел((НомерДняВГодуДляЧетверга - 1) / 7) + 1;
Возврат НомерНеделиISO;
КонецФункции
Этот алгоритм является более надежным и точным, поскольку он напрямую следует логике стандарта ISO 8601, основываясь на четверге недели. Он автоматически справляется с переходом между годами и корректно определяет, относится ли неделя к предыдущему или следующему календарному году, а также учитывает наличие 53-й недели в году, если это применимо.
Для более продвинутых сценариев, особенно при работе с отчетами или динамическими списками в 1С, может потребоваться не только номер недели, но и так называемый "недельный год" (ISO year), который может отличаться от календарного года для пограничных дат. Например, 31 декабря 2020 года по календарю относится к 2020 году, но по стандарту ISO 8601 может относиться к 1-й неделе 2021 года (и, соответственно, к ISO-году 2021).
Следующий запрос, представленный в обсуждении, позволяет получить оба значения — номер недели и "недельный год" — в соответствии со стандартом ISO 8601, а также сравнить их со стандартными функциями 1С. Этот запрос написан для использования в системе компоновки данных (СКД) или для выполнения в конструкторе запросов 1С. Параметр &Т в запросе представляет собой дату, для которой мы хотим определить номер недели и год.
ВЫБРАТЬ
// --- Расчет номера недели по ISO 8601 ---
ВЫБОР
// Условие 1: Если конец недели нашей даты приходится на 1-ю неделю по "сквозной" нумерации 1С
// И при этом 1 января "1С-года" этой недели (т.е. года, к которому по 1С относится конец недели)
// приходится на день недели > 4 (т.е. на пятницу, субботу или воскресенье).
// Это означает, что эта "первая неделя" 1С-года по ISO 8601 на самом деле принадлежит предыдущему ISO-году.
КОГДА НЕДЕЛЯ(КОНЕЦПЕРИОДА(&Т, НЕДЕЛЯ)) = 1
И ДЕНЬНЕДЕЛИ(НАЧАЛОПЕРИОДА(КОНЕЦПЕРИОДА(&Т, НЕДЕЛЯ), ГОД)) > 4
ТОГДА
// Вложенное условие: Если начало недели нашей даты (по 1С) также приходится на год,
// где 1 января > четверга.
ВЫБОР
КОГДА ДЕНЬНЕДЕЛИ(НАЧАЛОПЕРИОДА(НАЧАЛОПЕРИОДА(&Т, НЕДЕЛЯ), ГОД)) > 4
ТОГДА
// Тогда текущая неделя - это последняя неделя предыдущего ISO-года.
// Вычитаем 1 из номера недели 1С.
НЕДЕЛЯ(НАЧАЛОПЕРИОДА(&Т, НЕДЕЛЯ)) - 1
ИНАЧЕ
// Иначе, это все же первая неделя (например, если 1 января было в середине недели)
НЕДЕЛЯ(НАЧАЛОПЕРИОДА(&Т, НЕДЕЛЯ))
КОНЕЦ
ИНАЧЕ
// Условие 2: В остальных случаях (когда не первая неделя по 1С, или 1 января было в ПН-ЧТ)
ВЫБОР
// Если 1 января года, к которому относится конец недели нашей даты,
// приходится на день недели > 4 (т.е. ПТ, СБ, ВС).
// Это означает, что неделя, содержащая конец текущего года, может быть урезана.
КОГДА ДЕНЬНЕДЕЛИ(НАЧАЛОПЕРИОДА(КОНЕЦПЕРИОДА(&Т, НЕДЕЛЯ), ГОД)) > 4
ТОГДА
// Отнимаем 1 от номера недели 1С (корректируем из-за смещения начала ISO-года).
НЕДЕЛЯ(КОНЕЦПЕРИОДА(&Т, НЕДЕЛЯ)) - 1
ИНАЧЕ
// В большинстве случаев используем номер недели по концу периода 1С без корректировок.
НЕДЕЛЯ(КОНЕЦПЕРИОДА(&Т, НЕДЕЛЯ))
КОНЕЦ
КОНЕЦ КАК Неделя,
// --- Расчет "недельного года" по ISO 8601 ---
ВЫБОР
// Условие 1: Если дата находится в первой неделе по "сквозной" нумерации 1С.
КОГДА НЕДЕЛЯ(&Т) = 1
ТОГДА
// Вложенное условие: Если 1 января текущего календарного года приходится на ПТ, СБ или ВС.
// Это означает, что эта "первая" неделя по 1С на самом деле относится к предыдущему ISO-году.
ВЫБОР
КОГДА ДЕНЬНЕДЕЛИ(НАЧАЛОПЕРИОДА(&Т, ГОД)) > 4
ТОГДА
ГОД(&Т) - 1 // ISO-год будет предыдущим календарным годом.
ИНАЧЕ
ГОД(&Т) // Иначе ISO-год совпадает с текущим календарным годом.
КОНЕЦ
// Условие 2: Если календарный год нашей даты не равен году начала СЛЕДУЮЩЕЙ недели (по 1С).
// Это пограничный случай конца календарного года, когда текущая неделя может
// полностью или частично относиться к следующему ISO-году.
КОГДА ГОД(&Т) <> ГОД(НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(&Т, ДЕНЬ, 7), ГОД))
ТОГДА
ВЫБОР
// Вложенное условие: Если 1 января следующего календарного года приходится на ПТ, СБ или ВС.
// Тогда текущая неделя еще относится к текущему календарному году по ISO.
КОГДА ДЕНЬНЕДЕЛИ(НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(&Т, ДЕНЬ, 7), ГОД)) > 4
ТОГДА
ГОД(&Т) // ISO-год совпадает с текущим календарным годом.
ИНАЧЕ
// Иначе, это может быть следующей ISO-год.
ВЫБОР
// Если конец календарного года приходится на воскресенье,
// то текущая неделя еще в текущем ISO-году.
КОГДА ДЕНЬНЕДЕЛИ(КОНЕЦПЕРИОДА(&Т, ГОД)) = 7
ТОГДА
ГОД(&Т)
ИНАЧЕ
ГОД(&Т) + 1 // В этом случае, текущая неделя относится к следующему ISO-году.
КОНЕЦ
КОНЕЦ
ИНАЧЕ
ГОД(&Т) // В большинстве случаев ISO-год совпадает с календарным годом.
КОНЕЦ КАК Год,
// --- Для сравнения: стандартные функции 1С ---
НЕДЕЛЯ(&Т) КАК Неделя1С,
ГОД(&Т) КАК Год1С
ИЗ
ПРЕДСТАВЛЕНИЕ(&Т) КАК Т // Использование фиктивной таблицы для работы с параметром &Т
Этот запрос является очень мощным инструментом. Он не только вычисляет номер недели, но и определяет корректный "недельный год" в соответствии с ISO 8601. Он учитывает все сложные переходы на стыке лет, когда неделя может быть отнесена к предыдущему или следующему календарному году. Это особенно полезно для построения аналитических отчетов, требующих строгой совместимости с международными стандартами.
Мы рассмотрели несколько подходов к решению задачи определения номера недели по стандарту ISO 8601 в 1С. Выбор конкретного алгоритма будет зависеть от ваших специфических требований к точности, производительности и сложности реализации:
НеделяГода() (Алгоритм 1): Этот подход является самым простым в реализации и может подойти для нестрогих требований, где небольшие погрешности на стыке лет допустимы, и нет необходимости учитывать 53-ю неделю. Однако для полного соответствия стандарту ISO 8601 он не рекомендован.При выборе алгоритма учитывайте контекст использования. Если вам нужен быстрый расчет в коде и не требуется отдельно определять "недельный год", используйте математическую формулу. Если вы строите сложный отчет или работаете с данными на уровне запросов, где важно определить не только номер недели, но и соответствующий ISO-год, комплексный запрос будет более подходящим и мощным решением. В любом случае, избегайте использования стандартных функций 1С для этой конкретной задачи, если требуется строгое соответствие ISO 8601.