Table of Contents

Working-day arithmetic

Bodu.Globalization.Calendar ships parallel extension surfaces - NotableDateOnlyExtensions (over DateOnly), NotableDateTimeExtensions (over DateTime), and NotableDateTimeOffsetExtensions (over DateTimeOffset) - under the Bodu.Extensions namespace. Each method hangs off an INotableDateService and produces working-day-aware results that respect the working-week definition and the non-working notable dates for the queried territory.

These extensions are not auto-imported. Add the using directive explicitly:

using Bodu.Extensions;

Every method takes an INotableDateService service and a territory string. Working-day operations accept an optional trailing WeekPattern? workingWeek = null; notable-date operations accept an optional trailing NotableDateFilter? filter = null. The service is always passed explicitly - there is no ambient context.

DateOnly is the authoritative surface and carries the full method set. The DateTime and DateTimeOffset surfaces are a subset (see Surface differences).

The signatures below show the DateOnly overloads.

Lookup

Method Returns
IsWeekend(WeekPattern? workingWeek = null) true when the date falls outside the working week (no service needed).
IsWorkingDay(service, territory, WeekPattern? workingWeek = null) true when the date is neither a weekend nor a non-working notable date for the territory.
IsNonWorkingDay(service, territory, WeekPattern? workingWeek = null) true when the date is a weekend or a non-working notable date.
IsNotableDate(service, territory, NotableDateFilter? filter = null) true when any notable date (matching the optional filter) applies for the territory.
using Bodu.Extensions;
using Bodu.Globalization.Calendar;

DateOnly today = DateOnly.FromDateTime(DateTime.Today);

bool isWeekend  = today.IsWeekend();                       // weekend per the default Mon-Fri week
bool isHoliday  = today.IsNotableDate(service, "AU-NSW");
bool isWorking  = today.IsWorkingDay(service, "AU-NSW");
bool isClosed   = today.IsNonWorkingDay(service, "AU-NSW");
Method Effect
NextWorkingDay(service, territory, WeekPattern? workingWeek = null) Advance to the next working day.
PreviousWorkingDay(service, territory, WeekPattern? workingWeek = null) Retreat to the previous working day.
NextNonWorkingDay(service, territory, WeekPattern? workingWeek = null) Advance to the next non-working day. Throws InvalidOperationException if none is found.
PreviousNonWorkingDay(service, territory, WeekPattern? workingWeek = null) Retreat to the previous non-working day. Throws InvalidOperationException if none is found.
NextNotableDate(service, territory, NotableDateFilter? filter = null) The next NotableDate? matching the optional filter, or null when none is found.
PreviousNotableDate(service, territory, NotableDateFilter? filter = null) The previous NotableDate? matching the optional filter, or null.
NotableDateService service = AsiaPacificCalendarData.CreateService("AU-NSW");
DateOnly today = new DateOnly(2026, 1, 26);

DateOnly nextOpen    = today.NextWorkingDay(service, "AU-NSW");
DateOnly lastOpen    = today.PreviousWorkingDay(service, "AU-NSW");
NotableDate? holiday = today.NextNotableDate(service, "AU-NSW");

Snap

The Snap* operations are no-ops when the input is already a working day; otherwise they shift to the nearest working day in the requested direction.

Method Effect
SnapToWorkingDay(service, territory, WeekPattern? workingWeek = null) If the date is non-working, advance forward to the first working day.
SnapToWorkingDayBackward(service, territory, WeekPattern? workingWeek = null) If the date is non-working, retreat to the previous working day.
SnapToNearestWorkingDay(service, territory, WeekPattern? workingWeek = null) If the date is non-working, choose the closer of forward / backward snaps.
DateOnly saturday = new DateOnly(2026, 1, 3);                    // Saturday
DateOnly snapped  = saturday.SnapToWorkingDay(service, "AU-NSW"); // Monday 5 Jan 2026

Arithmetic

