Как из строки с названием месяца и года получить дату в 1С?

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

В процессе интеграции, загрузки данных из файлов Excel, текстовых документов или сторонних систем программисты 1С часто сталкиваются со строковыми представлениями периодов, например «Ноябрь 2016», «май 2023» или «21 ноября 2017г.». Стандартная задача в таком случае — преобразовать текстовое значение в полноценный тип Дата, указывающий на начало соответствующего месяца (например, 01.11.2016 00:00:00).

Выясним причину, почему стандартные средства платформы не могут обработать такую строку напрямую, и детально разберем несколько практических способов решения: от простых и компактных до универсальных парсеров.

Ограничения стандартного конструктора Дата()

Встроенный конструктор Дата(<Строка>) платформы 1С:Предприятие крайне требователен к формату входной строки. Преобразование без ошибок срабатывает только в строго определенных ситуациях:

  1. Каноническая непрерывная цифровая последовательность вида "ГГГГММДД" или "ГГГГММДДЧЧММСС" (например, Дата("20161101")).
  2. Полное совпадение строки с региональными настройками информационной базы или операционной системы (например, "01.11.2016 00:00:00").

Если передать в конструктор строку с текстовым названием месяца или произвольными разделителями, платформа сгенерирует исключение: «Преобразование значения к типу Дата не может быть выполнено». В платформе 1С отсутствует встроенный аналог функций обратного форматирования по шаблону (таких как strptime в C/Python), поэтому разбор текстовых периодов необходимо реализовывать программно.

Способ 1. Простой разбор по префиксу месяца (по трем первым буквам)

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

Посмотрим на пример функции разбора:


Функция ПолучитьДатуИзМесяцГод(Знач СтрокаПериода) Экспорт
    
    СтрокаПериода = СокрЛП(СтрокаПериода);
    
    // Получаем первые три символа названия месяца в нижнем регистре
    ПрефиксМесяца = НРег(Лев(СтрокаПериода, 3));
    
    // Получаем последние 4 символа строки, предполагая четырехзначный год
    ЧислоГода = Число(Прав(СтрокаПериода, 4));
    
    ЧислоМесяца = 0;
    
    Если ПрефиксМесяца = "янв" Тогда
        ЧислоМесяца = 1;
    ИначеЕсли ПрефиксМесяца = "фев" Тогда
        ЧислоМесяца = 2;
    ИначеЕсли ПрефиксМесяца = "мар" Тогда
        ЧислоМесяца = 3;
    ИначеЕсли ПрефиксМесяца = "апр" Тогда
        ЧислоМесяца = 4;
    ИначеЕсли ПрефиксМесяца = "май" Тогда
        ЧислоМесяца = 5;
    ИначеЕсли ПрефиксМесяца = "июн" Тогда
        ЧислоМесяца = 6;
    ИначеЕсли ПрефиксМесяца = "июл" Тогда
        ЧислоМесяца = 7;
    ИначеЕсли ПрефиксМесяца = "авг" Тогда
        ЧислоМесяца = 8;
    ИначеЕсли ПрефиксМесяца = "сен" Тогда
        ЧислоМесяца = 9;
    ИначеЕсли ПрефиксМесяца = "окт" Тогда
        ЧислоМесяца = 10;
    ИначеЕсли ПрефиксМесяца = "ноя" Тогда
        ЧислоМесяца = 11;
    ИначеЕсли ПрефиксМесяца = "дек" Тогда
        ЧислоМесяца = 12;
    КонецЕсли;
    
    Если ЧислоМесяца = 0 Или ЧислоГода = 0 Тогда
        Возврат '00010101';
    КонецЕсли;
    
    Возврат Дата(ЧислоГода, ЧислоМесяца, 1);
    
КонецФункции

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

Способ 2. Динамическое построение таблицы месяцев через функцию Формат()

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

Разберем по шагам создание соответствия названий месяцев их порядковым номерам в цикле от 1 до 12:

  1. Формируем базовую дату на каждый месяц года с помощью функций НачалоГода() и ДобавитьМесяц().
  2. С помощью функции Формат() получаем локализованное текстовое представление месяца.
  3. Заполняем объект Соответствие парами: «Название месяца» -> Порядковый номер.
  4. Разбиваем входящую строку на составляющие с помощью СтрРазделить(), извлекаем номер месяца из подготовленного соответствия и год, после чего формируем итоговую дату.

