Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2d4b1c3
WiP format function
rsill-neo4j Aug 27, 2025
03a8731
general structure, additions page entry, references
rsill-neo4j Aug 28, 2025
efeca11
examples for instance types
rsill-neo4j Aug 28, 2025
b594c52
added a block title
rsill-neo4j Aug 28, 2025
81a5bf5
examples for duration types
rsill-neo4j Aug 28, 2025
b9ad57a
clarification for duration conversions
rsill-neo4j Aug 29, 2025
2cbca9a
nicer considerations block
rsill-neo4j Sep 2, 2025
f1f9c6c
nicer character table for durations
rsill-neo4j Sep 2, 2025
17cc934
Merge branch 'dev' into document-format-function
rsill-neo4j Sep 2, 2025
b760987
reduced number of examples, reduced 'considerations' list, moved char…
rsill-neo4j Sep 3, 2025
64a517c
Merge branch 'dev' into document-format-function
rsill-neo4j Sep 3, 2025
935e5a7
improved examples
rsill-neo4j Sep 3, 2025
aa5a14c
review suggestions
rsill-neo4j Sep 4, 2025
dff9453
review suggestion
rsill-neo4j Sep 5, 2025
93e85bc
Update modules/ROOT/pages/functions/temporal/format.adoc
rsill-neo4j Sep 8, 2025
7b99147
review suggestions
rsill-neo4j Sep 8, 2025
ef697e4
Apply suggestions from code review
rsill-neo4j Sep 10, 2025
b7d6eb2
review suggestions
rsill-neo4j Sep 10, 2025
a8085ce
Merge branch 'dev' into document-format-function
rsill-neo4j Sep 10, 2025
bb499cd
replaced the additions page example query
rsill-neo4j Sep 10, 2025
34579f2
syntax fix
rsill-neo4j Sep 10, 2025
118abe6
Apply suggestions from code review
rsill-neo4j Sep 11, 2025
e4bc6f8
review suggestions
rsill-neo4j Sep 11, 2025
e9b8b32
removed labels from a test section
rsill-neo4j Sep 11, 2025
66ea4d7
Apply suggestions from code review
rsill-neo4j Sep 11, 2025
2d9bea5
EU <-> US examples
rsill-neo4j Sep 12, 2025
eda89a5
added cheat sheet tags
rsill-neo4j Sep 12, 2025
4abf925
Apply suggestions from code review
rsill-neo4j Sep 12, 2025
8389c85
Merge branch 'dev' into document-format-function
rsill-neo4j Sep 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/ROOT/content-nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
** xref:functions/string.adoc[]
** xref:functions/temporal/duration.adoc[]
** xref:functions/temporal/index.adoc[]
** xref:functions/temporal/format.adoc[]
** xref:functions/user-defined.adoc[]
** xref:functions/vector.adoc[]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ For more information, see xref:queries/select-version.adoc[].
| Feature
| Details

a|
label:functionality[]
label:new[]
[source, cypher, role="noheader"]
----
WITH datetime('2024-06-27T14:30:45.123456789+02:00[Europe/Paris]') AS dt
RETURN format(dt, "yyyy-MM-dd'T'HH:mm:ss.SSSZ") AS x
----

| Cypher's new xref:functions/temporal/format.adoc[format function] can create dynamically formatted string representations of temporal instance and duration types.

a|
label:functionality[]
label:new[]
Expand All @@ -40,6 +51,7 @@ New operator: `LockNodes`


a| Introduced xref::planning-and-tuning/operators/operators-detail.adoc#query-plan-lock-nodes[`LockNodes`] operator, sometimes used in conjunction with the `LockingMerge` operator to lock nodes.

|===

[[cypher-deprecations-additions-removals-2025.08]]
Expand Down
332 changes: 332 additions & 0 deletions modules/ROOT/pages/functions/temporal/format.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
:description: Cypher provides a function for creating dynamically formatted string representations of temporal instance and duration types.
:table-caption!:

