{
  "name": "ezGPT - Normalize Dates UTC",
  "active": false,
  "nodes": [
    {
      "id": "trigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        240,
        300
      ],
      "parameters": {}
    },
    {
      "id": "sample",
      "name": "Sample Requests",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const requests = [\n  {\n    \"request_id\": \"T1\",\n    \"local_time\": \"15.10.2026 09:30\"\n  },\n  {\n    \"request_id\": \"T2\",\n    \"local_time\": \"16.10.2026 00:15\"\n  },\n  {\n    \"request_id\": \"T3\",\n    \"local_time\": \"17.10.2026 18:00\"\n  }\n];\nif (!requests.length) throw new Error(\"requests_empty\");\nreturn requests.map(json => ({ json }));"
      }
    },
    {
      "id": "parse",
      "name": "Parse Local Time",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const FORMAT = 'dd.MM.yyyy HH:mm';\nconst ZONE = 'Europe/Moscow';\nconst ids = new Set();\nreturn $input.all().map((item, i) => {\n  const row = item.json;\n  if (typeof row.request_id !== 'string' || !row.request_id.trim() || ids.has(row.request_id)) {\n    throw new Error('request_id_missing_or_duplicate');\n  }\n  if (typeof row.local_time !== 'string' ||\n      !/^\\d{2}\\.\\d{2}\\.\\d{4} \\d{2}:\\d{2}$/.test(row.local_time)) {\n    throw new Error('local_time_format_invalid');\n  }\n  const parsed = DateTime.fromFormat(row.local_time, FORMAT, { zone: ZONE });\n  if (!parsed.isValid || parsed.year < 2000 || parsed.year > 2099 ||\n      parsed.toFormat(FORMAT) !== row.local_time) throw new Error('local_time_invalid');\n  ids.add(row.request_id);\n  return { json: {\n    request_id: row.request_id,\n    local_time: row.local_time,\n    source_zone: ZONE,\n    source_iso: parsed.toISO(),\n  }, pairedItem: { item: i } };\n});"
      }
    },
    {
      "id": "format",
      "name": "Format UTC",
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 2,
      "position": [
        900,
        300
      ],
      "parameters": {
        "operation": "formatDate",
        "date": "={{ $json.source_iso }}",
        "format": "custom",
        "customFormat": "yyyy-MM-dd'T'HH:mm:ssZZ",
        "outputFieldName": "scheduled_at_utc",
        "options": {
          "includeInputFields": true,
          "timezone": true
        }
      }
    },
    {
      "id": "check",
      "name": "Check Result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const items = $input.all();\nconst source = $('Parse Local Time').all().map(item => item.json);\nif (items.length !== source.length) throw new Error('date_count_mismatch');\nreturn items.map((item, i) => {\n  const row = item.json;\n  const original = source[i];\n  const keys = Object.keys(row).sort().join('|');\n  if (keys !== 'local_time|request_id|scheduled_at_utc|source_iso|source_zone') {\n    throw new Error('date_fields_mismatch');\n  }\n  for (const key of ['request_id', 'local_time', 'source_zone', 'source_iso']) {\n    if (row[key] !== original[key]) throw new Error('date_source_mismatch');\n  }\n  if (typeof row.scheduled_at_utc !== 'string' ||\n      !/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+00:00$/.test(row.scheduled_at_utc)) {\n    throw new Error('utc_format_invalid');\n  }\n  const utc = DateTime.fromISO(row.scheduled_at_utc, { setZone: true });\n  const input = DateTime.fromISO(original.source_iso, { setZone: true });\n  if (!utc.isValid || utc.offset !== 0 || utc.toMillis() !== input.toMillis() ||\n      utc.setZone(original.source_zone).toFormat('dd.MM.yyyy HH:mm') !== original.local_time) {\n    throw new Error('date_instant_mismatch');\n  }\n  return { json: {\n    request_id: row.request_id,\n    local_time: row.local_time,\n    source_zone: row.source_zone,\n    scheduled_at_utc: row.scheduled_at_utc,\n    status: 'ready_for_review',\n    action_performed: false,\n  }, pairedItem: { item: i } };\n});"
      }
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Sample Requests",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sample Requests": {
      "main": [
        [
          {
            "node": "Parse Local Time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Local Time": {
      "main": [
        [
          {
            "node": "Format UTC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format UTC": {
      "main": [
        [
          {
            "node": "Check Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "timezone": "UTC",
    "executionOrder": "v1",
    "saveDataSuccessExecution": "all",
    "saveDataErrorExecution": "all"
  },
  "pinData": {}
}
