Authoring with the notable-date builder
Bodu.Globalization.Calendar.Builder is a fluent, chainable API for authoring notable-date documents in C#. It is the programmatic peer of the XML / JSON authoring path in Authoring notable date rules: both produce the same notable-date document, feed the same resolution pipeline, and validate through the same loader - but the builder keeps everything in code, so documents can be composed, cloned, serialized, and round-tripped without writing XML by hand.
For the vocabulary it assumes (document vs. resource, definition vs. rule, strategy, adjustment policy, nominal vs. observed) read Core concepts first. For the full type list see the Bodu.Globalization.Calendar.Builder reference.
The builder hierarchy
Each level is reached through a nested Add*(…, configure) callback that hands you the child builder and returns the parent for chaining:
| Builder | Configures | Obtained from |
|---|---|---|
| NotableDateDocumentBuilder | The whole <NotableDateResource> document |
NotableDateDocumentBuilder.Create(resourceId) |
| ResolutionPolicyBuilder | The <ResolutionPolicy> |
WithResolutionPolicy(configure) |
| AdjustmentPolicyBuilder | A reusable <AdjustmentPolicy> |
AddAdjustmentPolicy(id, configure) |
| NotableDateDefinitionBuilder | One <NotableDate> concept |
AddNotableDate(id, displayName, category, configure) |
| NotableDateRuleBuilder | One <Rule> |
AddRule(id, configure) |
| ImportBuilder / ImportUseBuilder | <Imports> from the common catalogues |
AddImport(resource, configure) |
| OverrideBuilder | ID-targeted <Overrides> |
AddOverride(configure) |
Building a document
Create starts an empty document; AddNotableDate adds a concept, and each concept holds one or more rules:
NotableDateDocumentBuilder builder = NotableDateDocumentBuilder.Create("contoso.holidays")
.WithMetadata(name: "Contoso holidays", description: "Company observances.")
.AddNotableDate("new-years-day", "New Year's Day", NotableDateCategory.PublicHoliday, d => d
.AsNonWorkingByDefault()
.AddRule("default", r => r.ForTerritory("US").Fixed(1, 1)));
Rules and strategies
NotableDateRuleBuilder sets the rule's scalars (WithPriority, WithCategory, AsNonWorking, WithDurationDays, WithComment, AddTag), its applicability (ForCalendar, ForTerritory / ForTerritories, FromYear, ToYear, EveryYears, AnchorYear, OnlyYears, ExceptYears), its adjustment references (WithAdjustment), and exactly one occurrence source - a single-date strategy or a frequency-based recurrence source. The most common:
r.Fixed(1, 1); // 1 January (month name or number)
r.DayOfWeekInMonth(11, DayOfWeek.Thursday, WeekOrdinal.Fourth); // 4th Thursday in November
r.WeekdayNearDate(5, 24, DayOfWeek.Monday, WeekdayProximity.OnOrBefore);
r.RelativeWeekdayInMonth(11, DayOfWeek.Monday, WeekOrdinal.First, DayOfWeek.Tuesday, WeekdayProximity.After);
r.OffsetFromRule("easter-sunday", -2, ruleRef: "default"); // Good Friday = Easter − 2
r.Algorithm("western-easter"); // a named algorithm key
The remaining single-date strategies (OrdinalDayOfMonth, DayOfYear, IsoWeekDate, WeekdayNearRule, NthWeekdayFromRule, WorkingDayOffsetFromRule, WorkingDayInMonth) and the recurrence sources (DailyInterval, Weekly, MonthlyDay, MonthlyWeekday) follow the same shape. Selecting a second strategy on the same rule throws InvalidOperationException - each rule commits to one. See the strategy reference for the full catalogue and Date calculation algorithms for the strategy semantics and the <Algorithm> keys.
Adjustment policies
A weekend-substitution or "move to the next working day" shift is authored once as a reusable policy and referenced from any rule by id (there are no inline per-rule adjustments):
builder
.AddAdjustmentPolicy("weekend-to-monday", a => a
.WithDescription("Observe weekend holidays on the next working day.")
.When(AdjustmentTrigger.IfWeekend)
.Then(AdjustmentAction.MoveToNextWorkingDay)
.Emit(Bodu.Globalization.Calendar.RangeResolution.EmissionMode.ObservedOnly))
.AddNotableDate("anzac-day", "Anzac Day", NotableDateCategory.PublicHoliday, d => d
.AsNonWorkingByDefault()
.AddRule("default", r => r.ForTerritory("AU").Fixed(4, 25).WithAdjustment("weekend-to-monday")));
AdjustmentPolicyBuilder also exposes the trigger modifiers (OnTriggerWeekdays, WithTriggerMonth, …), action modifiers (WithActionDays, WithMaxSearchDays, SkipWeekends, WithReplacementRule, …), emission (WithReason, EmitNonWorking), handler parameters (WithParameter), and a WithScope(configure) callback over AdjustmentScopeBuilder. See Observance adjustment rules for the full trigger / action / emission catalogues.
Importing the common catalogues
AddImport pulls concepts from the bundled common catalogues; Use cherry-picks and re-scopes them. ImportUseBuilder exposes As(alias) (re-id the imported concept), ForTerritory, WithCategory, AsNonWorking, and WithAdjustment:
builder.AddImport("global-core", i => i
.Use("new-years-day", u => u
.As("us-new-years-day") // re-id to avoid clashing with a local concept
.ForTerritory("US")
.WithAdjustment("weekend-to-monday")));
An AddImport with no Use calls imports every concept the catalogue defines. Because the JSON subset cannot model imports, a document that calls AddImport serializes only as XML - see the note under Materializing.
Overrides
AddOverride authors ID-targeted edits applied at load time. OverrideBuilder offers three operations - AddRule (add a rule to an existing concept), PatchRule (replace an existing rule), and RemoveRule (suppress one):
builder.AddOverride(o => o
.RemoveRule("boxing-day", "default")
.PatchRule("anzac-day", "default", r => r.ForTerritory("AU").Fixed(4, 25).WithAdjustment("weekend-to-monday"))
.AddRule("company-founding-day", "hq", r => r.ForTerritory("US").Fixed(6, 15)));
Resolution policy
builder.WithResolutionPolicy(p => p
.WithDuplicatePolicy(Bodu.Globalization.Calendar.RangeResolution.DuplicatePolicy.KeepFirst)
.WithWorkingWeek(WeekPattern.MondayToFriday));
Materializing, serializing, and saving
A finished builder produces the document in several forms:
// 1. A built, validated resource - ready for a NotableDateService.
NotableDateResource resource = builder.Build(); // Build(resolver) when the document imports
NotableDateService service = new NotableDateService(resource);
// 2. An INotableDateResourceProvider (for the reloadable service / DI).
INotableDateResourceProvider provider = builder.ToProvider();
// 3. Serialized text - full-fidelity XML, or the JSON subset.
string xml = builder.ToXml(); // also ToXDocument()
string json = builder.ToJson(); // also ToJsonObject()
// 4. Straight to a file (format inferred from the extension).
builder.Save("holidays.xml");
builder.Save("holidays.json");
// …or pin the format explicitly when the extension does not imply it:
builder.Save("holidays.txt", NotableDateDocumentFormat.Xml);
Build() serializes to XML and loads through NotableDateResourceLoader, so the built resource is exactly what the runtime would load - and the same validation applies (Build() throws NotableDateValidationException on an invalid document).
Note
XML is the full-fidelity format. ToJson() / Save(*.json) emit the narrower JSON subset and throw NotSupportedException when the document uses a feature the JSON schema cannot model - imports, a non-Gregorian calendar, an XML-only trigger/action value, handler parameters, or scope year-bounds. Serialize those documents as XML.
Round-tripping and cloning
FromXml / FromJson parse a document back into a builder for editing, and Load(path) reads a file by extension - so you can load, mutate, and re-save:
NotableDateDocumentBuilder edited = NotableDateDocumentBuilder.Load("holidays.xml");
edited.AddNotableDate("juneteenth", "Juneteenth", NotableDateCategory.PublicHoliday, d => d
.AddRule("default", r => r.ForTerritory("US").Fixed(6, 19)));
edited.Save("holidays.xml");
NotableDateDocumentBuilder copy = edited.Clone(); // deep, independent copy
Round-tripping carries precise guarantees - builder-canonical XML is byte-stable, JSON is identity within its subset, and both formats resolve the same occurrences. Builder round-trip guarantees states the full contract, including the XML → JSON lossiness boundary.
Where to go next
- Builder round-trip guarantees - exactly what
FromXml/ToXml,FromJson/ToJson, andSave/Loadguarantee. - Authoring notable date rules - the XML / JSON document model the builder produces.
- NotableDateRule and adjustment-policy reference - the per-element field reference.
- Date calculation algorithms - the strategy kinds and the
<Algorithm>keys. - Using NotableDateService - resolving the documents you build.
Bodu.Globalization.Calendar.BuilderAPI reference - the full type list.- Globalization & Calendars guides - every guide in this topic: the runtime, companions, data packs, and the notable-date catalogue.