Dates and calendars¶
A model's clock is a plain number in the base time unit and always starts at 0. That is the right choice for an abstract study: "minute 4320" needs no timezone and no leap year.
It is the wrong choice when the study is about real weeks. A shift starts at 06:00 every Monday, a plant shuts for Christmas, a tariff is dated, and a client report should say "Wednesday 10:00", not "minute 4320". For that, give the model a calendar.
A calendar is optional. Without one nothing changes: the same numbers, the same results, the same exports. With one, the numbers stay exactly as they are -- the engine never learns about dates -- and everything that shows a time shows a real instant.
Setting one¶
Run > Run experiment..., tick Model runs on real dates, and pick a start instant and a timezone. In the model file:
"simulation": {
"time_unit": "minutes",
"calendar": {
"start": "2026-01-05T08:00:00", // the real instant model time 0 stands for
"timezone": "Europe/Rome", // IANA name; UTC by default
"end": "2026-01-12T08:00:00" // optional: a run END DATE instead of a length
}
}
start written without an offset is read in timezone -- "08:00 at the plant", which is what
a planner means. Written with one (2026-01-05T07:00:00Z, ...+01:00) the offset wins. It is
never read in the machine's local time, so the same file runs identically on a laptop in Rome and
on a build server in UTC.
end, when you give it, replaces simulation.time_limit: the run length becomes the distance
from start to end.
On the command line:
naga run model.json --start 2026-01-05T08:00:00 --timezone Europe/Rome --until 2026-01-12T08:00:00
Weekday schedules¶
With a calendar, a capacity or arrival schedule can be written the way a roster is written: days of the week and wall-clock bands, instead of a list of durations.
{
"name": "agent_shifts", "kind": "capacity", "time_unit": "hours",
"off_value": 0,
"weekly": {
"mon": [["08:00", "12:00", 4], ["12:00", "16:00", 5]],
"tue": [["08:00", "16:00", 4]],
"sat": [["08:00", "12:00", 2]]
},
"exceptions": [
{"start": "2026-12-24", "end": "2026-12-26", "value": 0, "description": "Christmas"}
]
}
- A day with no entry, and any hour outside every band, takes
off_value(0 by default). - Bands run forwards within one day; a night shift is two bands (
22:00-24:00and00:00-06:00the next day). exceptionsoverride whole local days,endinclusive, last match wins -- holidays and shutdowns. Givevalueto run reduced rather than closed.- For an
"arrival"schedule the band value is a rate per the entry'stime_unit, exactly as a points schedule's is. repeatdoes not apply: a weekday grid repeats by the calendar.
Wall clock, not elapsed time¶
A weekday schedule means local wall-clock time. A 06:00 shift starts at 06:00 local on both sides of a daylight-saving change -- which is what a plant means, and is why the transition day is simply 23 or 25 hours long in model time. Elapsed-time arithmetic (a duration, a service time, the run length) is plain absolute time and never shifts.
It costs nothing at run time¶
A weekday grid is compiled once per run, over the run horizon, into the same
piecewise-constant [duration, value] points the engine has always evaluated. No datetime
arithmetic ever happens during a run, and a model without a calendar never touches any of this.
A run with no horizon at all compiles 52 weeks ahead; past that the last value holds.
Precedence over schedule_offset
A warm start's initial_state.schedule_offset says how far into its own cycle a points
schedule already is. A weekday schedule is absolute in calendar terms, so the offset does not
apply to it -- move the calendar start instead. Points schedules and repeating time series
keep honouring the offset exactly as before.
Dated time series¶
A timeseries_inputs entry can carry real instants instead of numbers:
{"name": "tariff", "timestamps": ["2026-01-05T00:00:00", "2026-01-05T08:00:00"],
"values": [0.15, 0.31], "beyond": "repeat", "period": "daily"}
timestampsreplacestimes(irregular shape); an ISOstartwith astepreplaces a numericstart(regular shape).periodmay be"daily","weekly"or"yearly"instead of a number.- Everything is converted to model time once, when the model is built.
Dated orders, due dates and warm start¶
Anywhere an absolute model time is accepted, a calendar model also accepts an ISO timestamp:
"orders": [{"id": "WO-1001", "release": "2026-01-05T08:00:00",
"due": "2026-01-06T14:30:00", "quantity": 2}],
"initial_state": {"resources": {"Scanner": {"down_until": "2026-01-05T09:30:00"}}}
Durations (age, remaining, a service time) stay numbers: they are lengths, not instants.
What you get back¶
| Where | With a calendar |
|---|---|
| Report header | a Calendar row: the run's real window and its timezone |
| Report and results charts | date axes -- hours, days, weeks, months or years, picked from the span |
| Playback clock | Mon 05 Jan 08:30 instead of t = 30.0 |
| Excel export | a real datetime column beside every time column, with a date format |
| CSV and JSON export | the same wall-clock column |
| MES twin | unchanged: a sync sets the model start to the snapshot instant |
The result frames themselves stay numeric, so a golden hash, a KPI and a scenario comparison are identical whether or not the model is dated. The wall-clock columns are added on the way out.
Validation¶
| Code | Meaning |
|---|---|
E_CALENDAR_START |
no start instant, or it is not ISO 8601 |
E_CALENDAR_TIMEZONE |
the timezone is not an IANA name |
E_CALENDAR_DATE |
a field written as a date is not an ISO date or timestamp |
E_SCHEDULE_NO_CALENDAR |
a weekday schedule, but the model has no calendar |
E_SCHEDULE_WEEKLY |
unknown day, malformed band, or overlapping bands |
E_SCHEDULE_EXCEPTION |
an exception has no start date, or an inverted range |
W_SCHEDULE_EXCEPTION_UNUSED |
an exception falls entirely outside the run |
E_SERIES_NO_CALENDAR |
a dated series, but the model has no calendar |
W_SERIES_BEFORE_START |
a dated series starts before, or ends after, the run window |
Worked examples¶
- the bundled model
call_centre_day-- weekday opening hours, a Saturday morning and a dated holiday. - the bundled model
biogas_plant_library-- a maize campaign in real autumn weeks, dated spreading windows and a weekly power price. - the bundled model
energy_site_library-- a dated week with daily profiles.