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:
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, "")