Skip to main content
CFDL

Schedules and calendars

Every model has one master timeline; every stream occurrence lands on it.

The timeline

time calendar monthly from 2026-01 for 72

Add a projection tail when a valuation needs periods beyond the hold (e.g. exit on forward NOI):

time calendar monthly from 2026-01 for 120 project 12

Phases name sub-ranges for organization and scoping:

phase lease_up from 2026-01 to 2027-06

The calendar is an assumption when events are involved

An event fires in the first period where its condition holds, and never between periods. So the calendar sets how precisely a condition can be met — which matters as soon as an event decides who gets paid.

An annual grid asks a covenant test, a flip test or a cash-trap test twelve times less often than a monthly one, and each miss delays the consequence to the next year end. On the tax-equity flip the same deal flips ten months later on an annual calendar than a monthly one, moving about $3.5m.

Choose the grid the test is actually performed on, not the grid the reporting happens to use.

Schedule patterns

One-time:

schedule on 2026-06

Recurring:

schedule every month from 2026-01 to 2026-12

Day rules:

schedule every month on day 15 from 2026-01 to 2026-12
schedule every month on eom from 2026-01 to 2026-12

YYYY-MM dates normalize to first-of-month; from must be ≤ to, and occurrences must land inside the model timeline (E2103). A schedule finer than the timeline, such as every week on a monthly calendar, is refused (E2108): model at the finest grain at which anything varies.

Placement in the period — start, mid or end — says where a payment sits and so how far it is discounted. A recurrence defaults to end, a one-time date to start, and a schedule states at most one (E2109):

schedule every year start from 2026-01 to 2030-01
schedule every year mid from 2026-01 to 2030-01
schedule on 2029-06 end

Business-day conventions roll a date that falls on a weekend or holiday: following, preceding, modified_following, modified_preceding and none, against a named calendar:

schedule on 2026-07-04 convention following calendar "us"

Exceptions stay in one schedule: except drops dates and also adds them:

schedule every day from 2026-07-01 to 2026-07-05 except [2026-07-03] also [2026-07-08]

A partial first or last period is not inferred: state it as its own schedule, a one-time payment beside the regular pattern.

Phase-anchored, so the schedule moves when the phase does:

schedule on phase_enter("operations")
schedule every quarter from phase_start("hold") to phase_end("hold")

State-anchored (the third anchor, beside dates and phases):

schedule every month from state_enter(asset.site, building) for 18 periods

Each entry of the entity into the state opens its own window of n grid periods; a re-entered state re-anchors with a fresh window. This is what "eighteen months of construction from whenever construction starts" needs — the machine enters the state whenever its edge fires, and the schedule hangs off the entry. See lifecycles and state.

A contract's timing

A pack contract states when its obligations run with term, as dates (term 2026-01..2030-12) or anchored on a state entry (term from state_enter(party.acme, renewed) for 60 periods), and how its lowered lines fall in their period with payment: a lag, payment net 45 or payment net 2 months, or a placement, payment start. A clause may name one line by its role, payment rent start, and then governs that line alone. The pack's rule decides how often the agreement pays.

Day counts

Accrual-style math uses year_frac(d1, d2, basis) in expressions, with "30/360" (US/bond), "act/360", "act/365", and actual/actual bases — decimal-exact. Date helpers: eomonth, edate, parse_date, months_between.