Method Effect
AddWorkingDays(int count, service, territory, WeekPattern? workingWeek = null) Add (or subtract, when count is negative) the signed number of working days, skipping non-working dates.
WorkingDaysBetween(DateOnly end, service, territory, WeekPattern? workingWeek = null) Count the working days between the receiver and end.
DateOnly inFive  = today.AddWorkingDays(5, service, "AU-NSW");
DateOnly fiveAgo = today.AddWorkingDays(-5, service, "AU-NSW");
int span         = today.WorkingDaysBetween(inFive, service, "AU-NSW");

Enumeration

The day enumerations return a lazily-evaluated IEnumerable<DateOnly> over the inclusive range from the receiver to end, so they compose naturally with LINQ and stop early when you do. EnumerateNotableDates returns an already-materialized IReadOnlyList<NotableDate> - a notable-date enumeration resolves the whole range up front rather than streaming.

Method Yields
EnumerateWorkingDays(DateOnly end, service, territory, WeekPattern? workingWeek = null) IEnumerable<DateOnly> - every working day in the inclusive range (lazy).
EnumerateNonWorkingDays(DateOnly end, service, territory, WeekPattern? workingWeek = null) IEnumerable<DateOnly> - every non-working day in the inclusive range (lazy).
EnumerateNotableDates(DateOnly end, service, territory, NotableDateFilter? filter = null) IReadOnlyList<NotableDate> - every notable date in the inclusive range (eager).
NotableDateService service = AsiaPacificCalendarData.CreateService("AU-NSW");
DateOnly start = new DateOnly(2026, 1, 1);
DateOnly end   = new DateOnly(2026, 1, 31);

foreach (DateOnly workday in start.EnumerateWorkingDays(end, service, "AU-NSW"))
{
    // process each open business day in January 2026
}

Notable-date lookups

Method Returns
GetNotableDates(service, territory, NotableDateFilter? filter = null) The notable dates that apply on the receiver date.
GetNotableDatesInMonth(service, territory, NotableDateFilter? filter = null) Notable dates in the calendar month containing the receiver. (DateOnly only.)
GetNotableDatesInYear(service, territory, NotableDateFilter? filter = null) Notable dates in the calendar year containing the receiver. (DateOnly only.)
IReadOnlyList<NotableDate> onDay     = today.GetNotableDates(service, "AU-NSW");
IReadOnlyList<NotableDate> thisMonth = today.GetNotableDatesInMonth(service, "AU-NSW");
IReadOnlyList<NotableDate> thisYear  = today.GetNotableDatesInYear(service, "AU-NSW");

To resolve a whole year independently of a receiver date, prefer the by-year service extension service.Resolve(2026, "AU-NSW"). See Using NotableDateService.

The working week

The optional trailing WeekPattern? workingWeek argument overrides the default Monday-Friday working week for a single call. WeekPattern is the WeekPattern value type from Bodu.Core; any day outside the pattern is treated as a weekend. Use the named presets for common shapes, or compose a custom pattern for non-standard schedules:

Preset Working days Weekend days
WeekPattern.MondayToFriday (default) Mon-Fri Saturday + Sunday (most western territories).
WeekPattern.SundayToThursday Sun-Thu Friday + Saturday (much of the Middle East).
WeekPattern.SundayToFriday Sun-Fri Saturday only.
WeekPattern.SaturdayToThursday Sat-Thu Friday only.
WeekPattern.SaturdayToWednesday Sat-Wed Thursday + Friday.
WeekPattern.MondayToSaturday Mon-Sat Sunday only.
WeekPattern.MondayToThursdayAndSaturday Mon-Thu + Sat Friday + Sunday.
WeekPattern.AllDays Every day No weekend - every day is working unless a non-working notable date applies.

WeekPattern is composable beyond these presets - see WeekPattern for building a custom selection from arbitrary days.

using Bodu;                 // WeekPattern
using Bodu.Extensions;

DateOnly today = DateOnly.FromDateTime(DateTime.Today);

