Как преобразовать строковое название дня недели в ближайшую дату в 1С?

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

Мы часто сталкиваемся с задачами, где необходимо работать с календарными данными, представленными в виде строк, например, "Вторник", и преобразовывать их в конкретные даты. Цель состоит в том, чтобы получить дату ближайшего такого дня недели. В этой статье мы подробно рассмотрим, как это можно реализовать в 1С, используя различные подходы и применяя лучшие практики программирования.

Начальный подход к решению задачи: пошаговое определение даты

Наш коллега, столкнувшийся с подобной задачей, предложил следующий вариант решения, который, хотя и является рабочим, может быть значительно улучшен. Он использовал функцию НачалоНедели(ТекущаяДата()) для получения понедельника текущей недели, а затем прибавлял определенное количество секунд для каждого последующего дня.


// Пример получения данных из таблицы графика работы
// ГрафикПреподавателя - это некий объект, из которого мы получаем строки с днями недели
// Предположим, что каждая строка содержит название дня недели
// в первом элементе (индекс 0)

Для День=0 По КоличествоДнейРаботыПреподавателя-1 Цикл
    СтрокаГрафика = ГрафикПреподавателя.Получить(День); // Получаем текущую строку графика
    ДеньНеделиСтрокой = СтрокаГрафика.Получить(0); // Предполагается, что это строка типа "Понедельник"
    НачалоТекущейНедели = НачалоНедели(ТекущаяДата()); // Получаем дату понедельника текущей недели
    
    ДеньГрафика = Неопределено;
    
    Если ДеньНеделиСтрокой = "Понедельник" Тогда
        ДеньГрафика = НачалоТекущейНедели;
    ИначеЕсли ДеньНеделиСтрокой = "Вторник" Тогда
        ДеньГрафика = НачалоТекущейНедели + 24*3600; // Добавляем один день (24 часа * 3600 секунд)
    ИначеЕсли ДеньНеделиСтрокой = "Среда" Тогда
        ДеньГрафика = НачалоТекущейНедели + 2*24*3600; // Добавляем два дня
    ИначеЕсли ДеньНеделиСтрокой = "Четверг" Тогда
        ДеньГрафика = НачалоТекущейНедели + 3*24*3600; // Добавляем три дня
    ИначеЕсли ДеньНеделиСтрокой = "Пятница" Тогда
        ДеньГрафика = НачалоТекущейНедели + 4*24*3600; // Добавляем четыре дня
    ИначеЕсли ДеньНеделиСтрокой = "Суббота" Тогда
        ДеньГрафика = НачалоТекущейНедели + 5*24*3600; // Добавляем пять дней
    ИначеЕсли ДеньНеделиСтрокой = "Воскресенье" Тогда
        ДеньГрафика = НачалоТекущейНедели + 6*24*3600; // Добавляем шесть дней
    КонецЕсли;
    
    // ... дальнейшая работа с переменной ДеньГрафика, например:
    // Сообщить("Для дня " + ДеньНеделиСтрокой + " дата: " + Формат(ДеньГрафика, "ДФ=dd.MM.yyyy"));
КонецЦикла;

Этот код, несмотря на свою работоспособность, имеет несколько потенциальных недостатков, которые мы сейчас проанализируем:

  1. Избыточность кода: Использование длинной цепочки операторов Если-ИначеЕсли для каждого дня недели делает код громоздким, менее читаемым и сложным для поддержки. При необходимости добавления новых языков или изменения логики, потребуется редактировать множество строк.
  2. Проблемы с регистром: Данный код чувствителен к регистру входной строки дня недели. Например, если в графике будет указано "вторник" вместо "Вторник", код не сможет определить день недели и переменная ДеньГрафика останется неопределенной.
  3. Некорректное определение "ближайшего": Подход с отсчетом от НачалоНедели(ТекущаяДата()) не всегда корректно определяет "ближайший" день недели. Например, если сегодня среда, и мы ищем ближайший вторник, этот код найдет вторник *текущей* недели (который уже прошел), а не *ближайший* (который будет во вторник следующей недели). Задача подразумевает поиск дня, который наступит *после* или *в* текущий момент.
  4. Менее наглядная работа с датами: Добавление дней через секунды (24*3600) менее наглядно, чем использование специализированных методов работы с датами, предназначенных для этих целей.

Улучшенный подход: использование соответствия и функций 1С

Давайте рассмотрим, как мы можем значительно улучшить данный код, сделав его более гибким, читаемым и устойчивым к ошибкам. Мы будем использовать объект Соответствие для преобразования строковых названий и встроенные функции 1С для работы с датами.

