Как определить номер недели по стандарту ISO 8601 (ГОСТ ИСО 8601-2001) в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с датами в 1С часто возникает необходимость определить номер недели для той или иной даты. Стандартные функции платформы 1С, такие как Неделя() и НеделяГода(), используют так называемую «сквозную» нумерацию недель. Это означает, что 1 января всегда относится к первой неделе года, а 31 декабря — к последней неделе текущего года, независимо от дня недели, на который эти даты приходятся.

Однако существуют международные стандарты, такие как ISO 8601 (и соответствующий ему ГОСТ ИСО 8601-2001 в Российской Федерации), которые определяют нумерацию недель иначе. В соответствии с этим стандартом, первой неделей года считается та, которая содержит первый четверг текущего года. Это правило также эквивалентно тому, что первая неделя года — это та, которая содержит 4 января. Главный принцип заключается в том, что большая часть дней первой недели (не менее четырех) должна приходиться на новый год.

Такое расхождение приводит к тому, что даты в начале января (например, 1-3 января) могут фактически относиться к последней неделе предыдущего года (52-й или 53-й), а даты в конце декабря (например, 29-31 декабря) — к первой неделе следующего года. Поскольку встроенные функции 1С не поддерживают эту логику, нам потребуется реализовать собственный алгоритм для корректного расчета номера недели по стандарту ISO 8601.

Ключевые принципы определения номера недели по стандарту ISO 8601

Прежде чем мы перейдем к реализации алгоритмов, давайте подробнее разберем основные правила стандарта ISO 8601, чтобы убедиться в правильности понимания задачи:

  1. Начало недели: По стандарту ISO 8601, календарная неделя всегда начинается с понедельника и длится семь дней, заканчиваясь воскресеньем. Это важное отличие от некоторых других систем, где неделя может начинаться с воскресенья.
  2. Первая неделя года (Неделя 01): Определение первой недели года является ключевым моментом стандарта.
    • Первой календарной неделей года считается та неделя, которая содержит первый четверг текущего года.
    • Эквивалентное правило: первая неделя года — это та, которая содержит 4 января.
    • Еще одно эквивалентное правило: для того чтобы неделя считалась первой неделей нового года, большая часть её дней (то есть не менее четырех из семи) должна приходиться на новый календарный год.
    Это означает, что 1, 2 или 3 января могут относиться к последней неделе предыдущего года, если первый четверг текущего года приходится на 5, 6 или 7 января соответственно.
  3. Последняя неделя года: Последней календарной неделей года является неделя, которая непосредственно предшествует первой календарной неделе следующего года. Год по ISO может иметь 52 или 53 недели. Неделя, содержащая 28 декабря, всегда является последней неделей года по ISO.
  4. Количество недель в году: Год по стандарту ISO может содержать 52 или 53 недели. Это зависит от того, как дни в начале или конце года распределяются по неделям. Год имеет 53 недели, если он заканчивается в четверг, или если он является високосным годом и заканчивается в среду.
  5. Переход между годами:
    • Даты в конце декабря (например, 29-31 декабря) могут относиться к первой неделе следующего года, если эти дни входят в неделю, содержащую первый четверг следующего года.
    • И наоборот, даты в начале января (например, 1-3 января) могут относиться к последней неделе предыдущего года (52-й или 53-й), если они попадают в неделю, первый четверг которой был в предыдущем году.

Почему стандартные функции 1С не подходят для расчета по ISO 8601

Рассмотрим пример, который хорошо иллюстрирует разницу между нумерацией 1С и стандартом ISO 8601. Возьмем дату 01.01.2017:

Именно из-за таких расхождений нам необходимо разработать или использовать адаптированный алгоритм.

Алгоритм 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-ю неделю года и может давать неточные результаты в сложных пограничных случаях перехода между годами, когда неделя фактически относится к другому году.

Алгоритм 2: Расчет номера недели по математической формуле

Более точный и универсальный подход основан на математической формуле, которая напрямую вычисляет номер недели по 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-й недели в году, если это применимо.

Алгоритм 3: Комплексный запрос 1С для определения номера недели и "недельного года" по ISO 8601

Для более продвинутых сценариев, особенно при работе с отчетами или динамическими списками в 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. Простая корректировка стандартной функции НеделяГода() (Алгоритм 1): Этот подход является самым простым в реализации и может подойти для нестрогих требований, где небольшие погрешности на стыке лет допустимы, и нет необходимости учитывать 53-ю неделю. Однако для полного соответствия стандарту ISO 8601 он не рекомендован.
  2. Математическая формула (Алгоритм 2): Этот метод является более точным и универсальным. Он подходит для большинства программных реализаций в коде 1С, когда требуется получить корректный номер недели без учета "недельного года" как отдельного параметра. Мы рекомендуем использовать именно этот подход для получения максимально точного номера недели в программном коде 1С. Он достаточно компактен и эффективен.
  3. Комплексный запрос (Алгоритм 3): Этот алгоритм представляет собой наиболее полное и robustное решение. Он не только вычисляет номер недели, но и определяет корректный "недельный год", что крайне важно для аналитики и отчетности, требующей полной совместимости с ISO 8601. Он идеально подходит для использования в запросах 1С, системе компоновки данных (СКД) или при построении сложных отчетов, где необходима полная информация о неделях и годах по ISO.

При выборе алгоритма учитывайте контекст использования. Если вам нужен быстрый расчет в коде и не требуется отдельно определять "недельный год", используйте математическую формулу. Если вы строите сложный отчет или работаете с данными на уровне запросов, где важно определить не только номер недели, но и соответствующий ISO-год, комплексный запрос будет более подходящим и мощным решением. В любом случае, избегайте использования стандартных функций 1С для этой конкретной задачи, если требуется строгое соответствие ISO 8601.

← На главную