// Sunday-Thursday working week (Friday/Saturday weekend, e.g. parts of the Middle East):
DateOnly nextOpen = today.NextWorkingDay(service, "AE", WeekPattern.SundayToThursday);
bool     isOpen   = today.IsWorkingDay(service, "AE", WeekPattern.SundayToThursday);

When omitted, the working-day extensions fall back to Monday-Friday. To bake a non-default working week into resolution itself (so adjustment triggers such as IfWeekend agree), set it on the resource's <ResolutionPolicy workingDays="…"> (a 7-character Sunday-first binary string). See Identity and resolution.

Fiscal-year helpers

NotableDateFiscalExtensions (over DateOnly) computes working-day boundaries of a fiscal year or quarter. Each method takes the month the fiscal year starts in (1-12), the service, the territory, and an optional working week:

Method Returns
FirstWorkingDayOfFiscalYear(int fiscalYearStartMonth, service, territory, WeekPattern? workingWeek = null) The first working day of the fiscal year containing the receiver.
LastWorkingDayOfFiscalYear(int fiscalYearStartMonth, service, territory, WeekPattern? workingWeek = null) The last working day of that fiscal year.
FirstWorkingDayOfFiscalQuarter(int fiscalYearStartMonth, service, territory, WeekPattern? workingWeek = null) The first working day of the fiscal quarter containing the receiver.
LastWorkingDayOfFiscalQuarter(int fiscalYearStartMonth, service, territory, WeekPattern? workingWeek = null) The last working day of that fiscal quarter.
using Bodu.Extensions;

DateOnly today = DateOnly.FromDateTime(DateTime.Today);

// Australian fiscal year starts in July (month 7):
DateOnly fyOpen  = today.FirstWorkingDayOfFiscalYear(7, service, "AU-NSW");
DateOnly fyClose = today.LastWorkingDayOfFiscalYear(7, service, "AU-NSW");
DateOnly qOpen   = today.FirstWorkingDayOfFiscalQuarter(7, service, "AU-NSW");

Surface differences

DateOnly is authoritative and carries every method above. The DateTime and DateTimeOffset surfaces are subsets:

Capability DateOnly DateTime DateTimeOffset
Lookup (IsWeekend, IsWorkingDay, IsNonWorkingDay, IsNotableDate) ✓ ✓ ✓
GetNotableDates ✓ ✓ ✓
Navigation (NextWorkingDay, PreviousWorkingDay) ✓ ✓ ✓
Snap (SnapToWorkingDay, SnapToWorkingDayBackward, SnapToNearestWorkingDay) ✓ ✓ ✓
AddWorkingDays, WorkingDaysBetween ✓ ✓ ✓
EnumerateWorkingDays, EnumerateNotableDates ✓ ✓ ✓
NextNonWorkingDay, PreviousNonWorkingDay, EnumerateNonWorkingDays ✓ ✓ -
NextNotableDate, PreviousNotableDate ✓ ✓ -
GetNotableDatesInMonth, GetNotableDatesInYear ✓ - -
Fiscal helpers (NotableDateFiscalExtensions) ✓ - -

When you need the month/year notable-date lookups or the fiscal helpers, work in DateOnly. The same operations over DateTime / DateTimeOffset are otherwise identical in shape.

Public holidays vs. observances

Working-day arithmetic respects a resolved date's IsNonWorkingDay flag - not its category. A NotableDateCategory.PublicHoliday occurrence with IsNonWorkingDay = false does not cause working-day arithmetic to skip the date. Conversely, a BankHoliday, Civic, or Cultural occurrence flagged non-working does skip.

Authors choose this when defining the rule (via defaultNonWorkingDay / nonWorking). The data packs follow the convention that nationally legislated closures (public holidays, bank closures) are non-working, while purely commemorative observances are not.

Territory-specific calculation

Every method takes a territory string. Because territories are hierarchical, a call with "AU-NSW" honours both national AU rules and NSW-specific AU-NSW rules. See Territories and regional composition for the containment semantics.

Where to go next