This site describes solve-engine 2.44.0, the version npm installs.
Date literals
Package:
DATETIME_PACKAGE. Registered bycreateEngine(); for a slimmer engine, register it explicitly (see choosing packages).
A date literal is one specific calendar day written out, like the 25th of December 2023. Solve reads several orders, so you can write a date the way you already do, and a setting fixes which order a purely numeric date is read in.
Several orders are recognised. Slashes, hyphens and dots all work, and the month may be spelled out.
25/12/2023 // Monday, December 25, 20232023-12-25 // Monday, December 25, 20232024-5-3 // Friday, May 3, 202425.12.2023 // Monday, December 25, 2023March 9, 2024 // Saturday, March 9, 202425 Dec 2026 // Friday, December 25, 20261 Oct 2026 // Thursday, October 1, 2026A spelled month may be written in full or shortened, Dec or December. oct
and dec are also the names of the octal and decimal conversions, and they stay
those straight after as, in or to, so 255 as dec is still 255.
Write a literal as one run of characters, with no spaces around its
separators. That is what tells a date from the arithmetic it is spelled
identically to: 2024-5-3 is a date, and the same digits spaced out are
subtraction.
2024 - 5 - 3 // 2,016Spacing decides it on its own, so a padded chain like 2024 - 05 - 03 is
subtraction too.
A date with a time of day
Section titled “A date with a time of day”A meeting, a deadline or a train is a day and a time on that day. Write the
time after the date, on its own or after at, or write the time first and the
day after on. Each reads as that moment: the same one the ISO form
2026-01-04T14:30 names, so the spellings always agree.
2026-01-04 14:30 // Sunday, January 4, 2026, 2:30:00 PM23 September 2026 3pm // Wednesday, September 23, 2026, 3:00:00 PM23 September 2026 at 3pm // Wednesday, September 23, 2026, 3:00:00 PM3pm on 23 September 2026 // Wednesday, September 23, 2026, 3:00:00 PM25/12/2026 9:30am // Friday, December 25, 2026, 9:30:00 AM2026-01-04 14:30:15 // Sunday, January 4, 2026, 2:30:15 PMThe time is written the way a clock time is anywhere else in Solve: 24-hour
14:30, 3pm, 9:30am, 3.30pm, or with seconds, 14:30:15. Midnight is
12am or 00:00, and a moment at midnight shows as the day alone.
Once written, the moment behaves like any other date and time: add a length of time to it, or measure the time between two of them.
23 September 2026 3pm + 90 minutes // Wednesday, September 23, 2026, 4:30:00 PMhours between 2026-01-04 9am and 2026-01-10 5pm // 152 hoursAfter on, anything that gives a date will do, a name included, so a note can
set the day once and put several times on it:
launch = 23 September 20263pm on launch // Wednesday, September 23, 2026, 3:00:00 PM5:30pm on launch // Wednesday, September 23, 2026, 5:30:00 PMA time that does not exist on the clock is refused by name rather than read as something else, and a month with no day takes no time, since there is no day to put it on:
2026-01-04 24:00 // ERROR: "24:00" is not a valid timeFebruary 2026 3pm // ERROR: Expected an operator or the end of the line, but found "3pm"On the days the clocks change, a time in the hour that is skipped or repeated
lands where the ISO form puts it. To read the moment in another zone, name the
zone the time is in, as in 3pm London on 23 September 2026 in Tokyo (see
time zones).
The boundary: a day named in words and then a time, tomorrow 3pm, is not read
this way. It belongs with the spoken relative dates, and until then the
time-first spelling covers it, since on takes a relative date as readily as
a fixed one (3pm on tomorrow).
Choosing the input order
Section titled “Choosing the input order”An all-numeric date can mean two different days. 03/04/2026 is the 3rd of
April to most of the world and the 4th of March in the United States, and
nothing in the characters says which. The input order is how you settle it.
By default a slash date is read day first (25/12/2023) and a hyphen date
month first (12-25-2023), unless it starts with a four-digit year, which
makes it ISO. Fix the order for every numeric separator with the
date.inputOrder setting:
new ExpressionEngine({ config: { date: { inputOrder: "MDY" } } });inputOrder | 12/25/2023 | 25/12/2023 | 2023/12/25 | 2023-12-25 | 25.12.2023 |
|---|---|---|---|---|---|
"auto" (default) | refused | 25 December 2023 | 6.74 | 25 December 2023 | 25 December 2023 |
"MDY" | 25 December 2023 | refused | 6.74 | 25 December 2023 | refused |
"DMY" | refused | 25 December 2023 | 6.74 | 25 December 2023 | 25 December 2023 |
"YMD" | refused | refused | 25 December 2023 | 25 December 2023 | refused |
“Refused” means the line reports what is wrong with the date rather than
answering something else; the next section covers what it says and how to turn
it off. 2023/12/25 is the one cell that stays arithmetic: a four-digit group
at the START of a slash run is ordinary division, because 1000/10/5 is 20
and people write that constantly.
Only ambiguous literals are affected. A spelled-out month (March 9, 2024) is
never ambiguous, and neither is a hyphen date that starts with a four-digit
year: 2023-12-25 has no reading but year, month, day, so it is read as ISO
whichever order is set, timestamp or not.
Reading the order from the reader’s machine
Section titled “Reading the order from the reader’s machine”A host that runs on the reader’s own machine can ask the machine instead of
choosing for everybody. "locale" reads the day, month and year order the
operating system writes dates in, so a British notepad reads day first and an
American one month first, with no setting to explain to anybody:
const engine = createEngine({ config: { date: { inputOrder: "locale" } } });A host that is not the reader (a server rendering someone else’s document) names the reader instead, with a BCP-47 language tag:
createEngine({ config: { date: { inputOrder: "locale", inputLocale: "en-US" } } });inputLocale is read only when inputOrder is "locale". Setting it beside
any other order changes nothing at all, which is deliberate: a field that
quietly switched inference on would turn a predictable mistake into a wrongly
read document rather than into no change.
Whatever you choose, getDateReading() reports what the engine settled on,
and where it came from. It is what a settings panel shows and what a bug
report should carry:
createEngine({ config: { date: { inputOrder: "locale" } } }).getDateReading();// { order: "DMY", orderSource: "host-locale", locale: "en-GB" }orderSource is one of "config" (you named an order), "locale" or
"host-locale" (it was read from a tag), "separator" (the "auto"
default), or "fallback", which means inference was asked for and could not
be made, so the engine is reading dates exactly as "auto" does. A
"fallback" is worth surfacing: it is the case where a reader should be
offered the choice by hand.
When a date cannot be read
Section titled “When a date cannot be read”A run of digits that is shaped like a date but that the chosen order cannot read is not answered as arithmetic. It says what is wrong.
There are two things that can be wrong, and they are separate because their fixes are. The order might be wrong, in which case the reading that would work is named:
12/25/2023 // ERROR: "12/25/2023" is not a date read day first: there is no month 25. Read month first it is 25 December 2023. Set date.inputOrder to "MDY" to read numeric dates month first.Or the date might not exist in any order, in which case no order is suggested, because none would help:
31/04/2026 // ERROR: "31/04/2026" is not a real date: April 2026 has 30 days.2026-02-29 // ERROR: "2026-02-29" is not a real date: February 2026 has 28 days.29 February 2026 // ERROR: "29 February 2026" is not a real date: February 2026 has 28 days.Both are ordinary error results on the line, not exceptions, and they carry
through the rest of the expression, so 31/02/2026 + 1 day reports the date
rather than answering a day count built on one that does not exist.
Before this behaviour existed, each of those lines answered a number instead:
12/25/2023 was 0.00, 31/04/2026 was 0.00, 2026-02-29 was 1,995, and
29 February 2026 was a fourteen-digit number, 29 multiplied by the instant
of the 1st of February. If a document relied on one of the first three,
date.onAmbiguous puts those numbers back. The fourth stays refused, since its
old number multiplied a date, and a date is refused in multiplication
everywhere: a moment has no size to multiply.
new ExpressionEngine({ config: { date: { onAmbiguous: "arithmetic" } } });The boundary, deliberately: only a run that could not be arithmetic is
refused. A four-digit group at the START of a slash run is left alone
(1000/10/5 is 20, 1024/8/2 is 64), and so is a run whose groups are all
one or two digits (12/13/14 is 0.07), because a two-digit year is too weak a
signal to hang a refusal on.
1000/10/5 // 201024/8/2 // 6412/13/14 // 0.07The one refusal onAmbiguous cannot switch off is the dot form, and it is the
one place this release takes an answer away. The dot form used to ignore
date.inputOrder altogether and always read day first, so a month-first engine
answered 25.12.2026 as 25 December while refusing 12.25.2026 outright. Both
were wrong for that reader, and in opposite directions.
| on a month-first engine | before | now |
|---|---|---|
25.12.2026 | Friday, December 25, 2026 | refused: not a date read month first |
12.25.2026 | a parse error | Friday, December 25, 2026 |
There is no arithmetic reading to fall back to here, because two dots are not division, which is why the setting cannot restore the old answer.