Как вывести дату прописью в 1С:Предприятие в формате «тридцатое января две тысячи двенадцатого года»?

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

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

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

Подход 1: Реализация на основе списков значений и строковых операций

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

Шаг 1: Исходная реализация функции ДатаПрописью

Для начала, мы создадим функцию ДатаПрописью, которая принимает дату и возвращает её текстовое представление. В этой реализации мы используем несколько списков значений для хранения склонений дней, месяцев и компонентов года.


// Функция Дата прописью
// Параметры:
// ДП - Дата
// Возвращаемое значение:
// дата прописью
//
// Модификация для v8: Evgeny Migachev
Функция ДатаПрописью(ДП) Экспорт
    стрРез = "";
    Д=Формат(ДП,"ДЛФ=D"); // Получаем дату в формате "ДД.ММ.ГГГГ" или "ДД.ММ.ГГГГ ЧЧ:ММ:СС" и отбрасываем время
    
    // Списки значений для склонения
    спсМес = Новый СписокЗначений;
    спсМес.Добавить("января");
    спсМес.Добавить("февраля");
    спсМес.Добавить("марта");
    спсМес.Добавить("апреля");
    спсМес.Добавить("мая");
    спсМес.Добавить("июня");
    спсМес.Добавить("июля");
    спсМес.Добавить("августа");
    спсМес.Добавить("сентября");
    спсМес.Добавить("октября");
    спсМес.Добавить("ноября");
    спсМес.Добавить("декабря");
    
    спсЧисл = Новый СписокЗначений; // Числительные для дней (им. и род. падеж)
    спсЧисл.Добавить("первое","первого");
    спсЧисл.Добавить("второе","второго");
    спсЧисл.Добавить("третье","третьего");
    спсЧисл.Добавить("четвертое","четвертого");
    спсЧисл.Добавить("пятое","пятого");
    спсЧисл.Добавить("шестое","шестого");
    спсЧисл.Добавить("седьмое","седьмого");
    спсЧисл.Добавить("восьмое","восьмого");
    спсЧисл.Добавить("девятое","девятого");
    
    спсЧислИм = Новый СписокЗначений; // Числительные для тысяч (им. и род. падеж)
    спсЧислИм.Добавить("тысяча","тысячного"); // Для 1000, 1001, ...
    спсЧислИм.Добавить("две тысячи","двухтысячного"); // Для 2000, 2001, ...
    спсЧислИм.Добавить("три тысячи","трехтысячного");
    спсЧислИм.Добавить("четыре тысячи","четырёхтысячного");
    спсЧислИм.Добавить("пять","пятитысячного"); // Здесь, вероятно, подразумевается "пять тысяч", но записано "пять"
    спсЧислИм.Добавить("шесть","шеститысячного");
    спсЧислИм.Добавить("семь","семитысячного");
    спсЧислИм.Добавить("восемь","восьмитысячного");
    спсЧислИм.Добавить("девять","девятитысячного");
    
    спсСотни = Новый СписокЗначений; // Сотни для года
    спсСотни.Добавить("сто");
    спсСотни.Добавить("двести");
    спсСотни.Добавить("триста");
    спсСотни.Добавить("четыреста");
    спсСотни.Добавить("пятьсот");
    спсСотни.Добавить("шестьсот");
    спсСотни.Добавить("семьсот");
    спсСотни.Добавить("восемьсот");
    спсСотни.Добавить("девятьсот");
    
    спсДесИм = Новый СписокЗначений; // Десятки для года (им. и род. падеж)
    спсДесИм.Добавить("","десятого"); // Для 10, 20, 30...
    спсДесИм.Добавить("двадцать","двадцатого");
    спсДесИм.Добавить("тридцать","тридцатого");
    спсДесИм.Добавить("сорок","сорокового");
    спсДесИм.Добавить("пятьдесят","пятидесятого");
    спсДесИм.Добавить("шестьдесят","шестидесятого");
    спсДесИм.Добавить("семьдесят","семидесятого");
    спсДесИм.Добавить("восемьдесят","восьмидесятого");
    спсДесИм.Добавить("девяносто","девяностого");
    
    спсСубДесРод = Новый СписокЗначений; // Числительные от 11 до 19 (род. падеж)
    спсСубДесРод.Добавить("одиннадцатого");
    спсСубДесРод.Добавить("двенадцатого");
    спсСубДесРод.Добавить("тринадцатого");
    спсСубДесРод.Добавить("четырнадцатого");
    спсСубДесРод.Добавить("пятнадцатого");
    спсСубДесРод.Добавить("шестнадцатого");
    спсСубДесРод.Добавить("семнадцатого");
    спсСубДесРод.Добавить("восемнадцатого");
    спсСубДесРод.Добавить("девятнадцатого");
    
    спсДес = Новый СписокЗначений; // Десятки для дней (им. падеж)
    спсДес.Добавить("десятое");
    спсДес.Добавить("двадцатое","двадцать"); // Второе значение - для составных числительных, типа "двадцать первое"
    спсДес.Добавить("тридцатое","тридцать");
    спсДес.Добавить("сороковое","тридцать"); // Ошибка в исходном коде: должно быть "сорок"
    спсДес.Добавить("пятидесятое","тридцать"); // Ошибка в исходном коде: должно быть "пятьдесят"
    спсДес.Добавить("шестидесятое","тридцать"); // Ошибка в исходном коде: должно быть "шестьдесят"
    спсДес.Добавить("семидесятое","тридцать"); // Ошибка в исходном коде: должно быть "семьдесят"
    // Не хватает остальных десятков
    
    спсСубДес = Новый СписокЗначений; // Числительные от 11 до 19 для дней (им. падеж)
    спсСубДес.Добавить("одиннадцатое");
    спсСубДес.Добавить("двенадцатое");
    спсСубДес.Добавить("тринадцатое");
    спсСубДес.Добавить("четырнадцатое");
    спсСубДес.Добавить("пятнадцатое");
    спсСубДес.Добавить("шестнадцатое");
    спсСубДес.Добавить("семнадцатое");
    спсСубДес.Добавить("восемнадцатое");
    спсСубДес.Добавить("девятнадцатое");
    
    спсДаты = СтрЗаменить(СокрЛП(Д),".",Символы.ПС); // Разбираем строку даты на части по разделителю "."
    
    // Разбираем день
    стрДень = СокрЛП(Число(СтрПолучитьСтроку(спсДаты,1)));
    Если СтрДлина(стрДень)=1 Тогда
        стрДень = спсЧисл.Получить(Число(стрДень)-1).Значение; // Единицы
    Иначе
        десДень = Число(Лев(стрДень,1)); // Десятки
        едДень = Число(Прав(стрДень,1)); // Единицы
        Если едДень=0 Тогда
            стрДень = спсДес.Получить(десДень-1).Значение; // Круглые десятки (десятое, двадцатое...)
        ИначеЕсли десДень > 1 Тогда
            т = Строка(спсДес.Получить(десДень-1)); // Десятки (двадцать, тридцать...)
            стрДень = т+" "+Строка(спсЧисл.Получить(едДень-1).Значение); // Составные числительные (двадцать первое)
        Иначе // Десяток = 1 (11-19)
            стрДень = спсСубДес.Получить(едДень-1).Значение; // Субдесятки (одиннадцатое, двенадцатое...)
        КонецЕсли;
    КонецЕсли;
    
    // Разбираем месяц
    стрМес = спсМес.Получить(Число(СтрПолучитьСтроку(спсДаты,2))-1).Значение;
    
    // Разбираем год
    стрГод = СтрПолучитьСтроку(спсДаты,3);
    длинаГода = СтрДлина(стрГод);
    Если длинаГода=4 Тогда
        тыс = Сред(стрГод,1,1); // Тысячи
        сот = Сред(стрГод,2,1); // Сотни
        дес = Сред(стрГод,3,1); // Десятки
        ед = Сред(стрГод,4,1);  // Единицы
        
        _т = спсЧислИм.Получить(Число(тыс)-1).Значение; // Тысячи (две тысячи)
        
        Если (Число(сот)=0) и (Число(дес)=0) и (Число(ед)=0) Тогда // Круглые тысячи (две тысячи)
            миллениум = Строка(спсЧислИм.Получить(Число(тыс)-1));
            стрГод = миллениум;
        Иначе
            с = "";
            дс = "";
            е = "";
            
            Если Число(сот)<>0 Тогда
                с = спсСотни.Получить(Число(сот)-1).Значение; // Сотни (сто, двести)
            КонецЕсли;
            
            Если Число(дес)<>0 Тогда
                Если Число(ед)=0 Тогда // Круглые десятки (десятого, двадцатого)
                    дг = Строка(спсДесИм.Получить(Число(дес)-1));
                    дс = дг;
                Иначе // 11-19 (одиннадцатого, двенадцатого)
                    дс = спсСубДесРод.Получить(Число(ед)-1).Значение;
                КонецЕсли;
            КонецЕсли;
            
            Если (Число(дес)>1) или (Число(дес)=0) Тогда // Для всех, кроме 11-19, обрабатываем единицы
                Если Число(ед)<>0 Тогда
                    е =Строка(спсЧисл.Получить(Число(ед)-1)); // Единицы (первого, второго)
                КонецЕсли;
            КонецЕсли;
            стрГод = Строка(_т)+" "+Строка(с)+" "+Строка(дс)+" "+Строка(е);
        КонецЕсли;
    Иначе
        // Обработка годов другой длины (не 4 цифры) здесь не предусмотрена
    КонецЕсли;
    
    стрГод = стрГод+" года"; // Добавляем слово "года"
    стрГод = СтрЗаменить(стрГод,"  "," "); // Удаляем лишние пробелы
    стрРез = Строка(стрДень)+" "+Строка(стрМес)+" "+Строка(стрГод);
    стрРез = СтрЗаменить(стрРез,"  "," "); // Удаляем лишние пробелы
    Возврат стрРез;