Посмотрим на реализацию алгоритма:


Функция ДатаИзТекстаМесяцГод(Знач СтрокаПериода) Экспорт
    
    // Формируем соответствие названий месяцев номерам
    ТаблицаМесяцев = Новый Соответствие;
    БазоваяДата = НачалоГода(ТекущаяДата());
    
    Для НомерМесяца = 1 По 12 Цикл
        ИмяМесяца = ВРег(Формат(БазоваяДата, "ДФ=ММММ"));
        ТаблицаМесяцев.Вставить(ИмяМесяца, НомерМесяца);
        БазоваяДата = ДобавитьМесяц(БазоваяДата, 1);
    КонецЦикла;
    
    // Очищаем и делим строку по пробелам
    ЧастиСтроки = СтрРазделить(СокрЛП(СтрокаПериода), " ", Ложь);
    
    Если ЧастиСтроки.Количество() < 2 Тогда
        Возврат '00010101';
    КонецЕсли;
    
    ТекстовыйМесяц = ВРег(ЧастиСтроки[0]);
    НомерМесяца = ТаблицаМесяцев[ТекстовыйМесяц];
    
    // Извлекаем цифры года из второй части
    Год = Число(ЧастиСтроки[1]);
    
    Если НомерМесяца = Неопределено Или Год = 0 Тогда
        Возврат '00010101';
    КонецЕсли;
    
    Возврат Дата(Год, НомерМесяца, 1);
    
КонецФункции

Важный нюанс: при частых вызовах в циклах перебора таблицы соответствия месяцев не следует создавать повторно на каждой итерации. Рекомендуется закэшировать соответствие в глобальной переменной модуля или передавать готовый объект в качестве параметра.

Способ 3. Универсальный лексический парсер дат

В задачах импорта текстовые даты нередко приходят в непредсказуемом виде: с указанием времени, дней недели, сокращений на русском или английском языках (например, «NOV 2016», «2016-11», «Ноябрь 16»). Для решения подобных задач создают универсальные парсеры, которые анализируют входящую строку посимвольно, разбивая ее на цифровые и буквенные лексемы.

Рассмотрим пример универсальной функции, выполняющей лексический разбор строки, распознавание ролей компонентов (день, месяц, год, час, минута, секунда) и сборку даты:


&НаКлиентеНаСервереБезКонтекста
Функция ДатаИзСтрокиУниверсальная(СтрокаВходная) Экспорт
    
    Результат = Неопределено;
    
    // Справочник наименований месяцев для русской и английской локалей
    Месяцы = Новый Соответствие;
    Месяцы.Вставить("ЯНВ", 1);  Месяцы.Вставить("ФЕВ", 2);
    Месяцы.Вставить("МАР", 3);  Месяцы.Вставить("АПР", 4);
    Месяцы.Вставить("МАЙ", 5);  Месяцы.Вставить("МАЯ", 5);
    Месяцы.Вставить("ИЮН", 6);  Месяцы.Вставить("ИЮЛ", 7);
    Месяцы.Вставить("АВГ", 8);  Месяцы.Вставить("СЕН", 9);
    Месяцы.Вставить("ОКТ", 10); Месяцы.Вставить("НОЯ", 11);
    Месяцы.Вставить("ДЕК", 12);
    Месяцы.Вставить("JAN", 1);  Месяцы.Вставить("FEB", 2);
    Месяцы.Вставить("MAR", 3);  Месяцы.Вставить("APR", 4);
    Месяцы.Вставить("MAY", 5);  Месяцы.Вставить("JUN", 6);
    Месяцы.Вставить("JUL", 7);  Месяцы.Вставить("AUG", 8);
    Месяцы.Вставить("SEP", 9);  Месяцы.Вставить("OCT", 10);
    Месяцы.Вставить("NOV", 11); Месяцы.Вставить("DEC", 12);
    
    // Разбиение строки на части: числа и слова
    ЧастиСтроки = Новый Массив;
    ТекущаяПодстрока = "";
    ТекущийВид = "";
    
    Для Счетчик = 1 По СтрДлина(СтрокаВходная) + 1 Цикл
        Символ = ?(Счетчик = СтрДлина(СтрокаВходная) + 1, " ", ВРег(Сред(СтрокаВходная, Счетчик, 1)));
        
        Если Символ >= "0" И Символ <= "9" Тогда
            ВидСимвола = "Цифры";
        ИначеЕсли (Символ >= "А" И Символ <= "Я") Или (Символ >= "A" И Символ <= "Z") Тогда
            ВидСимвола = "Буквы";
        Иначе
            ВидСимвола = "Разделитель";
        КонецЕсли;
        
        Если ТекущийВид <> ВидСимвола Тогда
            Если ТекущийВид = "Цифры" Тогда
                ЧастиСтроки.Добавить(Новый Структура("Тип, Значение", "Число", Число(ТекущаяПодстрока)));
            ИначеЕсли ТекущийВид = "Буквы" Тогда
                // Проверяем совпадение по первым трем буквам месяца
                КлючМесяца = Лев(ТекущаяПодстрока, 3);
                Если Месяцы[КлючМесяца] <> Неопределено Тогда
                    ЧастиСтроки.Добавить(Новый Структура("Тип, Значение", "Месяц", Месяцы[КлючМесяца]));
                КонецЕсли;
            КонецЕсли;
            ТекущаяПодстрока = "";
        КонецЕсли;
        
        ТекущийВид = ВидСимвола;
        ТекущаяПодстрока = ТекущаяПодстрока + Символ;
    КонецЦикла;
    
    // Определение компонентов даты
    Год = 0; Месяц = 1; День = 1;
    
    Для Каждого Элемент Из ЧастиСтроки Цикл
        Если Элемент.Тип = "Месяц" Тогда
            Месяц = Элемент.Значение;
        ИначеЕсли Элемент.Тип = "Число" Тогда
            Если Элемент.Значение > 1000 Тогда
                Год = Элемент.Значение;
            ИначеЕсли Элемент.Значение < 100 И Год = 0 И Элемент.Значение > 31 Тогда
                // Обработка двузначного года (например, 16 -> 2016)
                Год = ?(Элемент.Значение < 50, 2000 + Элемент.Значение, 1900 + Элемент.Значение);
            ИначеЕсли Элемент.Значение <= 31 И День = 1 Тогда
                День = Элемент.Значение;
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    
    Если Год > 0 Тогда
        Результат = Дата(Год, Месяц, День);
    КонецЕсли;
    
    Возврат Результат;
    
КонецФункции

Способ 4. Типовые функции БСП и регулярные выражения

Если разработка ведется в типовой конфигурации на базе Библиотеки Стандартных Подсистем (БСП), полезно проверить наличие общих модулей подсистемы базовой функциональности:

  1. СтроковыеФункцииКлиентСервер.СтрокаВДату(): Функция выполняет безопасное преобразование строк числовых форматов (например, "01.11.2016", "2016-11-01"). В отличие от стандартного конструктора Дата(), при некорректном формате функция не вызывает исключение, а возвращает пустую дату '00010101'.
  2. XMLЗначение: Для стандартных дат формата ISO (например, "2016-11-01" или "2016-11-01T00:00:00") самым быстрым платформенным решением является вызов XMLЗначение(Тип("Дата"), СтрокаISO).
  3. Регулярные выражения (начиная с платформы 8.3.23): На стороне сервера доступны встроенные методы глобального контекста СтрНайтиПоРегулярномуВыражению и СтрЗаменитьПоРегулярномуВыражению. С помощью регулярного выражения вида "([А-Яа-я]+)\s+(\d{4})" можно в одну строчку выделить текстовый месяц и цифры года из строки любой сложности.

Подведем итог: если формат входной строки строго регламентирован (например, «Ноябрь 2016»), оптимально использовать разбор по первым трем символам через конструкцию Если ... ИначеЕсли или предварительно подготовленное соответствие месяцев. При работе со сложными или нестабильными форматами надежнее применять посимвольный синтаксический анализ либо регулярные выражения.

← На главную