Docs / Language & functions
a9script authoring pack — date & time functions
The date & time functions, with their arguments and a line of real script each. Called by bare name; nothing is imported. A call marked pauses does real work outside the script: the run is saved, and continues on the next line with the result.
formatDate(date, pattern) — immediate
Writes a date out in a pattern of your own, always in UTC. The tokens are YYYY, MM, DD, HH, mm and ss; every other character of the pattern is written as it stands.
date(string | number | date) — An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value fromnew Date(...). A datetime without a timezone is refused: it would mean a different moment on every machine that read it.pattern(string) — The shape of the answer, e.g.DD.MM.YYYYorYYYY-MM-DD HH:mm:ss. Keep punctuation between the tokens: a word that happens to spell one carries it.
Answers: The formatted text.
log(formatDate(input.event.receivedAt, "YYYY-MM-DD HH:mm"));
addDays(date, days) — immediate
Moves a date forwards or backwards by whole days and answers the new moment, in UTC. The time of day is kept.
date(string | number | date) — An ISO date, a datetime that carries its timezone, milliseconds since 1970, or a date value — the same datesformatDatetakes.days(number) — How many days to move. A negative count moves back.
Answers: The moved date as an ISO string, e.g. 2026-07-21T00:00:00.000Z.
const due = addDays(input.event.receivedAt, 14);
log("payable by " + formatDate(due, "YYYY-MM-DD"));
dateDiff(from, to) — immediate
How many whole days lie between two dates, counted as UTC calendar days: positive when to is the later one, negative when it is the earlier.
from(string | number | date) — The date to count from — an ISO date, milliseconds since 1970, or a date value.to(string | number | date) — The date to count to, in any of the same forms.
Answers: The number of days. Two moments on the same UTC day answer 0; two hours either side of midnight answer 1, because the question is which day each one falls on.
const age = dateDiff("2026-03-01", input.event.receivedAt);
log("the order is " + age + " days old");
now() — pauses
The current moment, in milliseconds. The same clock Date.now() reads, so the two can never disagree — and the reading is kept: a run that is saved and continued later still sees the moment it read.
Answers: Milliseconds since the start of 1970, as a number.
const startedAt = now();
log("started at " + formatDate(startedAt, "YYYY-MM-DD HH:mm:ss"));
today() — pauses
Today’s date in UTC. The day now() falls on, written out.
Answers: The date as YYYY-MM-DD.
log("the report covers " + today());
dateAdd(date, amount, unit, timeZone?) — pauses
Moves a moment forwards by whole units and answers the new moment. Months and years follow the calendar: one month after 31 January is 28 February, because there is no 31 February.
date(string | number | date) — An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value. A datetime without a timezone is refused: it would mean a different moment on every machine that read it.amount(number) — How many units to move. A whole number; a negative one moves backwards.unit(string) — Which unit to move by:year,month,week,day,hour,minute,secondormillisecond— plurals work too.timeZone(string, optional) — Whose calendar to walk, such asEurope/Berlin. Without it, UTC — where a day is always twenty-four hours. With it, a day is a day on that clock: 09:00 in Berlin plus one day is 09:00 the next morning even when the clocks moved overnight, while plus 24 hours is 10:00.
Answers: The new moment as ISO text, written in the timezone that was named — 2026-02-28T10:00:00.000Z when none was.
const due = dateAdd(input.event.receivedAt, 1, "month");
log("payable by " + dateFormat(due, "DD.MM.YYYY"));
const sameTimeTomorrow = dateAdd(input.event.receivedAt, 1, "day", "Europe/Berlin");
log("and again at " + dateFormat(sameTimeTomorrow, "HH:mm", "Europe/Berlin"));
dateSubtract(date, amount, unit, timeZone?) — pauses
Moves a moment backwards by whole units — dateAdd the other way. Note that the two do not always undo each other: one month after 31 January is 28 February, and one month before that is 28 January.
date(string | number | date) — An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value. A datetime without a timezone is refused: it would mean a different moment on every machine that read it.amount(number) — How many units to move back. A whole number.unit(string) — Which unit to move by:year,month,week,day,hour,minute,secondormillisecond— plurals work too.timeZone(string, optional) — Whose calendar to walk, such asEurope/Berlin. Without it, UTC — where a day is always twenty-four hours. With it, a day is a day on that clock: 09:00 in Berlin plus one day is 09:00 the next morning even when the clocks moved overnight, while plus 24 hours is 10:00.
Answers: The new moment as ISO text, written in the timezone that was named.
const since = dateSubtract(today(), 7, "days");
log("looking back to " + since);
dateDifference(from, to, unit, timeZone?) — pauses
How many WHOLE units lie between two moments, counted the same way dateAdd moves them — so adding the answer to from never passes to.
from(string | number | date) — The moment to count from. An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value. A datetime without a timezone is refused: it would mean a different moment on every machine that read it.to(string | number | date) — The moment to count to, in any of the same forms.unit(string) — Which unit to count in:year,month,week,day,hour,minute,secondormillisecond— plurals work too.timeZone(string, optional) — Whose calendar to walk, such asEurope/Berlin. Without it, UTC — where a day is always twenty-four hours. With it, a day is a day on that clock: 09:00 in Berlin plus one day is 09:00 the next morning even when the clocks moved overnight, while plus 24 hours is 10:00.
Answers: The number of whole units, positive when to is the later moment and negative when it is the earlier one. Anything short of a whole unit is dropped: two moments twenty-three hours apart are 0 days apart. One thing to know about leap days: 29 February to 28 February the next year is 0 years, because the anniversary is 1 March. For calendar days — which day each moment falls on, rather than how much time passed — use dateDiff.
const age = dateDifference(input.event.receivedAt, now(), "day");
log("the order is " + age + " days old");
dateIsBefore(date, other, unit?, timeZone?) — pauses
Is the first moment earlier than the second?
date(string | number | date) — An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value. A datetime without a timezone is refused: it would mean a different moment on every machine that read it.other(string | number | date) — The moment to compare against.unit(string, optional) — Compare at this granularity instead of to the millisecond —"day"asks whether the first falls on an earlier day, so two moments on one day answer false. One ofyear,month,week,day,hour,minute,secondormillisecond— plurals work too.timeZone(string, optional) — Whose day (or week, or month) it is — 23:30 UTC is already the next day in Tokyo. Only meaningful together with a unit, and refused without one. Defaults to UTC.
Answers: true or false.
if (dateIsBefore(input.event.receivedAt, today())) {
log("this one arrived before today");
}
dateIsAfter(date, other, unit?, timeZone?) — pauses
Is the first moment later than the second?
date(string | number | date) — An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value. A datetime without a timezone is refused: it would mean a different moment on every machine that read it.other(string | number | date) — The moment to compare against.unit(string, optional) — Compare at this granularity instead of to the millisecond. One ofyear,month,week,day,hour,minute,secondormillisecond— plurals work too.timeZone(string, optional) — Whose day (or week, or month) it is — 23:30 UTC is already the next day in Tokyo. Only meaningful together with a unit, and refused without one. Defaults to UTC.
Answers: true or false.
log(dateIsAfter("2026-07-14", "2026-07-13"));
dateIsEqual(date, other, unit?, timeZone?) — pauses
Are the two the same moment? With a unit, whether they fall in the same one — which is how to ask “were these on the same day” without comparing text.
date(string | number | date) — An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value. A datetime without a timezone is refused: it would mean a different moment on every machine that read it.other(string | number | date) — The moment to compare against.unit(string, optional) — Compare at this granularity:"day"answers true for any two moments on the same day — whose day it is depends on the timezone. One ofyear,month,week,day,hour,minute,secondormillisecond— plurals work too.timeZone(string, optional) — Whose day (or week, or month) it is — 23:30 UTC is already the next day in Tokyo. Only meaningful together with a unit, and refused without one. Defaults to UTC.
Answers: true or false.
if (dateIsEqual(input.event.receivedAt, now(), "day")) {
log("it arrived today");
}
dateIsBetween(date, start, end) — pauses
Is the moment inside the window? Both edges count as inside, so a window built from dateStartOf and dateEndOf holds every moment of the period and no more.
date(string | number | date) — An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value. A datetime without a timezone is refused: it would mean a different moment on every machine that read it.start(string | number | date) — The first moment of the window.end(string | number | date) — The last moment of the window.
Answers: true or false. A window whose start is after its end is refused rather than answering false for everything, which would look exactly like an empty window.
const from = dateStartOf(today(), "month");
const to = dateEndOf(today(), "month");
log(dateIsBetween(input.event.receivedAt, from, to));
dateStartOf(date, unit, timeZone?) — pauses
The first moment of the day, month, year or other unit the date falls in. A week starts on Monday.
date(string | number | date) — An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value. A datetime without a timezone is refused: it would mean a different moment on every machine that read it.unit(string) — Which period:year,month,week,day,hour,minute,secondormillisecond— plurals work too.timeZone(string, optional) — A timezone name such asEurope/Vienna, orUTC. Defaults to UTC — never the machine’s own, so the same script answers the same everywhere. A name nobody has is refused rather than quietly read as UTC.
Answers: The moment as ISO text, written in the timezone that was asked for. On a day whose clocks moved, this is that day’s real first moment — 2026-03-29T00:00:00.000+01:00 in Vienna, even though the day ends on +02:00.
const monthStart = dateStartOf(now(), "month", "Europe/Vienna");
log("the month began at " + monthStart);
dateEndOf(date, unit, timeZone?) — pauses
The LAST millisecond of the day, month, year or other unit the date falls in.
date(string | number | date) — An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value. A datetime without a timezone is refused: it would mean a different moment on every machine that read it.unit(string) — Which period:year,month,week,day,hour,minute,secondormillisecond— plurals work too.timeZone(string, optional) — A timezone name such asEurope/Vienna, orUTC. Defaults to UTC — never the machine’s own, so the same script answers the same everywhere. A name nobody has is refused rather than quietly read as UTC.
Answers: The moment as ISO text in the timezone that was asked for, e.g. 2026-02-28T23:59:59.999Z — the last millisecond, so dateIsBetween with it covers the whole period.
const monthEnd = dateEndOf(now(), "month");
log("the month runs until " + monthEnd);
dateParse(text, pattern, timeZone?) — pauses
Reads a date somebody else wrote, by the pattern it was written in — the way to take 14.07.2026 from a file or a form and turn it into a moment.
text(string) — The text to read. It must match the pattern exactly, with nothing left over.pattern(string) — The shape the text is in, from the number tokensYYYY,MM,DD,HH,mm,ssandSSS; everything else in the pattern must appear in the text as it stands. What the pattern does not name starts the period, soYYYY-MMreads as the first of that month at midnight, and a pattern that names no part of a date at all is refused rather than answering 1970. Month names and two-digit years can be written but not read — a name needs a language and26cannot say which century it means.timeZone(string, optional) — Which timezone the text is written in —2026-07-14 12:30means a different moment in Vienna than in UTC. Defaults to UTC. A wall clock that never happened, because the clocks jumped over it, answers the moment the day reached it; one that happened twice answers the first of them.
Answers: The moment as ISO text, written in the timezone it was read in.
const paidAt = dateParse("14.07.2026", "DD.MM.YYYY", "Europe/Vienna");
log("paid at " + paidAt);
dateFormat(date, pattern, timeZone?, locale?) — pauses
Writes a moment out in a pattern of your own, in the timezone you name — the same tokens formatDate uses, plus milliseconds and the names of months and days.
date(string | number | date) — An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value. A datetime without a timezone is refused: it would mean a different moment on every machine that read it.pattern(string) — The shape of the answer.YYYYandYYfor the year,MMfor the month andMMM/MMMMfor its name,DDfor the day andDDD/DDDDfor the weekday’s name,HH,mm,ssandSSSfor the time. Every other character is written as it stands.timeZone(string, optional) — A timezone name such asEurope/Vienna, orUTC. Defaults to UTC — never the machine’s own, so the same script answers the same everywhere. A name nobody has is refused rather than quietly read as UTC.locale(string, optional) — Which language the month and weekday names are written in — a language tag such asde-DE. Defaults toen-US, stated rather than taken from the machine, so the same script writes the same date everywhere. A tag nobody has data for is refused rather than quietly written in English.
Answers: The written text.
log(dateFormat(now(), "DDDD, DD. MMMM YYYY", "Europe/Vienna", "de-DE"));
dateGet(date, unit, timeZone?) — pauses
Reads one part of a date as a number — the year, the month, the day, the time, or which day of the week it is.
date(string | number | date) — An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value. A datetime without a timezone is refused: it would mean a different moment on every machine that read it.unit(string) — Which part:year,month(1–12),day,hour,minute,second,millisecond, orweekday(1 for Monday through 7 for Sunday).timeZone(string, optional) — A timezone name such asEurope/Vienna, orUTC. Defaults to UTC — never the machine’s own, so the same script answers the same everywhere. A name nobody has is refused rather than quietly read as UTC.
Answers: The part as a number. Which one it is depends on the timezone: 23:30 UTC is already the next day, with a different weekday, in Vienna.
if (dateGet(now(), "weekday", "Europe/Vienna") === 1) {
log("it is Monday in Vienna");
}
dateSet(date, unit, value, timeZone?) — pauses
Answers the same moment with one part changed — the way to say “nine o’clock that morning” or “the first of that month”.
date(string | number | date) — An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value. A datetime without a timezone is refused: it would mean a different moment on every machine that read it.unit(string) — Which part to change:year,month,day,hour,minute,secondormillisecond. The weekday cannot be set — move the date withdateAddinstead.value(number) — What to set it to. A value the part cannot hold is refused by name — the 31st of a month with 30 days says so, rather than quietly becoming the 1st of the next one. Changing the MONTH to a shorter one pulls the day back to its last day, exactly asdateAdddoes.timeZone(string, optional) — A timezone name such asEurope/Vienna, orUTC. Defaults to UTC — never the machine’s own, so the same script answers the same everywhere. A name nobody has is refused rather than quietly read as UTC.
Answers: The new moment as ISO text, written in the timezone that was asked for.
const nine = dateSet(dateStartOf(now(), "day", "Europe/Vienna"), "hour", 9, "Europe/Vienna");
log("the reminder goes out at " + nine);
isWeekend(date, timeZone?) — pauses
Is the date a Saturday or a Sunday?
date(string | number | date) — An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value. A datetime without a timezone is refused: it would mean a different moment on every machine that read it.timeZone(string, optional) — A timezone name such asEurope/Vienna, orUTC. Defaults to UTC — never the machine’s own, so the same script answers the same everywhere. A name nobody has is refused rather than quietly read as UTC.
Answers: true or false. In the timezone asked for: Friday evening in UTC is already Saturday in Tokyo.
if (isWeekend(now(), "Europe/Vienna")) {
log("holding this until Monday");
}
isLeapYear(date, timeZone?) — pauses
Does the year this date falls in have a 29 February?
date(string | number | date) — An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value. A datetime without a timezone is refused: it would mean a different moment on every machine that read it.timeZone(string, optional) — A timezone name such asEurope/Vienna, orUTC. Defaults to UTC — never the machine’s own, so the same script answers the same everywhere. A name nobody has is refused rather than quietly read as UTC.
Answers: true or false, by the full rule — 2000 was a leap year, 1900 was not. Which year the date falls in depends on the timezone: 23:30 UTC on New Year’s Eve is already the new year further east.
log(isLeapYear("2024-05-05"));
daysInMonth(date, timeZone?) — pauses
How many days the month this date falls in has.
date(string | number | date) — An ISO date (2026-07-14), a datetime that carries its timezone (2026-07-14T12:30:00Z), milliseconds since 1970, or a date value. A datetime without a timezone is refused: it would mean a different moment on every machine that read it.timeZone(string, optional) — A timezone name such asEurope/Vienna, orUTC. Defaults to UTC — never the machine’s own, so the same script answers the same everywhere. A name nobody has is refused rather than quietly read as UTC.
Answers: The number of days — 28, 29, 30 or 31.
const days = daysInMonth(now());
log("this month has " + days + " days");