Шаг 1: Преобразование строкового названия дня недели в числовой номер

Вместо множественных операторов Если-ИначеЕсли, мы можем использовать объект Соответствие для быстрого и эффективного преобразования строкового названия дня недели в его числовой номер. Это не только значительно сократит код, но и сделает его более понятным и легким для расширения.


// Функция для создания соответствия дней недели
// Ключи храним в нижнем регистре для удобства обработки
// Значения - числовые номера дней (1 - понедельник, 7 - воскресенье)
Функция ПолучитьСоответствиеДнейНедели() Экспорт
    ДниНедели = Новый Соответствие();
    ДниНедели.Вставить("понедельник", 1);
    ДниНедели.Вставить("вторник", 2);
    ДниНедели.Вставить("среда", 3);
    ДниНедели.Вставить("четверг", 4);
    ДниНедели.Вставить("пятница", 5);
    ДниНедели.Вставить("суббота", 6);
    ДниНедели.Вставить("воскресенье", 7);
    Возврат ДниНедели;
КонецФункции

Обратите внимание, что мы сохраняем ключи соответствия в нижнем регистре. Это позволяет нам легко обрабатывать входные строки, независимо от их регистра, используя функцию НРег() (НижнийРегистр) перед поиском в соответствие.


// Пример использования соответствия для получения номера дня недели
ПередаваемыйДеньНедели = "ВТОрНИК"; // Входная строка с названием дня недели
ДниНеделиМап = ПолучитьСоответствиеДнейНедели(); // Получаем наше соответствие
НомерЦелевогоДня = ДниНеделиМап.Получить(НРег(ПередаваемыйДеньНедели)); // Приводим к нижнему регистру и ищем

Если НомерЦелевогоДня = Неопределено Тогда
    // Обработка ошибки: если название дня недели не найдено в соответствие
    Сообщить("Ошибка: Неизвестное название дня недели: " + ПередаваемыйДеньНедели);
Иначе
    Сообщить("Для дня '" + ПередаваемыйДеньНедели + "' числовой номер: " + НомерЦелевогоДня);
КонецЕсли;

Шаг 2: Нахождение ближайшей даты целевого дня недели

Для определения ближайшей даты заданного дня недели, начиная с текущей даты, мы можем использовать простой, но эффективный циклический перебор. В 1С есть встроенная функция ДеньНедели(<Дата>), которая возвращает числовой номер дня недели для указанной даты (1 - понедельник, 7 - воскресенье).

Мы будем последовательно прибавлять по одному дню к текущей дате, пока ДеньНедели() для нашей искомой даты не совпадет с числовым номером целевого дня недели. Это гарантирует, что мы найдем именно ближайший день, который наступит.

Для добавления дней к дате более наглядно использовать метод ДобавитьДни() объекта Дата, а не прямое добавление секунд.


// Функция для получения ближайшей даты заданного дня недели
// Возвращает дату или Неопределено в случае ошибки
Функция ПолучитьБлижайшуюДатуДняНедели(СтрокаДняНедели) Экспорт
    
    // Получаем соответствие дней недели (предполагается, что функция уже определена)
    ДниНеделиМап = ПолучитьСоответствиеДнейНедели();
    
    // Определяем числовой номер целевого дня недели, учитывая регистр
    НомерЦелевогоДня = ДниНеделиМап.Получить(НРег(СтрокаДняНедели));
    
    Если НомерЦелевогоДня = Неопределено Тогда
        Сообщить("Ошибка: Некорректное название дня недели '" + СтрокаДняНедели + "'");
        Возврат Неопределено;
    КонецЕсли;
    
    ИскомаяДата = ТекущаяДата(); // Начинаем поиск с текущей даты и времени
    ИскомаяДата = НачалоДня(ИскомаяДата); // Обнуляем время, если важна только дата
    
    // Циклически добавляем дни, пока не найдем целевой день недели
    // Этот цикл найдет ближайший день недели, включая текущий, если он совпадает.
    // Если сегодня вторник и мы ищем вторник, то вернется сегодняшняя дата.
    // Если сегодня среда и мы ищем вторник, то вернется вторник следующей недели.
    Пока ДеньНедели(ИскомаяДата) <> НомерЦелевогоДня Цикл
        ИскомаяДата = ИскомаяДата.ДобавитьДни(1); // Добавляем один день
    КонецЦикла;
    
    Возврат ИскомаяДата;
    
