Table of Contents

Notable-date rule strategies

Every NotableDateRule answers three independent questions: when does the occurrence start, how long does it last, and how is it observed. This page is the catalogue for the first two - the occurrence source (a single-date strategy or a recurrence) and the duration (a fixed day count or a calculated end date). Observed-date shifting is a separate concern handled by Observance adjustment rules.

A rule carries exactly one occurrence source:

  • a <Strategy> - one of the single-occurrence strategies below, yielding at most one date per year; or
  • a <Recurrence> - one of the recurrence strategies, yielding zero, one, or many dates within a window.

It may additionally carry a <Duration> giving the occurrence a span. For the surrounding document model - concepts, applicability, imports, overrides - read Authoring notable date rules; for every attribute of the surrounding elements, NotableDateRule and adjustment-policy reference.


Choosing a strategy

Match how the date is defined to the strategy that expresses it directly. Prefer the simplest kind; reach for a reference-based or business-day strategy only when the date genuinely depends on another rule or on working days.

If the date is defined as… Use
A fixed month + day <Fixed>
A position from the start or end of a month <OrdinalDayOfMonth>
A position from the start or end of a year <DayOfYear>
A weekday within an ISO-8601 week <IsoWeekDate>
The nth or last weekday of a month <DayOfWeekInMonth>
A weekday near a fixed date <WeekdayNearDate>
A weekday relative to a weekday-in-month anchor <RelativeWeekdayInMonth>
A fixed day offset from another rule <OffsetFromRule>
A weekday near another rule's date <WeekdayNearRule>
The nth weekday before/after another rule <NthWeekdayFromRule>
A count of working days from another rule <WorkingDayOffsetFromRule>
The nth working day of a month <WorkingDayInMonth>
An astronomical / ecclesiastical date <Algorithm>
A repeating cadence (every n days / weeks / months) a recurrence source
Note

Yearly periodicity ("every second year") is not a strategy - it is everyYears / anchorYear on <Applicability>. See Year periodicity. Observed-date movement is not a strategy either - model it with an adjustment policy.


Single-occurrence strategies

Each maps to a public IDateCalculationStrategy; the contract is DateOnly? Calculate(int year, StrategyResolutionContext context), returning null when the rule produces no occurrence for the year. Out-of-range and overflowing dates return null rather than throwing.

Fixed and positional

The first six kinds pin a date by calendar position, with no dependency on any other rule.

<Fixed> - a fixed month and day

The same calendar position every year (see reference for skipLeapMonth / sweepCalendarYears and non-Gregorian calendars).

<Strategy><Fixed month="December" day="25" /></Strategy>

<OrdinalDayOfMonth> - a signed day-of-month

A day by its ordinal position from the start (positive) or end (negative) of a Gregorian month - the natural way to express "the last day of February" without a leap-year special case.

Attribute Required Type Description
month Yes string Month number 1-12 or an English month name.
ordinal Yes int Non-zero, -31…31. 1 = first day, -1 = last day, -2 = second-last.
<!-- Last day of February (28 or 29). -->
<Strategy><OrdinalDayOfMonth month="February" ordinal="-1" /></Strategy>

A positive ordinal beyond the month's length (e.g. 31 in April) yields no occurrence; the result always stays within the named month. Maps to OrdinalDayOfMonthStrategy.

<DayOfYear> - a signed day-of-year

A date by its ordinal position from 1 January (positive) or 31 December (negative).

Attribute Required Type Description
ordinal Yes int Non-zero, -366…366. 1 = 1 January, -1 = 31 December.
<!-- Programmers' Day - the 256th day of the year. -->
<Strategy><DayOfYear ordinal="256" /></Strategy>

366 exists only in a leap year; a positive ordinal beyond the year's length yields no occurrence. Maps to DayOfYearStrategy.

<IsoWeekDate> - a weekday in an ISO week

A weekday within an ISO-8601 week of an ISO week-year. The year passed to the rule is interpreted as the ISO week-year, so the resolved date can fall in the previous or following Gregorian year.

Attribute Required Type Description
week Yes int ISO week 1-53.
dayOfWeek Yes day of week The weekday within the ISO week.
<!-- Monday of ISO week 1. -->
<Strategy><IsoWeekDate week="1" dayOfWeek="Monday" /></Strategy>

Week 53 is valid only for ISO years that contain 53 weeks; otherwise the rule produces no occurrence. Maps to IsoWeekDateStrategy.

Weekday-in-month

