Когда файл появился на диске: способы узнать это из 1С
Содержание
Дату создания файла объект Файл в 1С 8.3 не отдаёт: у него есть только время изменения. Чтобы получить дату создания, на Windows создайте Новый COMОбъект("Scripting.FileSystemObject") и прочитайте GetFile(Путь).DateCreated, на Linux вызовите stat -c %W и переведите секунды в дату.
Ниже рабочий код для каждого случая: файл на клиенте, файл на сервере, файл по ссылке в интернете и файл Excel, у которого своя «дата создания» внутри. Всё проверяется на платформе 8.3 и не зависит от конфигурации: БП, ЗУП и УТ здесь ведут себя одинаково, потому что работа идёт средствами встроенного языка.
Что умеет объект Файл и почему даты создания в нём нет
Объект Файл создаётся по полному пути и описывает файл или каталог. Он работает и на клиенте, и на сервере. Набор методов у него такой:
| Метод | Что возвращает |
|---|---|
Существует() | Есть ли файл по пути |
ЭтоФайл(), ЭтоКаталог() | Тип объекта |
Размер() | Размер в байтах |
ПолучитьВремяИзменения() | Время изменения, местное |
ПолучитьУниверсальноеВремяИзменения() | Время изменения, UTC |
ПолучитьТолькоЧтение(), ПолучитьНевидимость() | Атрибуты |
Свойства Имя, ИмяБезРасширения, Расширение, Путь и ПолноеИмя разбирают путь на части. Метода для времени создания нет. Причина простая: платформа кроссплатформенная, а Linux долгое время вообще не хранил дату рождения файла.
Файл = Новый Файл("C:\Обмен\выгрузка.xml");
Если Файл.Существует() Тогда
Изменен = Файл.ПолучитьВремяИзменения();
Сообщить("Изменён: " + Формат(Изменен, "ДЛФ=DT"));
КонецЕсли;
Если задача на самом деле «найти свежие файлы в папке обмена», хватит времени изменения. Его можно получить без внешних средств, в том числе через НайтиФайлы(), который сразу возвращает массив объектов Файл.
Время создания нужно, когда важен момент появления файла: файл скопировали со старой датой изменения, архив распаковали, документ скачали. Тогда понадобятся средства операционной системы.
Дата создания файла на Windows через COM
Самый надёжный и быстрый способ на Windows. Объект Scripting.FileSystemObject есть в любой поддерживаемой версии Windows, ставить ничего не нужно. Свойство DateCreated возвращает дату COM, и 1С сама превращает её в тип Дата в местном времени.
Функция для сервера
&НаСервереБезКонтекста
Функция ДатаСозданияФайлаWindows(ПолноеИмя)
Файл = Новый Файл(ПолноеИмя);
Если Не Файл.Существует() Тогда
Возврат Неопределено;
КонецЕсли;
Попытка
FSO = Новый COMОбъект("Scripting.FileSystemObject");
Если Файл.ЭтоКаталог() Тогда
Возврат FSO.GetFolder(ПолноеИмя).DateCreated;
Иначе
Возврат FSO.GetFile(ПолноеИмя).DateCreated;
КонецЕсли;
Исключение
ЗаписьЖурналаРегистрации("ДатаСозданияФайла",
УровеньЖурналаРегистрации.Ошибка, , ПолноеИмя,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат Неопределено;
КонецПопытки;
КонецФункции
Ту же функцию можно объявить &НаКлиенте, если файл лежит на компьютере пользователя. Тонкий и толстый клиенты под Windows создают COM-объекты без ограничений. Веб-клиент так не умеет.
У того же объекта есть ещё два свойства: DateLastModified (изменение) и DateLastAccessed (последнее обращение). Удобно, когда в отчёт нужны все три даты разом.
Вариант через WMI
Если FileSystemObject запрещён политикой безопасности, а WMI доступен, дату даёт класс CIM_DataFile. Он возвращает строку вида 20231015093000.000000+180, первые 14 символов которой 1С понимает как дату:
&НаСервереБезКонтекста
Функция ДатаСозданияЧерезWMI(ПолноеИмя)
ИмяДляWMI = СтрЗаменить(ПолноеИмя, "\", "\\");
ИмяДляWMI = СтрЗаменить(ИмяДляWMI, "'", "\'");
WMI = ПолучитьCOMОбъект("winmgmts:\\.\root\cimv2");
Выборка = WMI.ExecQuery("SELECT CreationDate FROM CIM_DataFile WHERE Name = '"
+ ИмяДляWMI + "'");
Для Каждого Элемент Из Выборка Цикл
Возврат Дата(Лев(Элемент.CreationDate, 14));
КонецЦикла;
Возврат Неопределено;
КонецФункции
WMI заметно медленнее: на каждый файл уходит заметная доля секунды. Для сотни файлов в цикле лучше FileSystemObject.
Через PowerShell и на Linux-сервере
Когда COM недоступен совсем, например сервер 1С работает на Linux, остаётся вызвать внешнюю команду и прочитать её вывод из временного файла.
PowerShell на Windows
&НаСервереБезКонтекста
Функция ДатаСозданияЧерезPowerShell(ПолноеИмя)
ВремФайл = ПолучитьИмяВременногоФайла("txt");
ПутьPS = СтрЗаменить(ПолноеИмя, "'", "''");
Команда = СтрШаблон("powershell -NoProfile -Command ""(Get-Item -LiteralPath '%1' -Force).CreationTime.ToString('yyyyMMddHHmmss') | Out-File -Encoding ascii '%2'""",
ПутьPS, ВремФайл);
ЗапуститьПриложение(Команда, , Истина);
Результат = Неопределено;
Если (Новый Файл(ВремФайл)).Существует() Тогда
Текст = Новый ТекстовыйДокумент;
Текст.Прочитать(ВремФайл);
Строка = СокрЛП(Текст.ПолучитьТекст());
Если СтрДлина(Строка) = 14 Тогда
Результат = Дата(Строка);
КонецЕсли;
УдалитьФайлы(ВремФайл);
КонецЕсли;
Возврат Результат;
КонецФункции
Три детали, на которых этот код обычно ломается:
- 🔹
-LiteralPathвместо простого пути: квадратные скобки в имени файла PowerShell иначе понимает как шаблон. - 🔹
-Forceнужен, чтобы видеть скрытые файлы. - 🔹
Out-File -Encoding asciiпишет чистые цифры. Без него Windows PowerShell 5.1 сохранит вывод в UTF-16, и строка придёт с лишними символами.
Если нужна дата в UTC, замените CreationTime на CreationTimeUtc.
На клиенте с отключёнными синхронными вызовами используйте ЗапуститьПриложениеАсинх() внутри асинхронной процедуры с Ждать.
stat на Linux
На Linux дату рождения файла хранят ext4, XFS и Btrfs, а показывает её stat с форматом %W: число секунд с 1 января 1970 года в UTC. Если файловая система или ядро дату не отдают, команда вернёт 0.
&НаСервереБезКонтекста
Функция ДатаСозданияLinux(ПолноеИмя)
ВремФайл = ПолучитьИмяВременногоФайла("txt");
Путь = СтрЗаменить(ПолноеИмя, "'", "'\''");
Команда = СтрШаблон("/bin/sh -c ""stat -c %%W '%1' > '%2'""", Путь, ВремФайл);
ЗапуститьПриложение(Команда, , Истина);
Текст = Новый ТекстовыйДокумент;
Текст.Прочитать(ВремФайл);
Секунды = Число(СокрЛП(Текст.ПолучитьТекст()));
УдалитьФайлы(ВремФайл);
Если Секунды = 0 Тогда
Возврат Неопределено;
КонецЕсли;
Возврат МестноеВремя(Дата(1970, 1, 1) + Секунды);
КонецФункции
В СтрШаблон знак процента удваивается: %%W даёт в команде %W, а %1 и %2 подставляют параметры. Нужен coreutils версии 8.32 или новее, в старых stat формата %W для даты рождения не поддерживает.
Даты файла по HTTP: заголовки и свой HTTP-сервис
Файл на чужом веб-сервере
Если файл доступен по ссылке, скачивать его целиком незачем. Метод ПолучитьЗаголовки() объекта HTTPСоединение отправляет запрос HEAD и возвращает только заголовки. Дата создания по HTTP не передаётся вообще, есть только Last-Modified, то есть время последнего изменения на сервере.
Соединение = Новый HTTPСоединение("files.example.ru", 443, , , , 30,
Новый ЗащищенноеСоединениеOpenSSL);
Запрос = Новый HTTPЗапрос("/upload/price.xlsx");
Ответ = Соединение.ПолучитьЗаголовки(Запрос);
Если Ответ.КодСостояния = 200 Тогда
Строка = Ответ.Заголовки.Получить("Last-Modified");
Если Строка <> Неопределено Тогда
Изменен = МестноеВремя(ДатаИзRFC1123(Строка));
КонецЕсли;
КонецЕсли;
Заголовок приходит в формате Wed, 21 Oct 2015 07:28:00 GMT. Встроенного разбора такой строки в платформе нет, функция короткая:
Функция ДатаИзRFC1123(Строка)
Месяцы = "JanFebMarAprMayJunJulAugSepOctNovDec";
Части = СтрРазделить(Строка, " ", Ложь);
НомерМесяца = (СтрНайти(Месяцы, Части[2]) + 2) / 3;
Время = СтрРазделить(Части[4], ":");
Возврат Дата(Число(Части[3]), НомерМесяца, Число(Части[1]),
Число(Время[0]), Число(Время[1]), Число(Время[2]));
КонецФункции
Проверка на примере: Oct стоит в строке месяцев на 28-й позиции, (28 + 2) / 3 = 10, октябрь. Время в заголовке всегда в GMT, поэтому результат переводится через МестноеВремя().
Соответствие Заголовки чувствительно к регистру ключа. Некоторые серверы присылают last-modified строчными. Если Получить вернул Неопределено, переберите заголовки циклом и сравните ключ через НРег().
Свой HTTP-сервис для файлов на сервере
Бывает, что файлы лежат на сервере 1С, а дата нужна внешней системе или другой базе. Тогда публикуют HTTP-сервис: обработчик на сервере получает дату функцией из раздела про COM и отдаёт JSON.
Функция ДатаФайлаGET(Запрос)
Имя = Запрос.ПараметрыЗапроса.Получить("name");
Если Имя = Неопределено Или СтрНайти(Имя, "..") > 0
Или СтрНайти(Имя, "\") > 0 Или СтрНайти(Имя, "/") > 0 Тогда
Возврат Новый HTTPСервисОтвет(400);
КонецЕсли;
ПолноеИмя = "D:\Обмен\Входящие\" + Имя;
Создан = ДатаСозданияФайлаWindows(ПолноеИмя);
Если Создан = Неопределено Тогда
Возврат Новый HTTPСервисОтвет(404);
КонецЕсли;
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Ответ.УстановитьТелоИзСтроки("{""created"":""" + XMLСтрока(Создан) + """}");
Возврат Ответ;
КонецФункции
XMLСтрока() выдаёт дату в виде 2023-10-15T09:30:00, который понимает любая внешняя система. Функцию ДатаСозданияФайлаWindows для этого вынесите в общий модуль с флагом «Сервер».
Никогда не принимайте в HTTP-сервисе полный путь от клиента. Только имя файла внутри заранее заданного каталога, с запретом .. и разделителей. Иначе любой, у кого есть доступ к публикации, прочитает сведения о любом файле сервера.
Пути, кавычки и кириллица
Большая часть ошибок с датами файлов возникает не в получении даты, а в передаче пути. Каждый слой требует своего экранирования:
| Где передаётся путь | Что экранировать | Как |
|---|---|---|
| Строка в коде 1С | Двойную кавычку | Удвоить: "" |
| Запрос WMI | Обратную косую черту и апостроф | \\ и \' |
| PowerShell, строка в апострофах | Апостроф | Удвоить: '' |
| sh, строка в апострофах | Апостроф | Заменить на '\'' |
Обратная косая черта в строке 1С отдельного экранирования не требует: "C:\Обмен\файл.xml" записывается как есть.
Ещё несколько правил:
- 📁 Собирайте путь через
ПолучитьРазделительПути(), если код должен работать и на Windows, и на Linux. - 📁 Временные файлы берите из
ПолучитьИмяВременногоФайла(), а не пишите в каталог базы. - 📁 Сетевые пути вида
\\server\share\file.pdfFileSystemObject понимает. Подключённые буквы дисков у службы сервера 1С обычно не видны: служба не видит сетевых дисков, подключённых пользователем. - 📁 Кириллица в пути FileSystemObject и PowerShell не мешает. Проблемы бывают только при чтении вывода команд, поэтому код выше пишет в файл одни цифры.
Дата создания внутри документа: xlsx, docx и табличный документ
Дата файла на диске и дата создания документа могут расходиться на годы. Файл Excel, который бухгалтер скачал из почты, получает на диске дату скачивания. А в самом документе Office записал момент, когда таблицу создали впервые.
Объект ТабличныйДокумент эти сведения не показывает. Метод Прочитать() загружает содержимое xlsx или mxl, но не свойства документа. Формат mxl собственной даты создания не хранит вовсе.
Файлы xlsx и docx — это zip-архивы. Дата лежит в docProps/core.xml в элементе dcterms:created, в UTC:
&НаСервереБезКонтекста
Функция ДатаСозданияДокументаOffice(ПолноеИмя)
Каталог = ПолучитьИмяВременногоФайла();
СоздатьКаталог(Каталог);
Результат = Неопределено;
Архив = Новый ЧтениеZipФайла(ПолноеИмя);
Для Каждого Элемент Из Архив.Элементы Цикл
Если Элемент.ПолноеИмя = "docProps/core.xml" Тогда
Архив.Извлечь(Элемент, Каталог,
РежимВосстановленияПутейФайловZIP.НеВосстанавливать);
КонецЕсли;
КонецЦикла;
Архив.Закрыть();
ИмяXML = Каталог + ПолучитьРазделительПути() + "core.xml";
Если (Новый Файл(ИмяXML)).Существует() Тогда
Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл(ИмяXML);
Пока Чтение.Прочитать() Цикл
Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента
И Чтение.ЛокальноеИмя = "created" Тогда
Чтение.Прочитать();
Результат = МестноеВремя(
XMLЗначение(Тип("Дата"), Лев(Чтение.Значение, 19)));
Прервать;
КонецЕсли;
КонецЦикла;
Чтение.Закрыть();
КонецЕсли;
УдалитьФайлы(Каталог);
Возврат Результат;
КонецФункции
Лев(..., 19) отрезает букву Z в конце, чтобы XMLЗначение гарантированно разобрал строку. Для файлов LibreOffice (ods, odt) путь другой: meta.xml, элемент creation-date.
Старые xls и doc — не архивы, этот код к ним не подходит. Для них остаётся дата файла на диске.
Ошибки, права и безопасный режим
Код из примеров учитывает отсутствие файла, но в реальной базе мешают ещё и настройки.
- ⛔
Ошибка при вызове конструктора (COMОбъект). На сервере это часто означает, что база работает с профилем безопасности или код вызван в безопасном режиме. COM-объекты, запуск приложений и доступ к файловой системе в таком режиме запрещены, пока администратор не разрешит их в профиле. - ⛔ Файл есть, а
Существует()возвращаетЛожь. Серверный код выполняется от имени учётной записи службы 1С. У неё нет прав на каталог пользователя или на сетевую папку. Дайте права учётной записи службы на чтение каталога. - ⛔ Пустой результат PowerShell. Политика выполнения сценариев на
-Commandне влияет, а вот антивирус может блокировать запускpowershell.exeиз процессаrphost. Проверьте журнал антивируса. - ⛔ Дата создания позже даты изменения. Это не ошибка. Windows при копировании ставит новое время создания, а время изменения сохраняет от исходного файла.
Перехваченную ошибку пишите в журнал регистрации, как в первой функции. Пользователю достаточно короткого сообщения, а администратору нужен полный текст из ПодробноеПредставлениеОшибки().
Частые вопросы
Можно ли изменить дату создания файла из 1С?
FileSystemObject даёт это свойство только на чтение. Через PowerShell можно присвоить новое значение свойству CreationTime у объекта Get-Item.Сохраняется ли дата создания при перемещении файла?
Какое время возвращает DateCreated — местное или UTC?
Подходит ли этот способ для файлов в томах хранения 1С?
Есть ли дата создания у файла в двоичных данных?
ДвоичныеДанные хранят только содержимое. После ПолучитьИзВременногоХранилища() и записи на диск у файла будет новая дата создания.