Working with dates in n8n used to mean writing custom JavaScript in a Code node just to reformat a timestamp or calculate "how many days ago." The DateTime node (introduced in n8n 0.221) handles all of that natively — no code required.
This guide covers every operation the DateTime node supports, the formatting tokens you'll actually use, and three real-world workflow patterns with free JSON you can import today.
What the n8n DateTime Node Does
The DateTime node performs four categories of operations:
- Format a date — convert any date/time value into a human-readable or machine-readable string
- Calculate a difference — find the gap between two dates in seconds, minutes, hours, days, weeks, months, or years
- Add or subtract time — offset a date by a duration
- Extract a part — pull out just the year, month, day, hour, etc.
All operations use Luxon under the hood, which is also available in n8n expressions as $now, $today, and DateTime.
Operation 1: Format a Date
Use case: You receive an ISO 8601 timestamp from an API (2026-07-02T10:25:00.000Z) and need to display it as July 2, 2026 or 02/07/2026 in an email or Slack message.
Settings:
- Operation: Format a Date
-
Date: The input field containing the timestamp (e.g.,
{{ $json.created_at }}) - Format: Choose a preset or enter a custom token string
- Output Field Name: The field name to write the result into
Common format tokens:
| Token | Output example | Meaning |
|---|---|---|
yyyy-MM-dd |
2026-07-02 | ISO date |
dd/MM/yyyy |
02/07/2026 | EU date |
MM/dd/yyyy |
07/02/2026 | US date |
MMMM d, yyyy |
July 2, 2026 | Long date |
EEE, MMM d |
Thu, Jul 2 | Short weekday |
HH:mm:ss |
10:25:00 | 24h time |
hh:mm a |
10:25 AM | 12h time |
yyyy-MM-dd'T'HH:mm:ssZZ |
2026-07-02T10:25:00+00:00 | ISO 8601 with offset |
X |
1751453100 | Unix timestamp (seconds) |
x |
1751453100000 | Unix timestamp (milliseconds) |
Timezone input: If your incoming date has no timezone info, set Input Timezone to the correct zone (e.g., America/Chicago) to avoid silent UTC mismatches.
Operation 2: Calculate a Difference
Use case: A support ticket was opened on 2026-06-25. Today is 2026-07-02. How many days has it been open?
Settings:
- Operation: Calculate a Date Difference
-
Start Date: Earlier date (e.g.,
{{ $json.opened_at }}) -
End Date: Later date (e.g.,
{{ $now }}) - Units: days (or seconds, minutes, hours, weeks, months, years)
-
Output Field Name: e.g.,
days_open
The result is an integer. For the example above: 7.
Tip: Use seconds for SLA calculations, then convert to minutes/hours in downstream expressions — integers are easier to compare than decimal days.
Operation 3: Add or Subtract Time
Use case: You want a "follow-up due" date that is 3 business days after a lead signs up, or an expiry timestamp 30 days from now.
Settings:
- Operation: Add to a Date
- Date: Base date
- Duration: The amount to add (can be negative to subtract)
- Duration Unit: seconds | minutes | hours | days | weeks | months | years
-
Output Field Name: e.g.,
follow_up_date
Example — 30-day trial expiry:
Base date: {{ $now }}
Duration: 30
Unit: days
Output: trial_expires_at
Then format trial_expires_at in a second DateTime node (or in an expression) to get a human-readable string for the welcome email.
Chaining two DateTime nodes is the right pattern: one to do arithmetic, one to format the result.
Operation 4: Extract a Part
Use case: You only need the hour from a timestamp to decide which Slack channel to route an alert to (business hours vs. on-call).
Settings:
- Operation: Extract Part of a Date
- Date: Input timestamp
- Part: year | month | day | hour | minute | second | millisecond | weekday | weekNumber | quarter
-
Output Field Name: e.g.,
alert_hour
Weekday note: Returns 1 (Monday) through 7 (Sunday) per ISO 8601. If you need 0-indexed Sunday-first, use an expression: {{ DateTime.fromISO($json.created_at).weekday % 7 }}.
Using Dates in Expressions (Without the Node)
The DateTime node is great for storing results in a field, but sometimes you just need a date inline. n8n's expression engine exposes Luxon directly:
// Current UTC time as ISO string
{{ $now.toISO() }}
// Today's date in US format
{{ $today.toFormat('MM/dd/yyyy') }}
// 7 days from now
{{ $now.plus({ days: 7 }).toISO() }}
// Start of current month
{{ $now.startOf('month').toISODate() }}
// Days since a field date
{{ $now.diff(DateTime.fromISO($json.created_at), 'days').days | round }}
// Is a date in the past?
{{ DateTime.fromISO($json.expires_at) < $now }}
These work in any n8n expression field — IF node conditions, Set node values, HTTP Request body templates, etc.
Common Gotchas
1. Timezone drift
If an incoming timestamp has no Z or offset suffix, n8n treats it as UTC. Pass America/Chicago (or whatever zone it actually represents) in the Input Timezone field to correct it.
2. String vs. Date type
The DateTime node accepts strings, numbers (Unix ms/s), and JavaScript Date objects. If your upstream node outputs a number like 1751453100000, n8n auto-detects it as Unix milliseconds. If it outputs a string without a recognizable format, use the Input Format field to tell Luxon how to parse it (e.g., MM/dd/yyyy HH:mm).
3. Two nodes for "format the result of arithmetic"
Add/subtract outputs a date object. If you want a human-readable result, chain a second DateTime node set to Format a Date — trying to format in the same node as arithmetic isn't supported.
4. $today vs $now
$today is midnight UTC at the start of today. $now is the current instant. Use $today for date-only comparisons; $now for timestamps.
5. Locale in format strings
Token MMMM outputs month names in English. If you need localized month names, use DateTime.fromISO($json.date).setLocale('de').toFormat('MMMM') in an expression.
Workflow Pattern 1: Overdue Ticket Alert
Every hour, check a Google Sheet of support tickets. For any ticket open > 48 hours with no reply, send a Slack alert.
Schedule Trigger (every hour)
→ Google Sheets (Get all rows)
→ Filter (status = "open")
→ DateTime [Difference] (opened_at → now, unit: hours → field: hours_open)
→ Filter (hours_open > 48)
→ Slack (Send message: "Ticket #{{ $json.ticket_id }} open {{ $json.hours_open }}h — no reply")
Free workflow JSON:
{
"name": "Overdue Ticket Alert",
"nodes": [
{
"parameters": { "rule": { "interval": [{ "field": "hours", "hoursInterval": 1 }] } },
"name": "Every Hour",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [0, 0]
},
{
"parameters": {
"operation": "dateDiff",
"date": "={{ $json.opened_at }}",
"date2": "={{ $now.toISO() }}",
"outputFieldName": "hours_open",
"options": { "unit": "hours" }
},
"name": "Hours Open",
"type": "n8n-nodes-base.dateTime",
"typeVersion": 2,
"position": [240, 0]
},
{
"parameters": {
"conditions": {
"number": [{ "value1": "={{ $json.hours_open }}", "operation": "larger", "value2": 48 }]
}
},
"name": "Over 48h?",
"type": "n8n-nodes-base.filter",
"typeVersion": 1,
"position": [480, 0]
},
{
"parameters": {
"text": "=Ticket #{{ $json.ticket_id }} open {{ $json.hours_open }}h — no reply yet.",
"channelId": "support-alerts"
},
"name": "Slack Alert",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.2,
"position": [720, 0]
}
],
"connections": {
"Every Hour": { "main": [[{ "node": "Hours Open", "type": "main", "index": 0 }]] },
"Hours Open": { "main": [[{ "node": "Over 48h?", "type": "main", "index": 0 }]] },
"Over 48h?": { "main": [[{ "node": "Slack Alert", "type": "main", "index": 0 }]] }
}
}
Workflow Pattern 2: Trial Expiry Reminder Email
When a user signs up, calculate their trial expiry date (30 days out) and store it. A daily job then emails anyone whose trial expires tomorrow.
Webhook (new signup)
→ DateTime [Add] (now + 30 days → trial_expires_at)
→ DateTime [Format] (trial_expires_at → "MMMM d, yyyy" → trial_expires_display)
→ Airtable (Create record with trial_expires_at + trial_expires_display)
Schedule Trigger (daily 08:00)
→ Airtable (Filter: trial_expires_at = tomorrow)
→ Gmail (Send: "Your trial ends on {{ $json.trial_expires_display }}")
Workflow Pattern 3: Business-Hours Router
Route incoming webhook events to the right Slack channel based on whether they arrive during business hours (Mon–Fri, 09:00–17:00 CT).
Webhook
→ DateTime [Extract Part] (now → hour → current_hour, timezone: America/Chicago)
→ DateTime [Extract Part] (now → weekday → current_weekday)
→ IF (current_weekday <= 5 AND current_hour >= 9 AND current_hour < 17)
→ True: Slack #support (business hours)
→ False: Slack #oncall (after hours)
The Full n8n Workflow Starter Pack
These patterns are part of the n8n Workflow Starter Pack — 20+ pre-built, documented workflow JSONs covering the most common automation patterns: webhooks, scheduling, database ops, API integrations, error handling, and date/time logic.
One-time purchase, import-ready JSON, no recurring fees.
→ Get the n8n Workflow Starter Pack ($29)
Quick Reference
| Task | Operation | Key field |
|---|---|---|
| Reformat a timestamp | Format a Date | Format tokens (e.g., MMMM d, yyyy) |
| Days/hours between two dates | Calculate Difference | Units dropdown |
| Add 30 days to now | Add to a Date | Duration + Unit |
| Get the hour from a timestamp | Extract Part | Part = hour |
| Current time in expression | — | $now.toISO() |
| 7 days from now in expression | — | $now.plus({days:7}).toISO() |
| Parse custom format | Format a Date | Input Format field |
What's Next?
- n8n Code Node Advanced Patterns — when you need date math beyond what the node offers
- n8n Wait Node — pause execution until a future date
- n8n Schedule Trigger — run workflows on a time-based schedule
Drop a comment with your date/time use case — I read every one. 👇
Top comments (1)
What datetime patterns are you solving in your workflows? For me the most common use case is business-hours routing — checking whether it's within 9-5 Mon-Fri before deciding whether to alert on-call or queue for morning. Drop your use case below 👇