КонецФункции

Процедура КнопкаВыполнитьНажатие(Кнопка)
    Сообщить(ДатаПрописью(РеквизитДатаНаФорме));
КонецПроцедуры

Анализ исходной реализации:

Данная функция пытается вручную склонять числа, используя списки значений. Для дня месяца она определяет, является ли число единичным (1-9), круглым десятком (10, 20, 30) или составным (11-19, 21-29 и т.д.). Для месяца используются фиксированные формы в родительном падеже. Год разбирается по тысячам, сотням, десяткам и единицам, также пытаясь вручную собрать правильное склонение. Однако этот подход очень хрупок и подвержен ошибкам из-за сложности русской грамматики.

Шаг 2: Выявление и исправление ошибок (для годов)

Как показывают сообщения на форуме, в исходном коде были обнаружены ошибки, особенно при работе с годами после 2020. Пользователи отмечали некорректную работу, например, для «21 года».

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

    Изначальная строка:

    дс = спсСубДесРод.Получить(Число(ед)-1).Значение;

    Была предложена замена на:

    
    Если Число(дес) <= 1 Тогда
        дс = спсСубДесРод.Получить(Число(ед)-1).Значение;
    Иначе
        дс = спсДесИм.Получить(Число(ед)).Значение; // Здесь Число(ед) должен быть индексом для десятков, что неверно
    КонецЕсли;
    

    Однако, как было замечено, эта правка сама по себе приводит к новым проблемам. Ошибка заключалась в использовании Число(ед) в качестве индекса для списка спсДесИм, который предназначен для десятков, а не единиц.

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

    Заменяем блок:

    
    Если Число(дес)<>0 Тогда
        Если Число(ед)=0 Тогда // Круглые десятки (десятого, двадцатого)
            дг = Строка(спсДесИм.Получить(Число(дес)-1));
            дс = дг;
        Иначе // 11-19 (одиннадцатого, двенадцатого)
            дс = спсСубДесРод.Получить(Число(ед)-1).Значение;
        КонецЕсли;
    КонецЕсли;
    

    На следующий блок, который корректнее обрабатывает склонение десятков и единиц:

    
    //МПЕ+
    //дс = спсСубДесРод.Получить(Число(ед)-1).Значение; // Исходная строка, закомментированная
    Если Число(дес)=1 Тогда // Если десяток равен 1 (10-19)
        дс=спсСубДесРод.Получить(Число(ед)-1).Значение; // Используем список для 11-19
    Иначе // Если десяток больше 1 (20-90) или равен 0 (для чисел 1-9)
        дс=спсДесИм.Получить(Число(дес-1)).Значение; // Используем список десятков (двадцатого, тридцатого)
    КонецЕсли;
    //МПЕ-
    

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

