В процессе интеграции, загрузки данных из файлов Excel, текстовых документов или сторонних систем программисты 1С часто сталкиваются со строковыми представлениями периодов, например «Ноябрь 2016», «май 2023» или «21 ноября 2017г.». Стандартная задача в таком случае — преобразовать текстовое значение в полноценный тип Дата, указывающий на начало соответствующего месяца (например, 01.11.2016 00:00:00).
Выясним причину, почему стандартные средства платформы не могут обработать такую строку напрямую, и детально разберем несколько практических способов решения: от простых и компактных до универсальных парсеров.
Встроенный конструктор Дата(<Строка>) платформы 1С:Предприятие крайне требователен к формату входной строки. Преобразование без ошибок срабатывает только в строго определенных ситуациях:
"ГГГГММДД" или "ГГГГММДДЧЧММСС" (например, Дата("20161101"))."01.11.2016 00:00:00").Если передать в конструктор строку с текстовым названием месяца или произвольными разделителями, платформа сгенерирует исключение: «Преобразование значения к типу Дата не может быть выполнено». В платформе 1С отсутствует встроенный аналог функций обратного форматирования по шаблону (таких как strptime в C/Python), поэтому разбор текстовых периодов необходимо реализовывать программно.
Проанализируем базовый подход. Если формат входных данных заранее известен и гарантированно содержит месяц словом и четырехзначный год, проще всего выделить подстроку года и определить номер месяца по первым трем символам. Сравнение первых трех букв помогает избежать ошибок, связанных с падежами (например, «ноябрь» и «ноября» имеют одинаковый префикс «ноя»).
Посмотрим на пример функции разбора:
Функция ПолучитьДатуИзМесяцГод(Знач СтрокаПериода) Экспорт
СтрокаПериода = СокрЛП(СтрокаПериода);
// Получаем первые три символа названия месяца в нижнем регистре
ПрефиксМесяца = НРег(Лев(СтрокаПериода, 3));
// Получаем последние 4 символа строки, предполагая четырехзначный год
ЧислоГода = Число(Прав(СтрокаПериода, 4));
ЧислоМесяца = 0;
Если ПрефиксМесяца = "янв" Тогда
ЧислоМесяца = 1;
ИначеЕсли ПрефиксМесяца = "фев" Тогда
ЧислоМесяца = 2;
ИначеЕсли ПрефиксМесяца = "мар" Тогда
ЧислоМесяца = 3;
ИначеЕсли ПрефиксМесяца = "апр" Тогда
ЧислоМесяца = 4;
ИначеЕсли ПрефиксМесяца = "май" Тогда
ЧислоМесяца = 5;
ИначеЕсли ПрефиксМесяца = "июн" Тогда
ЧислоМесяца = 6;
ИначеЕсли ПрефиксМесяца = "июл" Тогда
ЧислоМесяца = 7;
ИначеЕсли ПрефиксМесяца = "авг" Тогда
ЧислоМесяца = 8;
ИначеЕсли ПрефиксМесяца = "сен" Тогда
ЧислоМесяца = 9;
ИначеЕсли ПрефиксМесяца = "окт" Тогда
ЧислоМесяца = 10;
ИначеЕсли ПрефиксМесяца = "ноя" Тогда
ЧислоМесяца = 11;
ИначеЕсли ПрефиксМесяца = "дек" Тогда
ЧислоМесяца = 12;
КонецЕсли;
Если ЧислоМесяца = 0 Или ЧислоГода = 0 Тогда
Возврат '00010101';
КонецЕсли;
Возврат Дата(ЧислоГода, ЧислоМесяца, 1);
КонецФункции
Данный вариант работает максимально быстро, не требует создания лишних объектов и легко адаптируется под специфику конкретного формата.
Рассмотрим более элегантный способ, который избавляет разработчика от необходимости вручную прописывать все двенадцать условий в коде. Платформа 1С умеет форматировать дату в полное или сокращенное текстовое название месяца с учетом локализации с помощью параметров ДФ=ММММ (полное наименование) или ДФ=МММ (сокращенное).
Разберем по шагам создание соответствия названий месяцев их порядковым номерам в цикле от 1 до 12:
НачалоГода() и ДобавитьМесяц().Формат() получаем локализованное текстовое представление месяца.Соответствие парами: «Название месяца» -> Порядковый номер.СтрРазделить(), извлекаем номер месяца из подготовленного соответствия и год, после чего формируем итоговую дату.Посмотрим на реализацию алгоритма:
Функция ДатаИзТекстаМесяцГод(Знач СтрокаПериода) Экспорт
// Формируем соответствие названий месяцев номерам
ТаблицаМесяцев = Новый Соответствие;
БазоваяДата = НачалоГода(ТекущаяДата());
Для НомерМесяца = 1 По 12 Цикл
ИмяМесяца = ВРег(Формат(БазоваяДата, "ДФ=ММММ"));
ТаблицаМесяцев.Вставить(ИмяМесяца, НомерМесяца);
БазоваяДата = ДобавитьМесяц(БазоваяДата, 1);
КонецЦикла;
// Очищаем и делим строку по пробелам
ЧастиСтроки = СтрРазделить(СокрЛП(СтрокаПериода), " ", Ложь);
Если ЧастиСтроки.Количество() < 2 Тогда
Возврат '00010101';
КонецЕсли;
ТекстовыйМесяц = ВРег(ЧастиСтроки[0]);
НомерМесяца = ТаблицаМесяцев[ТекстовыйМесяц];
// Извлекаем цифры года из второй части
Год = Число(ЧастиСтроки[1]);
Если НомерМесяца = Неопределено Или Год = 0 Тогда
Возврат '00010101';
КонецЕсли;
Возврат Дата(Год, НомерМесяца, 1);
КонецФункции
Важный нюанс: при частых вызовах в циклах перебора таблицы соответствия месяцев не следует создавать повторно на каждой итерации. Рекомендуется закэшировать соответствие в глобальной переменной модуля или передавать готовый объект в качестве параметра.
В задачах импорта текстовые даты нередко приходят в непредсказуемом виде: с указанием времени, дней недели, сокращений на русском или английском языках (например, «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 Тогда
Результат = Дата(Год, Месяц, День);
КонецЕсли;
Возврат Результат;
КонецФункции
Если разработка ведется в типовой конфигурации на базе Библиотеки Стандартных Подсистем (БСП), полезно проверить наличие общих модулей подсистемы базовой функциональности:
"01.11.2016", "2016-11-01"). В отличие от стандартного конструктора Дата(), при некорректном формате функция не вызывает исключение, а возвращает пустую дату '00010101'."2016-11-01" или "2016-11-01T00:00:00") самым быстрым платформенным решением является вызов XMLЗначение(Тип("Дата"), СтрокаISO).СтрНайтиПоРегулярномуВыражению и СтрЗаменитьПоРегулярномуВыражению. С помощью регулярного выражения вида "([А-Яа-я]+)\s+(\d{4})" можно в одну строчку выделить текстовый месяц и цифры года из строки любой сложности.Подведем итог: если формат входной строки строго регламентирован (например, «Ноябрь 2016»), оптимально использовать разбор по первым трем символам через конструкцию Если ... ИначеЕсли или предварительно подготовленное соответствие месяцев. При работе со сложными или нестабильными форматами надежнее применять посимвольный синтаксический анализ либо регулярные выражения.