Task: Holiday-aware date picker widget
This page documents a task in the Calendar entity follow-ups: date picker, list-pagination fix, QuantLib materialization story. It captures the goal, current status, acceptance, and any notes or results.
Goal
A reusable Qt widget that lets a user pick a date while showing
which dates are holidays, given one or more calendars as input
(reusing calendar's holiday-adjustment data —
Calendar entity follow-ups: date picker, list-pagination fix, QuantLib materialization). Holidays for
the union of the input calendars are visually highlighted in the date
grid (weekends included, following the same convention QuantLib's
calendar classes use). When multiple calendars are given, it must be
clear which calendar(s) a given highlighted date's holiday is coming
from — e.g. a tooltip on hover naming the source calendar(s) — not
just an undifferentiated highlight. This widget becomes the standard
way to pick any date that is calendar-sensitive across the app (e.g.
trade dates, fixing dates, spot dates) — not just within
currency/currency-pair screens.
Depends on Model the calendar entity for codegen existing first, since the widget needs a real calendar → holiday-dates data source, not free text.
Also depends on Materialise QuantLib calendar holidays and support calendar adjustments:
this widget reads from that task's materialised calendar_dates table
(base QuantLib dates plus any calendar_adjustment overrides) rather
than calling into QuantLib live from the UI.
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Calendar entity follow-ups: date picker, list-pagination fix, QuantLib materialization |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-07-30 |
Acceptance
[X]A reusable widget exists (HolidayAwareDatePicker,ores.qt.api) that accepts one or more calendar codes and highlights their non-business dates in the popup date grid.[X]Weekends are highlighted using the same convention as holidays –calendar_date::is_business_daycovers both, undistinguished by design (see its own doc comment), so the widget highlights every non-business date uniformly rather than re-deriving a separate weekend mask client-side.[X]Hovering a highlighted date shows a tooltip naming which input calendar(s) it's non-business in — clear attribution when multiple calendars are given, not just an undifferentiated highlight.[X]Widget is documented in the Qt UI patterns doc (new "Reusable input widgets" section), alongside its siblingsOreDateEditandCalendarAssignmentWidget, so other screens know to reuse it instead of a plain date picker for calendar-sensitive dates.[X]At least one real usage wired up as a proof point:SwapInstrumentForm'sfraFixingDateEdit(FRA Fixing Date).[X]The date picker highlights materialisedcalendar_datesrows fetched from the server, not a live QuantLib computation — sourced from Materialise QuantLib calendar holidays and support calendar adjustments viafetch_calendar_holidays().
Plan
(Implementation strategy. Written when work starts; key decisions
are distilled into the parent story's * Decisions at close, but the
plan itself stays — it is the historical record of what we did.)
Data source: paged fetch of materialised calendar_dates, not a new bulk endpoint
The server already exposes
refdata.v1.calendar_dates.list_by_calendar_code (offset/limit,
ordered by date ascending – the same request the Browse Holidays
list window uses). Rather than adding a new bulk "give me all
holidays" endpoint, fetch_calendar_holidays() (ores.qt.api) pages
through the existing request per calendar code, stopping as soon as a
page's dates run past the requested window's end – cheap in the
common case (a few hundred rows at most for a practical window) and
reuses proven server-side plumbing rather than adding a parallel
data path.
Window sizing: lazy, bounded, not the full materialisation horizon
calendar_dates is materialised out to a multi-decade horizon
(default 2050) per the parent materialisation task – eagerly
fetching that whole range per calendar for every date picker would be
wasted work for the overwhelming common case (a date a few months to
a couple of years out). The widget instead loads a practical window
(1 year back, 5 years forward) around whichever month its popup
happens to be showing, expanding on demand as the user navigates
further via QCalendarWidget::currentPageChanged. A curve reaching
decades out (e.g. a 30Y swap) would need to navigate the popup that
far to see highlighting that far – a deliberate, documented trade-off
of eager-fetch cost against picker responsiveness, not an oversight.
Highlight mechanism: setDateTextFormat, not a custom paintCell override
QCalendarWidget::setDateTextFormat() (a supported public API for
per-date styling) does the highlighting – no need to subclass
QCalendarWidget and override its paint logic. Tooltips, which
QCalendarWidget has no built-in per-cell API for, use an event
filter on its internal QAbstractItemView (QEvent::ToolTip,
reconstructing the hovered cell's date from the view's model index,
the shown month/year, and firstDayOfWeek() – the model has no
header rows/columns of its own, headers are drawn by the view
separately).
Real usage: SwapInstrumentForm's FRA Fixing Date, with a static TARGET default
fraFixingDateEdit (SwapInstrumentForm.ui) swapped from
OreDateEdit to HolidayAwareDatePicker as the proof point. Its
calendar is hardcoded to TARGET rather than derived from the
form's own currency selection: no client-facing currency-to-calendar
lookup exists today – currency_calendar is a junction with no
NATS-exposed service/protocol layer of its own (the same precedent
CalendarAssignmentWidget's own doc comment already documents), so
deriving it dynamically would be new, unrelated scope. Documented as
a known simplification in the wiring code itself, not silently
passed off as the finished article.
Notes
Requested again during manual QA of the calendar Qt screens task
(2026-07-19): tester asked for "some kind of hand crafted notebook page
to show the holidays for the calendar" when reviewing a CalendarDetailDialog
row. This task's scope (holiday-aware picker, reusable everywhere a
calendar-sensitive date is entered) covers that need; no longer
blocked, since Model the calendar entity for codegen has landed.
Manual verification: partial
Launched the Qt client (compass client start) against the running
dev environment – the app started cleanly, loaded every plugin
(including ores.qt.trading, which now links the widget), and
reached the login screen with no crash, confirming the new code
loads and runs, not just compiles. Did not click through to
SwapInstrumentForm and visually confirm the popup calendar's
highlighting/tooltip: this environment has no display/screenshot
tool available to this session to drive or observe the GUI
interactively. Flagging honestly rather than claiming a full manual
click-through that didn't happen – a human with display access (or
a future screenshot-capable tool) should do that pass before/soon
after merge.
PRs
Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 (PR #1769, x4 reviewers) | Stale highlights + stale in-flight fetch survive a setCalendarCodes() re-call with different codes | HolidayAwareDatePicker.cpp/.hpp | Fixed | Reset previously-highlighted dates' QTextCharFormat in setCalendarCodes(); added a generation counter, bumped per setCalendarCodes() call and checked in loadWindow()'s finished handler, so a stale in-flight fetch's result is discarded rather than merged in. |
| 2 | Window expansion re-fetches the whole merged range from scratch each time (protocol has no date-range filter, only offset/limit) | HolidayAwareDatePicker.cpp, CalendarHolidayFetcher.cpp | Declined | Not a correctness bug – flagged as a protocol-level limitation, not fixable client-side without a new server-side date filter, which is separate scope from this widget. |
| 3 | errorMessage signal emitted but unwired by SwapInstrumentForm | SwapInstrumentForm.cpp | Declined | Intentional fail-open UX for the proof-point wiring; a real consumer would connect it, same as CalendarAssignmentWidget's own errorMessage/statusMessage signals. |
| 4 | cellDateAt() relies on undocumented QCalendarWidget internal view/model layout | HolidayAwareDatePicker.cpp | Declined | Correct as flagged – no public Qt API exists for "date at view index"; already commented in the code as a deliberate reliance on current Qt behaviour. |
Result
All six acceptance criteria met. Shipped HolidayAwareDatePicker
(ores.qt.api), a drop-in OreDateEdit replacement that highlights
non-business dates in its popup calendar for one or more calendars,
sourced from the server's materialised calendar_dates table via a
new fetch_calendar_holidays() helper (paged, windowed, never a
live QuantLib call). Tooltips on hover name which calendar(s) a
highlighted date applies to. Documented in the Qt UI patterns doc
alongside its siblings; wired into SwapInstrumentForm's FRA Fixing
Date field as the proof point.
Two deliberate, documented scope limits (see Plan):
- The widget loads a practical window (1y back / 5y forward) around the popup's shown month, not the full multi-decade materialisation horizon, expanding lazily as the user navigates further.
- The proof-point wiring uses a static
TARGETcalendar default rather than deriving it from the form's selected currency – no client-facing currency-to-calendar lookup exists yet (currency_calendarhas no NATS service/protocol layer, matchingCalendarAssignmentWidget's own precedent); wiring that up is new, separate scope.
Verified: full local build clean (linux-clang-debug-make,
including ores.qt.exe linking); ctest 72/74 passed, excluding
the two pre-existing, already-captured ores.iam failures unrelated
to this change. Manual verification was partial: the Qt client
launches cleanly with the new widget's code loaded (confirmed via
compass client start), but this session had no display/screenshot
tool to click through and visually confirm the popup's highlighting/
tooltip – flagged honestly rather than claimed. A human (or a
future screenshot-capable session) should do that pass.