Недостатки ручной реализации:

Подход 2: Использование стандартных механизмов склонения 1С (ПолучитьСклоненияСтрокиПоЧислу)

Платформа 1С:Предприятие 8.3 предоставляет более элегантный и надежный способ склонения слов и чисел с помощью функции ПолучитьСклоненияСтрокиПоЧислу(). Эта функция является частью встроенного лингвистического движка 1С, который базируется на внешней компоненте NameDecl.dll (или ее аналоге) и предназначен для склонения фамилий, имен, отчеств, а также других слов и числовых выражений. Она значительно упрощает задачу, перекладывая бремя грамматических правил на платформу.

Принцип работы ПолучитьСклоненияСтрокиПоЧислу():

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

Основные параметры, которые мы можем использовать:

Шаг 1: Реализация функции ДатаПрописью с использованием ПолучитьСклоненияСтрокиПоЧислу()

Рассмотрим пример реализации функции, которая использует этот мощный механизм для склонения дня и года:


// Функция ДатаПрописью
// Параметры:
// Дата - Дата для склонения
// Возвращаемое значение:
// строка с датой прописью
Функция ДатаПрописью(Дата) Экспорт
    МассивСтрок = Новый Массив;
    
    // Форматируем дату, чтобы получить день, месяц (полностью) и год
    // Например, для 30.01.2012 получим "30 Январь 2012"
    СлужебнаяСтрока = Формат(Дата, "ДФ='дд ММММ гггг'"); 
    ЧастиСтроки = СтрРазделить(СлужебнаяСтрока, " ", Ложь); // Разделяем на "30", "Январь", "2012"
    
    // 1. Склоняем день
    // Получаем порядковое числительное дня в именительном падеже
    // Например, для "30" получим "тридцатое"
    МассивСклоненийДень = ПолучитьСклоненияСтрокиПоЧислу("число", Число(ЧастиСтроки[0]), "", "ЧС=Порядковое", "ПД=Именительный; ПЧ=ЧислоПрописью");
    // Удаляем слово " число" из результата, если оно добавляется функцией
    МассивСтрок.Добавить(СтрЗаменить(МассивСклоненийДень[0], " число", ""));
    
    // 2. Добавляем месяц
    // Месяц уже получен в нужном падеже через Формат("ММММ") в типовых конфигурациях 
    // (например, "Января", "Февраля"). Если он выводится в именительном,
    // то потребуется его склонение, но обычно Формат("ММММ") уже дает родительный падеж.
    // В данном примере предполагается, что ЧастиСтроки[1] уже в родительном падеже ("Января").
    // Если "Формат(Дата, "ДФ='дд ММММ гггг'")" возвращает "Январь" (именительный),
    // то его тоже нужно было бы склонять.
    МассивСтрок.Добавить(ЧастиСтроки[1]); // Добавляем название месяца
    
    // 3. Склоняем год
    // Получаем порядковое числительное года в родительном падеже
    // Например, для "2012" получим "две тысячи двенадцатого"
    МассивСклоненийГод = ПолучитьСклоненияСтрокиПоЧислу("года", Число(ЧастиСтроки[2]), "", "ЧС=Порядковое", "ПД=Родительный; ПЧ=ЧислоПрописью");
    // Добавляем результат, приведенный к нижнему регистру, чтобы получить "года" вместо "Года"
    МассивСтрок.Добавить(НРег(МассивСклоненийГод[0])); 
    
    // Соединяем все части в итоговую строку
    Возврат СтрСоединить(МассивСтрок, " "); 