[[query-functions-temporal-format]]
= Temporal functions - format

[[query-functions-temporal-format-function]]
== format()

The format function creates dynamically formatted string representations of temporal instance and duration types.
The output format can be customized via the `pattern` parameter.
Creating a pattern follows the rules for the link:https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html[Java DateTimeFormatter].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If no pattern is specified, the function returns an ISO-formatted string.

.Details
|===
| *Syntax* 3+| `format(value[, pattern])`
| *Description* 3+| Returns the temporal value as an ISO-formatted string or as a string formatted by the provided pattern.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we normally say STRING etc in these descriptions?

.3+| *Arguments* | *Name* | *Type* | *Description*
| `value` | `DATE \| LOCAL TIME \| ZONED TIME \| LOCAL DATETIME \| ZONED DATETIME \| DURATION` | A temporal value to be formatted.
| `pattern` | `STRING` | A pattern used to format the temporal value. If the pattern is not provided the value will be formatted according to ISO 8601.
| *Returns* 3+| `STRING`
|===

[[query-functions-temporal-format-instance-types]]
== Instance types
Copy link
Collaborator

@JPryce-Aklundh JPryce-Aklundh Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instance types and duration types being H2 (==) might become confusing navigation wise if more format functions are introduced (usually the functions themselves are on the H2 level).

I'll leave it up to you change for now or not (and so long as there is only 1, I think its fine to leave as is), but just thought I'd point it out :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd say let's leave it for now, but definitely address it when we add more.
heading levels shouldn't interfere with anchored links either, so this sort of refactoring ought to be safe


Cypher's instance types are `DATE`, `LOCAL TIME`, `ZONED TIME`, `LOCAL DATETIME` and `ZONED DATETIME`, refer to xref:/values-and-types/temporal.adoc#cypher-temporal-instants[temporal instants].

Use the characters in xref:#query-functions-temporal-format-instance-types-characters[] to create a string pattern for instance types.


[[query-functions-temporal-format-instance-types-examples]]
=== Examples

.Instance formatting, European and US American dates
======
.Query
[source, cypher, indent=0]
----
WITH datetime('1986-11-18T6:04:45.123456789+01:00[Europe/Berlin]') AS dt
RETURN format(dt, "MM/dd/yyyy") AS EU, format(dt, "dd/MM/yyyy") AS US
----
.Result
[role="queryresult",options="header,footer",cols="2*<m"]
|===
| EU | US
| "11/18/1986" | "18/11/1986"
2+d|Rows: 1
|===
======

.Instance formatting with day-of-week, month-of-year, day-of-month, era and year
======
.Query
[source, cypher, indent=0]
----
WITH datetime('1986-11-18T6:04:45.123456789+01:00[Europe/Berlin]') AS dt
RETURN format(dt, "EEEE, MMMM d, G uuuu") AS instanceString
----
.Result
[role="queryresult",options="header,footer",cols="1*<m"]
|===
| instanceString
| "Tuesday, November 18, AD 1986"
1+d|Rows: 1
|===
Four occurrences of `E` and `M` (text presentations) output the full form of the day and month.
======

.Instance formatting with day-of-year and localized day-of-week
======
.Query
[source, cypher, indent=0]
----
WITH datetime('1986-11-18T6:04:45.123456789+01:00[Europe/Berlin]') AS dt
RETURN format(dt, "DDD'th day of the year,' c'rd day of the week'") AS instanceString
----
.Result
[role="queryresult",options="header,footer",cols="1*<m"]
|===
| instanceString
| "322th day of the year, 3rd day of the week"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it is just an example, but it bothers me that we don't say 322nd 😅

1+d|Rows: 1
|===
======

