I was sweeping GraphAcademy course content for deprecated Cypher this week. The checker prepends EXPLAIN to every query in every lesson and collects any notifications Neo4j sends back.
The EXPLAIN keyword instructs Neo4j to plan the query without executing it. This is a quick way to test the syntax of a query. In our case, it allows us to highlight any deprecated keywords or functions in the .
The following query was run to test a query in Cypher Intermediate Queries that formatted a date and time using apoc.temporal.format.
EXPLAIN
WITH datetime() AS datetime
RETURN apoc.temporal.format(datetime, 'HH:mm:ss.SSSS') AS formattedDateTimeIn running this query, we were informed that:
apoc.temporal.format has been replaced by Cypher's format function.When issues like this come up, we use a built-in MCP server to fact-check against the Neo4j documentation and confirm the correct syntax. In this case, we needed to replace apoc.temporal.format() with the format() function.
The Cypher format() function
The format() function, available in Cypher 25 and introduced in version Neo4j 2025.09, takes a temporal value — DATE, LOCAL TIME, ZONED TIME, LOCAL DATETIME, ZONED DATETIME, or a DURATION — and returns a STRING.
The pattern follows the same syntax as Java's DateTimeFormatter, so anything you wrote for apoc.temporal.format carries over unchanged:
WITH datetime('1986-11-18T6:04:45.123456789+01:00[Europe/Berlin]') AS dt
RETURN format(dt, "MM/dd/yyyy") AS US, // (1)
format(dt, "dd/MM/yyyy") AS EU, // (2)
format(dt) AS ISO // (3)- 1
Returns the date as {month}/{day}/{year} - eg:
11/18/1986 - 2
Returns the date as {day}/{month}/{year} - eg:
18/11/1986 - 3
Returns the ISO 8601 format - eg
1986-11-18T06:04:45.123456789+01:00[Europe/Berlin]
If you omit the pattern, an ISO 8601 string is returned, the same result as calling the toString() function.
Pattern tokens at a glance
The full table is in thedocumentation, but these cover most formatting work. Repeating a letter controls width and text style: M gives 7, MM gives 07, MMM gives Jul, MMMM gives July.
| Token | Meaning | Example output |
|---|---|---|
y | Year-of-era | 2004; 04 |
M | Month | 7; 07; Jul; July |
d | Day-of-month | 10 |
H | Hour-of-day (0-23) | 0 |
m | Minute-of-hour | 30 |
s | Second-of-minute | 55 |
S | Fraction-of-second | 978 |
E | Day-of-week | Tue; Tuesday |
Q | Quarter-of-year | 3; Q3; 3rd quarter |
G | Era | AD |
z | Time zone name | PST; Pacific Standard Time |
B | Period-of-day | in the morning |
Literal text goes in single quotes. A documented example combines day-of-year (D) and localised day-of-week (c): the pattern "DDD'nd day of the year,' c'rd day of the week'" produces 322nd day of the year, 3rd day of the week.
It formats durations too
The format() function also provides something that APOC didn't: the ability to format a duration. Pass a DURATION and the tokens become units, with larger components rolling into smaller ones when you leave them out:
WITH duration({years: 1, months: 4}) AS d
RETURN format(d, "y 'years' q 'quarters' M 'months'") AS withYears, // (1)
format(d, "q 'quarters' M 'months'") AS withoutYears // (2)- 1
Returns
1 years 1 quarters 1 months - 2
Returns
5 quarters 1 months
Migrating from APOC
The migration is close to mechanical, and we've already updated the Cypher Intermediate Queries course to match. Here's a reference table for the common replacements:
| Legacy APOC | Cypher replacement | Notes |
|---|---|---|
apoc.temporal.format(datetime, 'HH:mm:ss.SSSS') | format(datetime, 'HH:mm:ss.SSSS') | Drop the apoc.temporal. prefix and keep the pattern. |
apoc.date.toISO8601(datetime.epochMillis, "ms") | toString(datetime) | Pass the temporal value straight to toString(). |
Learn more with GraphAcademy
If you want to learn more about intermediate Cypher topics like this, take the Cypher Intermediate Queries course. The working-with-Cypher-data module covers dates, times, and durations from the ground up.
Until next time...
Comments (0)
Loading comments...