Three kinds resolve a weekday by its position in a month (see the reference for full attribute tables).

  • <DayOfWeekInMonth> - the nth or last weekday of a month (fourth Thursday in November).
  • <WeekdayNearDate> - a weekday on / before / after / nearest a fixed reference date (Monday on or before 24 May).
  • <RelativeWeekdayInMonth> - a weekday positioned relative to a weekday-in-month anchor (the Tuesday after the first Monday in November).
<Strategy><DayOfWeekInMonth month="11" dayOfWeek="Thursday" weekOrdinal="Fourth" /></Strategy>
<Strategy><WeekdayNearDate month="5" day="24" dayOfWeek="Monday" direction="OnOrBefore" /></Strategy>
<Strategy><RelativeWeekdayInMonth month="11" dayOfWeek="Monday" weekOrdinal="First"
                                  relativeDayOfWeek="Tuesday" direction="After" /></Strategy>

Reference-based

These derive a date from another rule's occurrence, resolved cycle-safely within the resource. Each takes notableDateRef (the referenced concept id) and an optional ruleRef; all but <OffsetFromRule> also accept referenceYearOffset, a signed year offset applied to the reference (so a December rule can anchor to a reference in the following year). A reference that is missing, ambiguous, circular, or itself a recurrence is reported as a load-time diagnostic.

<OffsetFromRule> - a signed offset from another rule

A fixed calendar-day offset (see reference). This is how Good Friday hangs off Easter Sunday.

<Strategy><OffsetFromRule notableDateRef="easter-sunday" ruleRef="default" offsetDays="-2" /></Strategy>

<WeekdayNearRule> - a weekday near another rule

The dynamic-reference twin of <WeekdayNearDate>: seek a weekday on / before / after / nearest another rule's date rather than a fixed month and day.

Attribute Required Type Description
notableDateRef Yes identifier The referenced concept id.
ruleRef No identifier A specific rule within that concept.
dayOfWeek Yes day of week The target weekday.
direction Yes proximity WeekdayProximity: Before, OnOrBefore, Nearest, OnOrAfter, After.
referenceYearOffset No int Signed year offset applied to the reference (default 0).
<!-- The first Monday after Easter Sunday. -->
<Strategy><WeekdayNearRule notableDateRef="easter-sunday" dayOfWeek="Monday" direction="After" /></Strategy>

Maps to WeekdayNearRuleStrategy.

<NthWeekdayFromRule> - the nth weekday from another rule

The nth matching weekday strictly after (positive ordinal) or before (negative ordinal) another rule's date. The reference date itself is never counted.

Attribute Required Type Description
notableDateRef Yes identifier The referenced concept id.
ruleRef No identifier A specific rule within that concept.
dayOfWeek Yes day of week The target weekday.
ordinal Yes int Non-zero. Positive counts strictly after, negative strictly before.
referenceYearOffset No int Signed year offset applied to the reference (default 0).
<!-- The second Monday after a referenced festival. -->
<Strategy><NthWeekdayFromRule notableDateRef="festival" dayOfWeek="Monday" ordinal="2" /></Strategy>

Maps to NthWeekdayFromRuleStrategy.

<WorkingDayOffsetFromRule> - a working-day offset from another rule

A count of working days before or after another rule's date. Unlike <OffsetFromRule> (which counts calendar days), this skips rest days and applicable non-working occurrences - so "one working day before Boxing Day" is correct even when the preceding calendar day is Christmas Day.

Attribute Required Type Description
notableDateRef Yes identifier The referenced concept id.
ruleRef No identifier A specific rule within that concept.
offsetWorkingDays Yes int Signed working-day count. The reference is the origin and is not counted; 0 returns it unchanged.
referenceYearOffset No int Signed year offset applied to the reference (default 0).
<!-- The last working day before Boxing Day. -->
<Strategy><WorkingDayOffsetFromRule notableDateRef="boxing-day" offsetWorkingDays="-1" /></Strategy>

The working-day set is computed deterministically and independently of the order concepts are declared - see Business-day strategies. Maps to WorkingDayOffsetFromRuleStrategy.

Business-day strategies