.Instance formatting with clock-hour-of-day (1-24), minute-of-hour and time-zone name
======
.Query
[source, cypher, indent=0]
----
WITH datetime('1986-11-18T6:04:45.123456789+01:00[Europe/Berlin]') AS dt
RETURN format(dt, "k:mm z") AS CET, format(dt, "K:mm O") AS GMT
----
.Result
[role="queryresult",options="header,footer",cols="2*<m"]
|===
| CET | GMT
| "6:04 CET" | "6:04 GMT+1"
2+d|Rows: 1
|===
======

.Instance formatting with month-of-year, day-of-month, milli-of-day, minute-of-hour and second-of-minute
======
.Query
[source, cypher, indent=0]
----
WITH datetime('1986-11-18T6:04:45.123456789+01:00[Europe/Berlin]') AS dt
RETURN format(dt, "LLL d,' minute 'm', second 's', millisecond of the day 'A") AS instanceString
----
.Result
[role="queryresult",options="header,footer",cols="1*<m"]
|===
| instanceString
| "Nov 18, minute 4, second 45, millisecond of the day 21885123"
1+d|Rows: 1
|===
Three occurrences of `L` (number/text presentation) output the short form ("Nov").
======

.Instance formatting with pad next and week-based-year
======
.Query
[source, cypher, indent=0]
----
WITH datetime('1986-11-18T6:04:45.123456789+01:00[Europe/Berlin]') AS dt
RETURN format(dt, "pppYY") AS instanceString
----
.Result
[role="queryresult",options="header,footer",cols="1*<m"]
|===
| instanceString
| " 86"
1+d|Rows: 1
|===
The three occurrences of `p` add one space character of padding to the two digit form output by two occurrences of `Y`.
======


[[query-functions-temporal-format-instance-types-characters]]
=== String pattern characters

