Date/Time: Luxon, German Formats, Time Zone Pitfalls

Luxon in n8n: produce German date formats with toFormat and setLocale and avoid typical time zone pitfalls in expressions.

n8n processes date and time internally via the JavaScript library Luxon and not via the native JavaScript `Date` object. You usually only notice this once a workflow outputs a date that is off by one hour, a month name appears in English instead of German, or a document suddenly lands on the wrong day at the end of the month. Once you understand how Luxon cleanly separates time zone, language, and format, you can specifically avoid exactly these errors, and you keep control over which date actually ends up on the invoice, in the CRM, or in the table.

This article deliberately focuses on formatting date and time in expressions and in the Date & Time Node, not on the time zone logic of the Schedule Trigger or of Cron expressions. That is a separate topic with its own pitfalls that deserves a separate look.

Why n8n uses Luxon at all

Plain JavaScript has no built-in concept for time zones or language formats that go beyond the most basic level. Luxon fills exactly this gap: every date that n8n generates internally, for example via `$now` or `$today`, is a Luxon `DateTime` object with its own time zone, its own language (locale), and its own methods for calculating, comparing, and formatting. According to the official documentation on working with Date and Time in n8n date values are passed between nodes as strings and must first be parsed again with Luxon functions for calculations or formatting. The native JavaScript `Date()` does not respect the time zone set in n8n, which is why the documentation explicitly recommends sticking with Luxon for everything related to date and time.

Formatting a date in expressions

For the actual formatting, several approaches are available in expressions, which are differently well suited to German requirements:

  • `.toFormat(fmt)`: the native Luxon method with which you build a format yourself using fixed tokens, for example `dd.MM.yyyy`. It works everywhere, including in the Code Node.
  • `.toLocaleString(opts)`: returns a localized, meaning language-dependent, format. Without a set locale, the result follows the default language of the n8n instance, which in practice often means English.
  • `format()`: an n8n-specific extension of the expression language for date formatting, which according to the DateTime Expression Reference is not available in the Code Node. There you instead fall back on the native `.toFormat()`.
  • Preset formats in the Date & Time Node: the Node comes with fixed presets like `MM/DD/YYYY` or `YYYY-MM-DD`, plus an option for a custom format. According to the documentation on the Date & Time Node n8n supports all formats for this that Luxon itself knows, whereby the tokens are case-sensitive.

For a purely numeric German date, `.toFormat()` with a fixed token pattern is usually enough, while `.toLocaleString()` and `.setLocale()` become important as soon as spelled-out month or weekday names are supposed to appear in German.

Producing correct German formats

For the classic German notation `TT.MM.JJJJ`, a fixed expression without locale dependency is enough:

  • `{{$now.toFormat('dd.MM.yyyy')}}` for example produces `20.07.2026`.
  • `{{$now.toFormat('dd.MM.yyyy HH:mm')}}` adds the time in 24-hour format.

As soon as spelled-out names come into play, for example for an invoice or a mail merge, you additionally need the German locale, because otherwise the month name is output in English:

  • `{{$now.setLocale('de-DE').toLocaleString({month: 'long', day: 'numeric', year: 'numeric'})}}` produces a result like `20. Juli 2026`.
  • `{{$now.setLocale('de-DE').monthLong}}` directly returns the spelled-out month name, for example `Juli`.

Important here: `.setLocale()` only changes the language of the output, not the time zone. You must set both settings separately, otherwise you get a German word for the month, but under certain circumstances still the wrong time.

Time zone pitfalls when formatting

Most errors do not occur in the format itself, but in the time zone in which formatting takes place. A few pitfalls that keep coming up in practice:

  • Instance default time zone instead of Europe/Berlin: Without explicit configuration, a self-hosted n8n instance according to the documentation defaults to `America/New York`, while on n8n Cloud the system falls back to GMT. `$now` and `$today` follow this instance time zone, unless a workflow-specific setting or a `GENERIC_TIMEZONE` environment variable specifies otherwise. Anyone who overlooks this gets timestamps that deviate by several hours from German time.
  • Formatting without a prior `.setZone()`: `.toFormat()` and `.toLocaleString()` always output the time in the time zone that the `DateTime` object currently has, not automatically Europe/Berlin. If a date comes from an API in UTC or in a foreign time zone, you should first convert it with `.setZone('Europe/Berlin')` and only format it afterward, otherwise a document created at 23:30 German time suddenly ends up on the following day in the report.
  • Daylight saving time and standard time: Europe/Berlin changes its offset to UTC twice a year. If you calculate with `.plus({days: n})` across such a change and then format the time, the time portion can shift by one hour, even though the calendar day is correct. For pure date-without-time comparisons this is usually uncritical, but for time-critical processes you should plan for it.
  • Passing on `.toISO()` with offset: Luxon's ISO string contains the time zone offset by default, for example `+02:00`. Systems that instead expect pure UTC or an offset-less string otherwise interpret this value incorrectly. Check in the target system which format is actually expected before blindly passing on the result.

Practical example: outputting the invoice date correctly

A typical case from practice: a workflow retrieves a document date from an API that delivers UTC timestamps, and is supposed to generate a German invoice date from it. The expression for this looks combined like this:

  • `{{ $json.createdAt.toDateTime().setZone('Europe/Berlin').setLocale('de-DE').toFormat('dd.MM.yyyy') }}`

The order here is no coincidence: first the native JavaScript timestamp is converted into a Luxon `DateTime`, then shifted into the correct time zone, then the language is set, and only at the end is it formatted. If you swap the time zone and formatting steps, you get a correctly looking German date, but possibly for the wrong calendar day.

Frequently asked questions

What is the difference between `.toFormat()` and `.toLocaleString()`?

`.toFormat()` follows a fixed token pattern that you define yourself, such as `dd.MM.yyyy`, and always delivers the same layout regardless of the locale. `.toLocaleString()`, on the other hand, follows the set language and automatically adjusts the order as well as the spelling, which makes the difference especially with spelled-out month or weekday names.

Why does the month name appear in English even though I work in Germany?

Without an explicit `.setLocale('de-DE')`, Luxon uses the default language of the n8n instance, which is often set to English. The time zone of the instance and its language are two separate settings, which is why a correctly set `Europe/Berlin` alone does not yet guarantee a German month name.

Do I have to manually set the time zone for every date?

Whenever a date comes from an external source such as an API, a database, or a form, yes. Such values are often in UTC or in a foreign time zone, and only `$now` or `$today` automatically follow the instance or workflow time zone configured in n8n.

Does this also apply to the Schedule Trigger or Cron expressions?

No, separate rules apply there for the execution time itself, not for the formatting of date values in expressions. This article deals exclusively with how an already existing date within a workflow is formatted correctly.

How do I find out which format tokens Luxon supports?

According to the documentation on the Date & Time Node, n8n supports all the tokens that Luxon itself knows, whereby uppercase and lowercase each have their own meaning. For a complete overview of all available tokens, it is worth taking a look at the linked Luxon formatting reference directly from the n8n documentation.

About NordFlux

NordFlux UG (haftungsbeschränkt)

NordFlux builds digital employees for organisations: automations and AI agents that take over repetitive work. You stay in control.

More about us
Free initial analysis

Concrete questions about automation or AI?

In a free initial analysis we discuss your case directly. No strings attached.