КонецФункции

Преимущества этого подхода:

Выявленные проблемы и дальнейшие действия:

Несмотря на кажущуюся простоту и мощь ПолучитьСклоненияСтрокиПоЧислу(), эта функция также может иметь свои нюансы. В форуме было отмечено, что «2024 год не отработал» с этим решением. Это может быть связано с несколькими причинами:

Если вы столкнулись с подобной проблемой, рекомендуем:

  1. Проверить версию платформы и конфигурации: Убедитесь, что вы используете актуальную версию 1С:Предприятие.
  2. Изучить документацию по ПолучитьСклоненияСтрокиПоЧислу(): Подробно изучите описание этой функции в контексте вашей конфигурации (например, в типовых библиотеках).
  3. Тестирование: Проведите тщательное тестирование функции на различных датах, особенно на тех, которые вызывают ошибки.
  4. Обращение к ИТС: На портале ИТС (Информационно-технологическое сопровождение) часто публикуются примеры использования и решения типовых проблем.

Общие рекомендации и альтернативы

Почему же в 1С нет одной универсальной встроенной функции «ДатаПрописью»?

Ключевая сложность заключается в исключительно богатой и сложной грамматике русского языка, особенно в части склонения числительных и согласовании их с существительными по падежам, родам и числам. Дата — это не просто число, а сочетание порядкового числительного дня, существительного месяца в родительном падеже и порядкового числительного года также в родительном падеже. Например, «первое (именительный) января (родительный) две тысячи двадцать четвертого (родительный) года».

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

