Reference
This page restates the language as lookup tables. The specification is the normative text; where wording differs, the specification governs.
Document keys
Section titled “Document keys”| Key | Required | Value |
|---|---|---|
version |
✓ | The spec version the document is written against, as an "x.y" string |
timezone |
✓ | An IANA Time Zone Database name ("Asia/Tokyo", "UTC"); fixed offsets are invalid |
resolvers |
Non-empty list of names the host must bind; omitted when there are none | |
calendar |
The definitions (below) | |
schedules |
✓ | Non-empty list of schedules, combined with OR |
label |
Annotation (below): one line, 1–100 characters | |
description |
Annotation (below): 1–1,000 characters, LF as the only line break |
Unknown keys are an error at every level — document, calendar, schedule, and the times object.
Calendar keys
Section titled “Calendar keys”| Key | Value | Role |
|---|---|---|
business_days |
date list or name | Top layer: days the organization works, overriding everything below |
business_holidays |
date list or name | The organization’s own closures |
holidays |
date list or name | Public holidays |
workweek |
non-empty list of day names | Bottom layer: the weekly default (omitted = mon–fri) |
business_hours |
non-empty list of windows | The window list behind "business_hour" |
date_sets |
object: name → date list | The open namespace of the document’s own named date lists (no layer semantics) |
- A date-list position accepts exactly two forms: an array of date
literals, or a name (a
date_setsentry, or a name the host binds to a resolver). A date-shaped string matches neither form - A date list may be empty — an explicit empty list states that there are no such days
Calendar vocabulary
Section titled “Calendar vocabulary”| Word | Asks |
|---|---|
weekday |
day-of-week ∈ Mon–Fri — fixed; workweek plays no part |
weekend |
day-of-week ∈ Sat–Sun — fixed |
holiday |
date ∈ holidays, that list alone |
business_day |
the stacked conclusion (procedure below) |
business_holiday |
the exact complement of business_day |
business_day(date): if date ∈ business_days: true — the top layer wins if date ∈ business_holidays: false if date ∈ holidays: false otherwise: day-of-week(date) ∈ workweek (workweek omitted = mon–fri)holiday requires the holidays definition; business_day /
business_holiday require all three of holidays /
business_holidays / business_days. Using a word without its
required definitions is a document validation error.
Schedule fields
Section titled “Schedule fields”| Field | Value | Notes |
|---|---|---|
years |
non-empty list of integers 1–9999 | Date axis |
months |
non-empty list of integers 1–12 | Date axis |
days |
non-empty list of day atoms | Date axis |
if |
filter tuple | Filters base days without moving them; applied before shift |
shift |
shift tuple | Moves each base day until a landing condition holds |
times |
list or grid object | Time part |
allday |
true |
Time part: a day-level occurrence |
every |
[count, unit] |
Time part: an interval sequence anchored at from |
from |
"YYYY-MM-DD HH:MM" |
Validity start, inclusive |
until |
"YYYY-MM-DD HH:MM" |
Validity end, exclusive |
label |
string | Annotation (below); inert |
description |
string | Annotation (below); inert |
- Exactly one of
times/allday/everyis required - A schedule with a top-level
everytakes noyears/months/days/shift/if, and requiresfrom fromis also required by the["every", N, "day"]day atom- An omitted date axis means no restriction on that axis
- The algebra: within an axis’s array — OR; between fields — AND; between schedules — OR
Annotations
Section titled “Annotations”label and description may appear on the document and on each
schedule. They are inert: never part of validation, evaluation, or
any query’s answer, preserved unmodified through round-trips, and not
identifiers (no uniqueness, no referring to a schedule by label).
| Field | Form |
|---|---|
label |
One line; 1–100 characters |
description |
1–1,000 characters; LF as the only line break |
- Control characters are forbidden (LF in
descriptionis the single exception), as are ZWSP, the word joiner, the BOM, and the bidi embedding / override / isolate controls. ZWJ / ZWNJ and the bidi marks are legal - Must contain at least one non-whitespace character — omit the key rather than write an empty annotation
Day atoms
Section titled “Day atoms”| Atom | Example | Selects |
|---|---|---|
| Number 1–31 | 25 |
That day of the month (a day the month does not have simply does not match) |
| Day name | "mon" |
That weekday |
| Calendar word | "holiday" |
The five vocabulary words above |
| Ordinal tuple | ["3rd", "mon"] |
The Nth / last such weekday of the month |
| End of month | "last_day_of_month" |
The last day of the month |
| Day-cycle tuple | ["every", 2, "day"] |
Every Nth day, counting the date of from as day one |
| Name | "founding-day" |
Membership in the named date set |
- Ordinals:
"1st""2nd""3rd""4th""5th""last" - Day names:
"mon""tue""wed""thu""fri""sat""sun" - The day-cycle count is an integer ≥ 1; the unit is the fixed
"day". The tuple is allowed only as an element ofdays, not as ashiftlanding condition or anifcondition - A date literal cannot be written in
days— give the date a name underdate_setsand refer to it - There are no negative day numbers
["prev" | "next", atom] exclusive: strictly before / after["prev" | "next", "or_same", atom] inclusive: the base day itself is tested first- The landing condition is a day atom (not a day-cycle tuple)
- Maximum displacement: 366 calendar days. A base day whose landing condition never holds within that range produces no occurrences
- Consecutive base days may land on the same day and collapse into a single match
- The landing day is not bound by the date axes and may move into an adjacent month or year
[atom] the day itself matches["not", atom] the day itself does not match["prev" | "next", atom] the neighbouring day matches["prev" | "next", "not", atom] the neighbouring day does not matchif filters the base days first; shift then moves what remains.
| Form | Example | Meaning |
|---|---|---|
| List | ["09:00", "12:00"] |
Non-empty enumeration of fixed times |
| Grid | {"every": [1, "hour"], "between": ["08:00", "20:00"]} |
Clock positions, laid out per day and per window |
| All-day | "allday": true |
A day-level occurrence; time does not apply to it |
The grid object:
every—[count, unit]. Unit and maximum count:"hour"24,"minute"1,440,"second"86,400 (one day’s worth in each unit); the count is an integer ≥ 1between— a window (half-open[start, end)), or"business_hour"(the window list ofcalendar.business_hours, which must then be defined). Omitted = the whole day [00:00, 24:00)- The grid anchors at the start of each window and carries nothing over from the previous day or window
every (directly on a schedule)
Section titled “every (directly on a schedule)”[count, unit] — the points from + k × interval (k = 0, 1, 2, …),
counting across days with no per-day re-anchoring.
- Unit:
"hour" | "minute" | "second"; the count is an integer ≥ 1 with no upper bound. The unit"day"is invalid here — whole-day cycles are the["every", N, "day"]atom combined withtimes fromis required;untilis optional
Literal forms
Section titled “Literal forms”| Kind | Form | Notes |
|---|---|---|
| Date | "YYYY-MM-DD" |
Proleptic Gregorian; years 1–9999; must be a real date |
| Time | "HH:MM" |
Zero-padded; no seconds; "24:00" is a token allowed only as a window end |
| Window | ["HH:MM", "HH:MM"] |
Half-open [start, end); start < end; windows must not overlap (touching is legal); cannot cross midnight |
| Date-time | "YYYY-MM-DD HH:MM" |
The from / until form; zero-padded, a single space (U+0020), no seconds, no 24:00 |
| Timezone | IANA name | "Asia/Tokyo", "UTC"; fixed offsets ("+09:00") are rejected |
| Version | "x.y" |
|
| Day of week | "mon"–"sun" |
A name — a date_sets key, a declared resolver, or the reference
written in a date-list position or in days — must be a non-empty
string that is not: digits only, time-shaped (HH:MM), date-shaped
(YYYY-MM-DD), or a reserved word. All names share one namespace: a
name must not be both a date_sets key and a declared resolver, and a
name that is used but neither defined nor declared is a document
validation error.
The reserved words:
weekday weekend holiday business_day business_holiday business_hourmon tue wed thu fri sat sun1st 2nd 3rd 4th 5th lastlast_day_of_monthnot prev next or_samehour minute second dayversion timezone resolvers calendar schedulesyears months days shift if times allday every between from untilholidays business_holidays business_days workweek business_hours date_setslabel descriptionValidation beyond the schema
Section titled “Validation beyond the schema”In addition to structural JSON Schema validation, implementations validate:
- every used name resolves — a
date_setsentry or a declared resolver; no name is both; every declared name is bound by the host - the calendar entries required by the calendar vocabulary in use are present
- start < end for every window, and windows do not overlap
- every date literal is a real date (
2026-02-30is well-formed but invalid) - the resolved instant of
fromis strictly earlier than the resolved instant ofuntil fromis present in a schedule that uses["every", N, "day"]- the timezone name exists in the IANA Time Zone Database as available to the implementation; fixed-offset strings are rejected
Deliberately unsupported
Section titled “Deliberately unsupported”Year cycles · relative intervals (“N seconds since the last run”) · computed dates anchored to a fixed date · anything that crosses the date plane and the time plane · windows that cross midnight · per-weekday business hours · user-defined window names · definition macros. The reasoning is in the specification’s Deliberately unsupported section.