hyf

Context-aware query service for Radroots
git clone https://radroots.dev/git/hyf.git
Log | Files | Refs | README | LICENSE

commit a4c8a75380f18c3573c7aed1c03d0ad4a3aa3448
parent 2091016f6330db45bc614934728e43fa4c93ce9c
Author: triesap <tyson@radroots.org>
Date:   Mon, 21 Sep 2026 12:58:36 +0000

core: implement source-anchored date resolution

Diffstat:
Asrc/hyf_core/normalization/dates.mojo | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/test_domain.mojo | 23+++++++++++++++++++++++
2 files changed, 115 insertions(+), 0 deletions(-)

diff --git a/src/hyf_core/normalization/dates.mojo b/src/hyf_core/normalization/dates.mojo @@ -0,0 +1,92 @@ +from hyf_core.domain.time import DateOnly + + +@fieldwise_init +struct ResolvedDate(Copyable, Movable): + var resolution: String + var date: DateOnly + var ambiguity: String + + +def _days_from_civil(year: Int, month: Int, day: Int) -> Int: + var y = year + var m = month + if m <= 2: + y -= 1 + var era = y // 400 + if y < 0 and y % 400 != 0: + era -= 1 + var yoe = y - era * 400 + var adjusted_month = m - 3 if m > 2 else m + 9 + var doy = (153 * adjusted_month + 2) // 5 + day - 1 + var doe = yoe * 365 + yoe // 4 - yoe // 100 + doy + return era * 146097 + doe - 719468 + + +def _civil_from_days(z: Int) raises -> DateOnly: + var adjusted = z + 719468 + var era = adjusted // 146097 + if adjusted < 0 and adjusted % 146097 != 0: + era -= 1 + var doe = adjusted - era * 146097 + var yoe = (doe - doe // 1460 + doe // 36524 - doe // 146096) // 365 + var y = yoe + era * 400 + var doy = doe - (365 * yoe + yoe // 4 - yoe // 100) + var mp = (5 * doy + 2) // 153 + var d = doy - (153 * mp + 2) // 5 + 1 + var m = mp + 3 if mp < 10 else mp - 9 + if m <= 2: + y += 1 + return DateOnly(year=y, month=m, day=d) + + +def weekday_index(date: DateOnly) -> Int: + # 0 = Monday ... 6 = Sunday + var days = _days_from_civil(date.year, date.month, date.day) + return (((days + 3) % 7) + 7) % 7 + + +def _weekday_number(name: String) raises -> Int: + var lowered = name.lower() + var names = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"] + for index in range(len(names)): + if lowered == names[index]: + return index + raise Error("unknown weekday: " + name) + + +def resolve_weekday( + reference: DateOnly, weekday_name: String, direction: String +) raises -> ResolvedDate: + var target = _weekday_number(weekday_name) + var current = weekday_index(reference) + var delta = (target - current + 7) % 7 + if direction == "next": + if delta == 0: + delta = 7 + elif direction != "on_or_after": + raise Error("direction must be 'next' or 'on_or_after'") + var base = _days_from_civil(reference.year, reference.month, reference.day) + return ResolvedDate( + resolution="resolved", + date=_civil_from_days(base + delta), + ambiguity="none", + ) + + +def resolve_relative_expression( + expression: String, reference: DateOnly +) raises -> ResolvedDate: + var lowered = expression.lower() + var base = _days_from_civil(reference.year, reference.month, reference.day) + if lowered == "today": + return ResolvedDate( + resolution="resolved", date=reference.copy(), ambiguity="none" + ) + if lowered == "tomorrow": + return ResolvedDate( + resolution="resolved", + date=_civil_from_days(base + 1), + ambiguity="none", + ) + return resolve_weekday(reference, lowered, "next") diff --git a/tests/test_domain.mojo b/tests/test_domain.mojo @@ -255,3 +255,26 @@ def test_time_types_distinguish_source_and_evaluation() raises: _ = date_only(2026, 13, 1) with assert_raises(): _ = timestamp(1, "") + + +from hyf_core.normalization.dates import ( + resolve_relative_expression, + resolve_weekday, + weekday_index, +) + + +def test_source_anchored_relative_date_replay() raises: + var monday = date_only(2026, 9, 21) + assert_equal(weekday_index(monday), 0) + var friday = resolve_relative_expression("Friday", monday) + assert_equal(friday.resolution, "resolved") + assert_equal(friday.date.year, 2026) + assert_equal(friday.date.month, 9) + assert_equal(friday.date.day, 25) + var same = resolve_weekday(monday, "Monday", "on_or_after") + assert_equal(same.date.day, 21) + var next = resolve_weekday(monday, "Monday", "next") + assert_equal(next.date.day, 28) + # Replay at a later evaluation time uses the same source reference. + assert_equal(resolve_relative_expression("Friday", monday).date.day, 25)