Пошаговые инструкции Для начинающих 12 мин

Date & Time в n8n: как перевести дату из заявки в UTC

Преобразуйте время из заявки в UTC без ручного сдвига часов. 12 шагов, готовый workflow, проверка календарных дат и смены дня.

n8n Date & Time UTC

Клиент выбрал 16 октября в 00:15 по Москве, а в системе нужно сохранить точный момент в UTC. Это будет ещё 15 октября, 21:15. Простая замена точек на дефисы такого преобразования не делает. Настроим n8n так, чтобы он явно понимал исходный часовой пояс, проверял дату и выдавал строку для дальнейшей передачи в API.

Результат инструкции

Соберём ручной workflow из пяти узлов. На входе три вымышленные заявки со строкой local_time в формате dd.MM.yyyy HH:mm. Все значения по договорённости относятся к Europe/Moscow. Luxon проверит календарную дату и добавит смещение, штатный Date & Time сформирует scheduled_at_utc, а последний Code сверит результат с исходным моментом времени.

На выходе останутся три items с ID, исходной датой, зоной и UTC-строкой вида 2026-10-15T06:30:00+00:00. Ничего не бронируется и не отправляется: status ready_for_review, action_performed false. Это подготовка данных, не календарь, таймер и не AI-агент.

Что понадобится

  • Работающий n8n и права создать отдельный workflow. Проверено на n8n 2.42.4, Date & Time версии 2.
  • Вымышленные даты из примера. Календарь, API-ключ и внешняя база не нужны.
  • Согласованное правило входа: точность до минуты, годы 2000–2099, исходная зона Europe/Moscow.

Установить n8n локально через Docker

Шаг 1. Импортируйте учебную схему

Скачать n8n-datetime-utc.json

Создайте новый workflow и выберите Import from File в меню редактора. Загрузите JSON. Если браузер открыл текст файла, сохраните его и импортируйте. На полотне появится цепочка:

text
Manual Trigger → Sample Requests → Parse Local Time → Format UTC → Check Result

Format UTC — Date & Time. Sample Requests, Parse Local Time и Check Result — Code. При ручной сборке задайте именно эти имена и последовательные соединения. В файле нет credentials и ID другого workflow. Он неактивен; для ручного выполнения публикация не требуется.

Шаг 2. Задайте часовой пояс workflow

Откройте Settings самого workflow, не отдельного узла. В Timezone выберите UTC и сохраните. В импортированном файле это уже задано. Настройку всего экземпляра n8n менять не нужно: остальные workflows могут использовать свои зоны.

Исходная зона клиента и зона workflow здесь разные. Europe/Moscow отвечает на вопрос, что означает введённое 09:30. UTC нужен для единого выходного представления. Не выбирайте зону по текущим часам своего компьютера и не вычитайте три часа вручную.

Шаг 3. Подготовьте три входные даты

В Sample Requests задайте Mode: Run Once for All Items и Language: JavaScript. Вставьте:

javascript
const requests = [
  {
    "request_id": "T1",
    "local_time": "15.10.2026 09:30"
  },
  {
    "request_id": "T2",
    "local_time": "16.10.2026 00:15"
  },
  {
    "request_id": "T3",
    "local_time": "17.10.2026 18:00"
  }
];
if (!requests.length) throw new Error("requests_empty");
return requests.map(json => ({ json }));

Выполните источник от Manual Trigger. Должно быть три items с T1/T2/T3. local_time остаётся строкой с ведущими нулями: две цифры дня, две месяца, четыре года и время HH:mm. T2 специально находится около полуночи, чтобы проверить изменение календарного дня.

Пустой список requests в этом источнике вызывает requests_empty. Это наше правило теста, не встроенная ошибка Date & Time. Если в вашей задаче пустой вход допустим, предусмотрите отдельный результат no_data, а не фиктивную дату.

Шаг 4. Разберите дату с явной исходной зоной

В Parse Local Time задайте Run Once for All Items и JavaScript. Вставьте:

javascript
const FORMAT = 'dd.MM.yyyy HH:mm';
const ZONE = 'Europe/Moscow';
const ids = new Set();
return $input.all().map((item, i) => {
  const row = item.json;
  if (typeof row.request_id !== 'string' || !row.request_id.trim() || ids.has(row.request_id)) {
    throw new Error('request_id_missing_or_duplicate');
  }
  if (typeof row.local_time !== 'string' ||
      !/^\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}$/.test(row.local_time)) {
    throw new Error('local_time_format_invalid');
  }
  const parsed = DateTime.fromFormat(row.local_time, FORMAT, { zone: ZONE });
  if (!parsed.isValid || parsed.year < 2000 || parsed.year > 2099 ||
      parsed.toFormat(FORMAT) !== row.local_time) throw new Error('local_time_invalid');
  ids.add(row.request_id);
  return { json: {
    request_id: row.request_id,
    local_time: row.local_time,
    source_zone: ZONE,
    source_iso: parsed.toISO(),
  }, pairedItem: { item: i } };
});

Используется встроенный в JavaScript-среду n8n DateTime из Luxon. Устанавливать пакет или разрешать внешние модули для этого примера не нужно. FORMAT задаёт порядок частей даты, ZONE — её исходный смысл. Проверка isValid отклоняет невозможный день, а обратное форматирование замечает нормализацию вроде перехода 24:00 на следующий день.

У T1 в Output Parse Local Time должен появиться source_iso 2026-10-15T09:30:00.000+03:00. Это уже однозначный момент времени со смещением. Исходная local_time тоже сохраняется. Не передавайте дальше сам объект DateTime: между узлами мы используем обычные строки.

Проверка принимает строго указанный формат и годы 2000–2099. null, пустота, лишние пробелы, другая запись даты и повтор ID останавливают запуск. Мы намеренно не угадываем, что хотел сказать источник.

Шаг 5. Настройте Date & Time

Откройте Format UTC. В Operation выберите Format a Date, в Date включите Expression:

text
{{ $json.source_iso }}

Далее задайте параметры из таблицы. Поля Format, Custom Format и Output Field Name вводятся как настройки, не как выражения:

Параметр Значение Зачем
Operation Format a Date Форматирование момента
Date Expression: $json.source_iso ISO с исходным смещением
Format Custom Format Свой шаблон результата
Custom Format yyyy-MM-dd'T'HH:mm:ssZZ Дата, время и фактическое смещение
Output Field Name scheduled_at_utc Отдельное выходное поле

В Custom Format вставьте шаблон целиком:

text
yyyy-MM-dd'T'HH:mm:ssZZ

Регистр значим: MM — месяц, mm — минуты. Буква T в одинарных кавычках — буквальный разделитель, ZZ добавляет смещение с двоеточием. Не заменяйте ZZ на текст Z в кавычках: так можно подписать не-UTC время как UTC, не выполнив преобразование.

Шаг 6. Сохраните исходные поля и включите зону workflow

В Options узла Format UTC добавьте Include Input Fields и включите его. Добавьте Use Workflow Timezone и тоже включите. Поэтому штатный узел оформит уже определённый момент в зоне UTC из шага 2, а не оставит исходное представление.

From Date Format здесь не задавайте: на вход этого узла уже приходит ISO со смещением, не исходная строка 15.10.2026 09:30. Разбор исходной строки сделан в Parse Local Time с явно указанной зоной. Это не опора на автоматическое распознавание русского формата или системную зону сервера.

В Settings узлов оставьте On Error: Stop Workflow. Execute Once, Always Output Data и Retry On Fail не включайте. Повтор не исправит 31 февраля.

Шаг 7. Добавьте проверку UTC и исходного момента

В Check Result выберите Run Once for All Items, JavaScript и вставьте весь код:

javascript
Показать код (32 строк)
const items = $input.all();
const source = $('Parse Local Time').all().map(item => item.json);
if (items.length !== source.length) throw new Error('date_count_mismatch');
return items.map((item, i) => {
  const row = item.json;
  const original = source[i];
  const keys = Object.keys(row).sort().join('|');
  if (keys !== 'local_time|request_id|scheduled_at_utc|source_iso|source_zone') {
    throw new Error('date_fields_mismatch');
  }
  for (const key of ['request_id', 'local_time', 'source_zone', 'source_iso']) {
    if (row[key] !== original[key]) throw new Error('date_source_mismatch');
  }
  if (typeof row.scheduled_at_utc !== 'string' ||
      !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+00:00$/.test(row.scheduled_at_utc)) {
    throw new Error('utc_format_invalid');
  }
  const utc = DateTime.fromISO(row.scheduled_at_utc, { setZone: true });
  const input = DateTime.fromISO(original.source_iso, { setZone: true });
  if (!utc.isValid || utc.offset !== 0 || utc.toMillis() !== input.toMillis() ||
      utc.setZone(original.source_zone).toFormat('dd.MM.yyyy HH:mm') !== original.local_time) {
    throw new Error('date_instant_mismatch');
  }
  return { json: {
    request_id: row.request_id,
    local_time: row.local_time,
    source_zone: row.source_zone,
    scheduled_at_utc: row.scheduled_at_utc,
    status: 'ready_for_review',
    action_performed: false,
  }, pairedItem: { item: i } };
});

Проверка требует тот же набор записей и исходных полей, UTC-смещение +00:00 и совпадение момента времени через toMillis. Дополнительно результат переводится обратно в Europe/Moscow и сравнивается с исходной строкой. Так обнаруживается не только другой формат, но и тихий сдвиг времени.

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

Шаг 8. Выполните весь workflow

Нажмите Execute Workflow. В Format UTC должно быть три items. В Check Result тоже три. Первый ожидаемый результат:

json
{
  "request_id": "T1",
  "local_time": "15.10.2026 09:30",
  "source_zone": "Europe/Moscow",
  "scheduled_at_utc": "2026-10-15T06:30:00+00:00",
  "status": "ready_for_review",
  "action_performed": false
}

Строка +00:00 означает UTC. Запись с Z может обозначать тот же момент, но в этом примере выбран один точный выходной формат с +00:00, и проверка требует именно его. Строки не нужно сравнивать как произвольный текст, если задача — выяснить, относятся ли они к одному моменту.

Шаг 9. Сверьте переход через полночь

Заявка Вход Europe/Moscow Ожидаемый UTC
T1 15.10.2026 09:30 2026-10-15T06:30:00+00:00
T2 16.10.2026 00:15 2026-10-15T21:15:00+00:00
T3 17.10.2026 18:00 2026-10-17T15:00:00+00:00

У T2 день на выходе стал 15 октября вместо 16-го. Это не потеря суток, а другой часовой пояс того же момента. Если ваша задача хранит просто календарную дату без времени, например день рождения, не применяйте эту схему автоматически: у такого значения иной смысл.

Для дополнительного опыта в отдельной копии оставьте одну заявку с 01.01.2027 00:15. Ожидается 2026-12-31T21:15:00+00:00. Такая проверка ловит ошибки не только времени, но и месяца и года.

Шаг 10. Проверьте невозможные даты

В Sample Requests временно замените дату T1 на 31.02.2026 09:30. Запуск должен остановиться в Parse Local Time с local_time_invalid, до успешного результата Check Result. Так же должны отклоняться 29.02.2026 09:30, 15.10.2026 24:00 и 15.10.2026 09:60.

На отдельном опыте 29.02.2024 09:30 должен пройти: ожидаемый UTC 2024-02-29T06:30:00+00:00. Строка 2026-10-15 09:30 должна дать local_time_format_invalid, потому что не соответствует договорённому формату, хотя сама дата существует. Верните три исходные заявки.

Шаг 11. Убедитесь, что неверная настройка замечена

На учебной копии поменяйте Timezone workflow с UTC на Europe/Moscow и выполните всю схему. Format UTC вернёт представление с +03:00, а Check Result остановится с utc_format_invalid. Имя поля scheduled_at_utc само по себе не делает значение UTC. Верните UTC.

Теперь выключите Include Input Fields в Format UTC. Проверка должна дать date_fields_mismatch: пропали ID и исходная дата. Верните опцию. Если вместо Expression подставить одну фиксированную дату для всех заявок, Check Result заметит date_instant_mismatch, когда дойдёт до другой заявки. Не отключайте проверку ради зелёного результата.