Практические советы по работе с функцией «Дата прописью»:

  1. Определите требования: Четко зафиксируйте, в каком формате и падеже должна быть выведена дата. Это критически важно, поскольку «первое января» отличается от «первого января».
  2. Используйте ПолучитьСклоненияСтрокиПоЧислу(): Всегда отдавайте предпочтение этому методу, если он доступен в вашей конфигурации. Это наиболее надежный и поддерживаемый платформой способ. Он позволяет указывать необходимый падеж (например, «ПД=Именительный», «ПД=Родительный»), тип числительного («ЧС=Порядковое», «ЧС=Количественное») и формат вывода («ПЧ=ЧислоПрописью»).
  3. Разбирайте дату на компоненты: Независимо от выбранного подхода, всегда сначала разделяйте дату на день, месяц и год. Это упрощает логику склонения каждой части.
  4. Месяц в родительном падеже: Для получения месяца в родительном падеже (например, «января») можно использовать формат "ДФ='МММММ'" (пять букв 'М' для полной формы родительного падежа, если это поддерживается версией платформы и локализацией) или использовать массив строковых значений месяцев. В большинстве типовых решений используется Формат(Дата, "ДЛФ=Д") или Формат(Дата, "ДФ='дд ММММ гггг'"), где название месяца уже будет в родительном падеже, но это зависит от конкретной локализации и версии платформы.
  5. Тестируйте граничные случаи: Обязательно тестируйте даты, которые могут вызвать проблемы:
    • Первые числа месяца (1, 2, 3).
    • Даты с составными числительными (11-19, 21-29).
    • Даты с круглыми десятками (10, 20, 30).
    • Годы, заканчивающиеся на 1, 2, 3 (например, 2021, 2022, 2023).
    • Круглые годы (2000, 2010).
  6. Внешние компоненты и сервисы: Для очень сложных и нестандартных задач, а также для работы с другими языками, можно рассмотреть использование сторонних решений, таких как веб-сервисы (например, «Морфер.ру») или внешние компоненты (например, Morpher.dll). Однако их интеграция потребует дополнительных усилий и не является стандартным функционалом платформы.

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

← На главную