hyf

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

commit 2091016f6330db45bc614934728e43fa4c93ce9c
parent a553585aca98e1ba0a3ce4fbc62ded353d7d6ea3
Author: triesap <tyson@radroots.org>
Date:   Mon, 21 Sep 2026 12:57:01 +0000

core: add source and evaluation time types

Diffstat:
Asrc/hyf_core/domain/time.mojo | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/test_domain.mojo | 31+++++++++++++++++++++++++++++++
2 files changed, 89 insertions(+), 0 deletions(-)

diff --git a/src/hyf_core/domain/time.mojo b/src/hyf_core/domain/time.mojo @@ -0,0 +1,58 @@ +from std.collections import Optional + + +@fieldwise_init +struct DateOnly(Copyable, Movable): + var year: Int + var month: Int + var day: Int + + +@fieldwise_init +struct Timestamp(Copyable, Movable): + var epoch_seconds: Int + var timezone: Optional[String] + + +@fieldwise_init +struct TimeContext(Copyable, Movable): + var source_time: Timestamp + var ingestion_time: Timestamp + var evaluation_time: Timestamp + + +def date_only(year: Int, month: Int, day: Int) raises -> DateOnly: + if month < 1 or month > 12: + raise Error("date month must be between 1 and 12") + if day < 1 or day > 31: + raise Error("date day must be between 1 and 31") + return DateOnly(year=year, month=month, day=day) + + +def timestamp(epoch_seconds: Int, timezone: String) raises -> Timestamp: + if timezone.strip() == "": + raise Error("timestamp timezone must not be empty") + return Timestamp( + epoch_seconds=epoch_seconds, + timezone=Optional[String](String(timezone)), + ) + + +def zoneless_timestamp(epoch_seconds: Int) -> Timestamp: + return Timestamp(epoch_seconds=epoch_seconds, timezone=None) + + +def timestamp_has_zone(value: Timestamp) -> Bool: + return value.timezone is not None + + +def time_context( + source: Timestamp, ingestion: Timestamp, evaluation: Timestamp +) raises -> TimeContext: + if not timestamp_has_zone(source): + raise Error("source time requires a known timezone") + return TimeContext( + source_time=source.copy(), + ingestion_time=ingestion.copy(), + evaluation_time=evaluation.copy(), + ) diff --git a/tests/test_domain.mojo b/tests/test_domain.mojo @@ -224,3 +224,34 @@ def test_price_unknown_distinct_from_zero_and_no_fx() raises: _ = known_price(1, 2, "", "per_unit") with assert_raises(): _ = known_price(1, 2, "CAD", "per_kg_approx") + + +from hyf_core.domain.time import ( + date_only, + time_context, + timestamp, + timestamp_has_zone, + zoneless_timestamp, +) + + +def test_time_types_distinguish_source_and_evaluation() raises: + var source = timestamp(1789000000, "America/Vancouver") + var ingestion = timestamp(1789000100, "America/Vancouver") + var replay_later = timestamp(1789900000, "America/Vancouver") + var context = time_context(source, ingestion, replay_later) + assert_true(timestamp_has_zone(context.source_time)) + assert_true(timestamp_has_zone(context.evaluation_time)) + assert_true(context.evaluation_time.epoch_seconds > context.source_time.epoch_seconds) + var d = date_only(2026, 9, 25) + assert_equal(d.year, 2026) + assert_equal(d.month, 9) + assert_equal(d.day, 25) + var zoneless = zoneless_timestamp(1789000000) + assert_true(not timestamp_has_zone(zoneless)) + with assert_raises(): + _ = time_context(zoneless, ingestion, replay_later) + with assert_raises(): + _ = date_only(2026, 13, 1) + with assert_raises(): + _ = timestamp(1, "")