Шаг 12. Восстановите и сохраните результат

Верните исходные три записи, Timezone UTC, Date через source_iso, полный custom format и обе опции из шага 6. Сохраните и выполните весь workflow. Сверьте каждую строку с таблицей, особенно T2. У всех должны быть status ready_for_review и action_performed false.

Готово: данные подготовлены, но события календаря не созданы. Перед настоящим API выясните, принимает ли он ISO со смещением, UTC с Z или отдельную зону, и проверьте тестовый запрос. Не передавайте время без зоны как будто это UTC и не меняйте формат исходного значения молча.

Если результат отличается

Симптом Причина Что проверить
local_time_format_invalid Вход не по шаблону dd.MM.yyyy HH:mm без пробелов по краям
local_time_invalid Дата невозможна или вне диапазона Календарный день, время, год
utc_format_invalid Не тот пояс или выходной формат Workflow UTC, ZZ и полное время
date_fields_mismatch Потеряны поля или другое имя даты Include Input Fields, scheduled_at_utc
date_instant_mismatch Момент не совпал с исходным Expression в Date и маршрут данных

Где заканчивается эта схема

Europe/Moscow здесь — правило для всех заявок, не вывод по IP, языку или номеру телефона клиента. Для нескольких зон нужно получать достоверную зону каждой заявки и отдельно проектировать проверку. Переходы летнего времени, повторяющиеся и несуществующие локальные часы в других зонах в этом материале не тестировались. Не переносите пример на них без дополнительных правил.

Это преобразование не ставит workflow на ожидание и не запускает его по расписанию. Оно также не решает, допустима ли дата в прошлом. Такие правила зависят от задачи и добавляются отдельно.

Подготовить поля для API через Edit Fields

Остановить workflow при нарушении договора данных

Что проверено редакцией

15 октября 2026 года workflow проверен на n8n 2.42.4 в отдельном Docker-экземпляре штатными ручными запусками API. Все 24 проверки прошли: три даты, одна запись, обратный порядок, лишнее поле, 29 февраля високосного года, переход через Новый год, пустой источник, 31 февраля, 29 февраля невисокосного года, 24:00, минуты 60, месяц 13, другой формат, null/отсутствующая/пустая дата, внешние пробелы, повтор/отсутствие ID, неверный timezone workflow, выключенный перенос входных полей, другое имя результата, формат только даты и фиксированный вход вместо выражения. Сверены точные UTC-строки, количество, исходные поля и отсутствие успешного результата при ошибках. Luxon разбирает вход с явной Europe/Moscow, штатный Date & Time версии 2 формирует выход с зоной workflow UTC. Официальные документы n8n, Luxon и код Date & Time сверены. Редакторские клики, Cloud, другие зоны и переходы летнего времени, автоматические триггеры, большие объёмы и внешние API не тестировались. Учебный контейнер остановлен, чужие workflows не менялись. Обложка — редакционная AI-иллюстрация.

Частые вопросы

Почему UTC-дата стала на день раньше?

Локальная полночь и UTC относятся к разным показаниям часов. В примере 16 октября 00:15 Europe/Moscow соответствует 15 октября 21:15 UTC. Обратная проверка подтверждает тот же момент.

Можно просто вычесть три часа?

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

Почему используется +00:00, а не Z?

Оба обозначения могут выражать UTC. Здесь выбран конкретный формат с +00:00. Если API требует Z, измените форматирование и проверку согласованно, а не дописывайте Z к локальному времени.

Что делать, если заявки приходят из разных зон?

Нужна достоверная зона на каждую заявку и отдельные правила её проверки. Эта схема рассчитана на фиксированную Europe/Moscow и не проверяет неоднозначные часы переходов летнего времени.

Почему после преобразования не создалась встреча?

Схема только подготавливает и проверяет строки даты. Узла календаря и внешнего действия в ней нет, поэтому action_performed false. Подключение API требует отдельного теста.

Дальше по теме

Похожие материалы