КонецФункции

Этот подход корректно находит ближайший день недели. Если, например, сегодня вторник, и мы ищем вторник, функция сразу вернет текущую дату. Если сегодня понедельник, а мы ищем вторник, функция вернет завтрашнюю дату. Если сегодня среда, а мы ищем вторник, функция вернет вторник следующей недели, что и требуется для определения "ближайшего" дня в будущем или сегодня.

Пример комплексного использования

Теперь мы можем объединить эти функции для получения полной картины и использования в реальном сценарии, например, при обработке графика работы.


// Имитация таблицы графика работы
МассивДнейИзГрафика = Новый Массив();
МассивДнейИзГрафика.Добавить("Вторник");
МассивДнейИзГрафика.Добавить("Пятница");
МассивДнейИзГрафика.Добавить("понедельник"); // Проверяем работу с разным регистром
МассивДнейИзГрафика.Добавить("СУББОТА"); // Еще один вариант регистра
МассивДнейИзГрафика.Добавить("НеизвестныйДень"); // Проверяем обработку ошибок

Сообщить("--- Определение ближайших дат по графику ---");

Для Каждого ДеньНеделиИзГрафика Из МассивДнейИзГрафика Цикл
    БлижайшаяДата = ПолучитьБлижайшуюДатуДняНедели(ДеньНеделиИзГрафика);
    
    Если БлижайшаяДата <> Неопределено Тогда
        Сообщить("Для дня '" + ДеньНеделиИзГрафика + "' ближайшая дата: " + Формат(БлижайшаяДата, "ДФ=dd.MM.yyyy (ДДН)"));
    КонецЕсли;
КонецЦикла;

Дополнительные рекомендации и анализ производительности

Помимо представленного решения, существуют дополнительные аспекты, которые стоит учитывать для написания более качественного и эффективного кода в 1С:

  1. Производительность Соответствие vs Если-ИначеЕсли: Объект Соответствие (который по сути является хэш-таблицей) обычно обеспечивает сложность поиска O(1) в среднем случае, что значительно быстрее, чем последовательный перебор в цепочке Если-ИначеЕсли (сложность O(N)). Для небольшого количества элементов, как 7 дней недели, разница в производительности будет несущественной и практически незаметной. Однако в общем случае, при необходимости быстрого преобразования значений по ключу, Соответствие является предпочтительным выбором. Главное преимущество здесь — это читаемость и легкость поддержки кода.
  2. Читаемость и поддерживаемость кода: Использование Соответствие для преобразования строк в числа и метода ДобавитьДни() для манипуляций с датами делает код более чистым, понятным и легким для отладки и последующей поддержки. Снижается вероятность ошибок и упрощается понимание логики программы.
  3. Вынесение логики в функции: Как было предложено в обсуждении, вынесение логики в отдельные, специализированные функции (например, ПолучитьСоответствиеДнейНедели() и ПолучитьБлижайшуюДатуДняНедели()) значительно улучшает модульность кода. Это позволяет переиспользовать функции в разных частях проекта без дублирования, а также упрощает тестирование и отладку.
  4. Константы и их использование: Умножение констант (например, 24*3600) можно оставлять для наглядности в коде. Компилятор 1С обычно оптимизирует такие выражения на этапе компиляции, заменяя их на итоговое числовое значение (например, 86400). Таким образом, это не влияет на производительность выполнения. Однако, как было отмечено, использование специализированных методов типа ДобавитьДни(1) всё же является более предпочтительным, так как они более явно передают намерение программиста и улучшают читаемость кода.
  5. Обработка ошибок и исключительных ситуаций: Очень важно предусмотреть обработку случаев, когда входная строка дня недели может быть некорректной (например, "Вторникк", "ОдинДень" или пустая строка). Возврат Неопределено из функции и последующая проверка этого значения позволяют гибко реагировать на такие ситуации, предотвращая дальнейшие ошибки в программе.

Заключение

Мы подробно разобрали, как эффективно преобразовывать строковое название дня недели в ближайшую дату в 1С. Мы начали с базового, но менее оптимального решения и пришли к более гибкому, читаемому и производительному коду с использованием объекта Соответствие и встроенных функций для работы с датами, таких как НРег(), ДеньНедели(), ТекущаяДата(), НачалоДня() и ДобавитьДни(). Этот подход позволяет создавать надежные и легко поддерживаемые решения для работы с расписаниями, графиками и другими календарными данными в ваших конфигурациях 1С.

← На главную