<WorkingDayOffsetFromRule> (above) and <WorkingDayInMonth> resolve against working days - days that are neither a rest day (outside the resource's working week, set on <ResolutionPolicy>) nor claimed by an applicable non-working notable-date occurrence. That working-day view is computed deterministically and is independent of resource-declaration order, so a business-day rule resolves the same regardless of where the holidays it depends on appear in the document. It is also cycle-safe: a business-day rule consulted while the working-day set is being built falls back to rest-days-only rather than recursing.

<WorkingDayInMonth> - the nth working day of a month

The nth working day from the start (positive) or end (negative) of a month.

Attribute Required Type Description
month Yes string Month number 1-12 or an English month name.
ordinal Yes int Non-zero. 1 = first working day, -1 = last working day.
<!-- The first working day of January (skips New Year's Day and the weekend). -->
<Strategy><WorkingDayInMonth month="January" ordinal="1" /></Strategy>

When the requested ordinal does not exist within the month the rule produces no occurrence rather than spilling into an adjacent month. Maps to WorkingDayInMonthStrategy. See also Working-day arithmetic.

Algorithm

<Algorithm> - a named calculator

Delegates to a built-in astronomical / ecclesiastical calculator (Easter, the equinoxes, Vesak, …) or a registered custom one (see reference and Date calculation algorithms).

<Strategy><Algorithm key="western-easter" /></Strategy>

Recurrence sources

A <Recurrence> replaces <Strategy> when a rule repeats on a sub-yearly cadence - every Monday, every 14 days, the 15th of every month, the last Friday of every month. It maps to a IDateRecurrenceStrategy and generates many base occurrences within the requested window. Every generated occurrence is a normal candidate: it independently receives the rule's category, non-working flag, duration/span, adjustment policy, and collision handling.

Results are deterministic, chronological, de-duplicated, and query-window invariant - whether a given date is an occurrence never depends on the size or start of the query range, because occurrences are computed from the anchor, not from the range. Recurrence never consults CultureInfo.CurrentCulture.

Anchors. An anchor is required only when the interval creates a phase that cannot otherwise be determined:

  • every day / week / month (interval 1) needs no anchor - every unit participates;
  • every n days, every n weeks, or every n months (interval > 1) requires an anchorDate.

<DailyInterval> - every n days

Attribute Required Type Description
anchorDate Yes date yyyy-MM-dd; occurrence zero and the phase of the series.
intervalDays No int ≥ 1 Days between occurrences (default 1).
<!-- Every 14 days from 1 January 2026 (fortnightly). -->
<Recurrence><DailyInterval anchorDate="2026-01-01" intervalDays="14" /></Recurrence>

Maps to DailyIntervalRecurrenceStrategy.

<Weekly> - selected weekdays every n weeks

Attribute / child Required Type Description
<Day dayOfWeek="…" /> Yes (1+) day of week One per selected weekday; duplicates are rejected.
intervalWeeks No int ≥ 1 Weeks between occurrences of each weekday (default 1).
anchorDate No* date Required when intervalWeeks > 1; phases each weekday series.
<!-- Every Monday and Friday. -->
<Recurrence>
  <Weekly intervalWeeks="1">
    <Day dayOfWeek="Monday" />
    <Day dayOfWeek="Friday" />
  </Weekly>
</Recurrence>

<!-- Every second Tuesday, phased from 6 January 2026. -->
<Recurrence>
  <Weekly intervalWeeks="2" anchorDate="2026-01-06">
    <Day dayOfWeek="Tuesday" />
  </Weekly>
</Recurrence>

Occurrences are emitted in chronological order regardless of the order the <Day> elements are declared. Maps to WeeklyRecurrenceStrategy.

<MonthlyDay> - a day-of-month every n months

Attribute Required Type Description
dayOfMonth Yes int 1-31 The calendar day.
intervalMonths No int ≥ 1 Months between occurrences (default 1; 3 = quarterly, 6 = semi-annual).
anchorDate No* date Required when intervalMonths > 1; its year/month is month zero.
invalidDayBehavior No enum Skip (default) or UseLastDayOfMonth when a month lacks the day.
<!-- The 31st of every month, clamped to the last day of shorter months. -->
<Recurrence><MonthlyDay dayOfMonth="31" invalidDayBehavior="UseLastDayOfMonth" /></Recurrence>

Month iteration uses a stable month anchor, so day-31 still evaluates March as the 31st even after February clamps - there is no month-to-month drift. Maps to MonthlyDayRecurrenceStrategy / InvalidDayOfMonthBehavior.

<MonthlyWeekday> - an ordinal weekday every n months

Attribute Required Type Description
dayOfWeek Yes day of week The target weekday.
weekOrdinal Yes ordinal First…Fifth, Last.
intervalMonths No int ≥ 1 Months between occurrences (default 1).
anchorDate No* date Required when intervalMonths > 1; its year/month is month zero.
<!-- The last Friday of every month. -->
<Recurrence><MonthlyWeekday dayOfWeek="Friday" weekOrdinal="Last" /></Recurrence>

Last is the final matching weekday; a Fifth that a month lacks simply skips that month. Maps to MonthlyWeekdayRecurrenceStrategy.


Durations: fixed or calculated

A duration gives an occurrence a span. It is independent of the occurrence source - a recurrence occurrence gets its own span exactly like a single-date one - and the resolved NotableDate always exposes a concrete Date, DurationDays, and inclusive EndDate.

Fixed - the durationDays attribute on the rule (or defaultDurationDays on the concept) sets a fixed day count, inclusive of the start; EndDate = Date + DurationDays − 1.

<Rule id="default" durationDays="7">
  <Strategy><Fixed month="October" day="4" /></Strategy>
</Rule>

Calculated - a <Duration><UntilDate> computes the span's end from a second strategy, producing a length that varies year to year and can cross the year boundary. A rule may declare a fixed durationDays or a calculated <Duration>, never both.

<UntilDate> attribute Required Type Description
startBoundary No enum DateBoundary: Inclusive (default) or Exclusive - is the start anchor part of the span?
endBoundary No enum Inclusive (default) or Exclusive - is the end anchor part of the span?
selection No enum EndDateSelection: FirstOnOrAfterStart (default) or FirstAfterStart.

The end strategy is evaluated for the start anchor's civil year and, when that yields nothing on/after the start, the following civil year. This models a year-end shutdown that begins the Friday before Boxing Day and ends the Monday after New Year's Day - 16 days some years, 9 in others:

<Rule id="default">
  <Strategy><WeekdayNearDate month="12" day="26" dayOfWeek="Friday" direction="Before" /></Strategy>
  <Duration>
    <UntilDate startBoundary="Exclusive" endBoundary="Exclusive" selection="FirstOnOrAfterStart">
      <Strategy><WeekdayNearDate month="1" day="1" dayOfWeek="Monday" direction="After" /></Strategy>
    </UntilDate>
  </Duration>
</Rule>

With both boundaries exclusive, the last day worked (that Friday) and the return-to-work Monday are outside the span; the occurrence's Date is the day after the Friday and its EndDate the day before the Monday. A span that resolves to fewer than one day, or whose end cannot be found in the two-year window, is simply not emitted. Maps to CalculatedEndDateDurationDefinition.


Common scenarios

I want to express… Author it as
New Year's Day <Fixed month="1" day="1" />
US Thanksgiving (4th Thursday of November) <DayOfWeekInMonth month="11" dayOfWeek="Thursday" weekOrdinal="Fourth" />
Good Friday (2 days before Easter) <OffsetFromRule notableDateRef="easter-sunday" offsetDays="-2" />
First Monday after Easter <WeekdayNearRule notableDateRef="easter-sunday" dayOfWeek="Monday" direction="After" />
Second Monday after a festival <NthWeekdayFromRule notableDateRef="festival" dayOfWeek="Monday" ordinal="2" />
Last day of February <OrdinalDayOfMonth month="2" ordinal="-1" />
Last working day before Boxing Day <WorkingDayOffsetFromRule notableDateRef="boxing-day" offsetWorkingDays="-1" />
First working day of the month <WorkingDayInMonth month="1" ordinal="1" />
Monday of ISO week 1 <IsoWeekDate week="1" dayOfWeek="Monday" />
Fortnightly event <Recurrence><DailyInterval anchorDate="2026-01-01" intervalDays="14" /></Recurrence>
Every Monday and Friday <Recurrence><Weekly><Day dayOfWeek="Monday" /><Day dayOfWeek="Friday" /></Weekly></Recurrence>
The 15th of every month <Recurrence><MonthlyDay dayOfMonth="15" /></Recurrence>
Last Friday of every month <Recurrence><MonthlyWeekday dayOfWeek="Friday" weekOrdinal="Last" /></Recurrence>
Quarterly on the 20th <Recurrence><MonthlyDay dayOfMonth="20" intervalMonths="3" anchorDate="2026-01-01" /></Recurrence>
A multi-day festival durationDays="7" on the rule
A variable-length year-end shutdown a <Duration><UntilDate> calculated span (above)
Every second year everyYears="2" on <Applicability> (not a strategy)
Move a holiday off the weekend an adjustment policy (not a strategy)

All of the above are equally expressible in JSON (lowercased property names) and through the fluent builder - see Authoring with the notable-date builder.


Where to go next