[#instance-character-table]
.Allowed characters for instance type string patterns
[options="header"]
|===
| Character | Meaning | Presentation | Examples
| `G` | era | text | AD; Anno Domini; A
| `u` | year | year | 2004; 04
| `y` | year-of-era | year | 2004; 04
| `D` | day-of-year | number | 189
| `M` / `L` | month-of-year | number/text | 7; 07; Jul; July; J
| `d` | day-of-month | number | 10
| `g` | modified-julian-day | number | 2451334
| `Q` / `q` | quarter-of-year | number/text | 3; 03; Q3; 3rd quarter
| `Y` | week-based-year | year | 1996; 96
| `w` | week-of-week-based-year | number | 27
| `W` | week-of-month | number | 4
| `E` | day-of-week | text | Tue; Tuesday; T
| `e` / `c` | localized day-of-week | number/text | 2; 02; Tue; Tuesday; T
| `F` | aligned-week-of-month | number | 3
| `a` | am-pm-of-day | text | PM
| `B` | period-of-day | text | in the morning
| `h` | clock-hour-of-am-pm (1-12) | number | 12
| `K` | hour-of-am-pm (0-11) | number | 0
| `k` | clock-hour-of-day (1-24) | number | 24
| `H` | hour-of-day (0-23) | number | 0
| `m` | minute-of-hour | number | 30
| `s` | second-of-minute | number | 55
| `S` | fraction-of-second | fraction | 978
| `A` | milli-of-day | number | 1234
| `n` | nano-of-second | number | 987654321
| `N` | nano-of-day | number | 1234000000
| `V` | time-zone ID | zone-id | America/Los_Angeles; Z; -08:30
| `v` | generic time-zone name | zone-name | Pacific Time; PT
| `z` | time-zone name | zone-name | Pacific Standard Time; PST
| `O` | localized zone-offset | offset-O | GMT+8; GMT+08:00; UTC-08:00
| `X` | zone-offset 'Z' for zero | offset-X | Z; -08; -0830; -08:30; -083015; -08:30:15
| `x` | zone-offset | offset-x | +0000; -08; -0830; -08:30; -083015; -08:30:15
| `Z` | zone-offset | offset-Z | +0000; -0800; -08:00
| `p` | pad next | pad modifier | 1
| `"` | escape for text | delimiter |
| `'` | single quote | literal |
|===

.Considerations
|===
| Most characters yield a different output when they are repeated.
| Some characters cannot be applied to certain types, for instance, `u` cannot be used to construct a string for a `LOCAL TIME` because it represents a year which is not part of the temporal value.
| For details, refer to the documentation of the link:https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html[Java DateTimeFormatter].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

| Any character that is not reserved, other than `[`, `]`, `{`, `}`, `#`, and `'`, are output directly. To ensure future compatibility it is recommended to wrap all characters that you want to output directly with single quotes.
|===


[[query-functions-temporal-format-duration-types]]
== Duration types

Use the characters in xref:#query-functions-temporal-format-duration-types-characters[] to create a string pattern for duration types.

Cypher's duration type `DURATION` has components and component groups, see xref:values-and-types/temporal.adoc#cypher-temporal-accessing-components-durations[components of durations].

If the string pattern contains a character from a component group but does not contain a character denoting a longer duration from the same group, `format()` converts the longer duration to the equivalent duration with the character that is present, for example a missing `y` (year) will be converted to four quarters, if `q` is present in the string pattern.
This is because without a reference point, there is no way of determining the specifics of a duration.


[[query-functions-temporal-format-duration-types-examples]]
=== Examples

.Duration formatting, years converted to quarters
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all these test we use the full duration constructor, even when test don't use all component groups. As the map is big and ugly, do we want it in all tests? Or a smaller map with only relevant values?

======
.Query
[source, cypher, indent=0]
----
WITH duration({years: 1, months: 4}) AS d
RETURN format(d, "y 'years' q 'quarters' M 'months'") AS withYears, format(d, "q 'quarters' M 'months'") AS withoutYears
----
.Result
[role="queryresult",options="header,footer",cols="2*<m"]
|===
| withYears | withoutYears
| "1 years 1 quarters 1 months" | "5 quarters 1 months"
2+d|Rows: 1
|===
======

.Duration formatting, weeks converted to days
======
.Query
[source, cypher, indent=0]
----
WITH duration({weeks: 3, days: 4}) AS d
RETURN format(d, "w 'weeks' d 'days'") AS withWeeks, format(d, "d 'days'") AS withoutWeeks
----
.Result
[role="queryresult",options="header,footer",cols="2*<m"]
|===
| withWeeks | withoutWeeks
| "3 weeks 4 days" | "25 days"
2+d|Rows: 1
|===
======

.Duration formatting with hours converted to minutes
======
.Query
[source, cypher, indent=0]
----
WITH duration({days: 4, hours: 5, minutes: 6, seconds: 7}) AS d
RETURN format(d, "m 'minutes' s 'seconds'") AS withMinutes, format(d, "s 'seconds'") AS withoutMinutes
----
.Result
[role="queryresult",options="header,footer",cols="2*<m"]
|===
| withMinutes | withoutMinutes
| "306 minutes 7 seconds" | "18367 seconds"
2+d|Rows: 1
|===
Not how the days cannot be converted to hours and do not affect the query result.
======


[[query-functions-temporal-format-duration-types-characters]]
=== String pattern characters
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there might be a line too much after this title

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the spacing in the preview?

i think this might be because of table title 🤔 it's correct in the source document (or rather, i'm pretty sure it wouldn't make a difference in this case)


[#duration-character-table]
.Allowed characters for a duration type string pattern
[options="header"]
|===
| Component Group | Characters | Presentation
.3+| Months | `y` / `Y`/ `u` | years
| `q` / `Q` | quarters
| `M` / `L` | months
.2+| Days | `w` / `W` | weeks
| `d` / `D` | days
.6+| Seconds | `h` / `H` / `k` / `K` | hours
| `m` | minutes
| `s` | seconds
| `n` / `S` | fraction-of-second
| `A` | milliseconds
| `N` | nanoseconds
|===
Loading