hyf

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

dates.mojo (4093B)


      1 from hyf_core.domain.time import DateOnly
      2 
      3 
      4 @fieldwise_init
      5 struct ResolvedDate(Copyable, Movable):
      6     var resolution: String
      7     var date: DateOnly
      8     var ambiguity: String
      9 
     10 
     11 def _days_from_civil(year: Int, month: Int, day: Int) -> Int:
     12     var y = year
     13     var m = month
     14     if m <= 2:
     15         y -= 1
     16     var era = y // 400
     17     if y < 0 and y % 400 != 0:
     18         era -= 1
     19     var yoe = y - era * 400
     20     var adjusted_month = m - 3 if m > 2 else m + 9
     21     var doy = (153 * adjusted_month + 2) // 5 + day - 1
     22     var doe = yoe * 365 + yoe // 4 - yoe // 100 + doy
     23     return era * 146097 + doe - 719468
     24 
     25 
     26 def _civil_from_days(z: Int) raises -> DateOnly:
     27     var adjusted = z + 719468
     28     var era = adjusted // 146097
     29     if adjusted < 0 and adjusted % 146097 != 0:
     30         era -= 1
     31     var doe = adjusted - era * 146097
     32     var yoe = (doe - doe // 1460 + doe // 36524 - doe // 146096) // 365
     33     var y = yoe + era * 400
     34     var doy = doe - (365 * yoe + yoe // 4 - yoe // 100)
     35     var mp = (5 * doy + 2) // 153
     36     var d = doy - (153 * mp + 2) // 5 + 1
     37     var m = mp + 3 if mp < 10 else mp - 9
     38     if m <= 2:
     39         y += 1
     40     return DateOnly(year=y, month=m, day=d)
     41 
     42 
     43 def weekday_index(date: DateOnly) -> Int:
     44     # 0 = Monday ... 6 = Sunday
     45     var days = _days_from_civil(date.year, date.month, date.day)
     46     return (((days + 3) % 7) + 7) % 7
     47 
     48 
     49 def _weekday_number(name: String) raises -> Int:
     50     var lowered = name.lower()
     51     var names = [
     52         "monday",
     53         "tuesday",
     54         "wednesday",
     55         "thursday",
     56         "friday",
     57         "saturday",
     58         "sunday",
     59     ]
     60     for index in range(len(names)):
     61         if lowered == names[index]:
     62             return index
     63     raise Error("unknown weekday: " + name)
     64 
     65 
     66 def resolve_weekday(
     67     reference: DateOnly, weekday_name: String, direction: String
     68 ) raises -> ResolvedDate:
     69     var target = _weekday_number(weekday_name)
     70     var current = weekday_index(reference)
     71     var delta = (target - current + 7) % 7
     72     if direction == "next":
     73         if delta == 0:
     74             delta = 7
     75     elif direction != "on_or_after":
     76         raise Error("direction must be 'next' or 'on_or_after'")
     77     var base = _days_from_civil(reference.year, reference.month, reference.day)
     78     return ResolvedDate(
     79         resolution="resolved",
     80         date=_civil_from_days(base + delta),
     81         ambiguity="none",
     82     )
     83 
     84 
     85 def resolve_relative_expression(
     86     expression: String, reference: DateOnly
     87 ) raises -> ResolvedDate:
     88     var lowered = expression.lower()
     89     var base = _days_from_civil(reference.year, reference.month, reference.day)
     90     if lowered == "today":
     91         return ResolvedDate(
     92             resolution="resolved", date=reference.copy(), ambiguity="none"
     93         )
     94     if lowered == "tomorrow":
     95         return ResolvedDate(
     96             resolution="resolved",
     97             date=_civil_from_days(base + 1),
     98             ambiguity="none",
     99         )
    100     return resolve_weekday(reference, lowered, "next")
    101 
    102 
    103 from std.collections import List
    104 
    105 
    106 @fieldwise_init
    107 struct LocalTimeResolution(Copyable, Movable):
    108     var resolution: String
    109     var ambiguity: String
    110     var offset_minutes: Int
    111 
    112 
    113 def resolve_local_time(
    114     candidate_offsets: List[Int],
    115 ) raises -> LocalTimeResolution:
    116     if len(candidate_offsets) == 0:
    117         return LocalTimeResolution(
    118             resolution="unresolved", ambiguity="missing_zone", offset_minutes=0
    119         )
    120     if len(candidate_offsets) > 1:
    121         return LocalTimeResolution(
    122             resolution="unresolved",
    123             ambiguity="ambiguous_local",
    124             offset_minutes=0,
    125         )
    126     return LocalTimeResolution(
    127         resolution="resolved",
    128         ambiguity="none",
    129         offset_minutes=candidate_offsets[0],
    130     )
    131 
    132 
    133 def window_contains(
    134     start_day: Int, end_day: Int, boundary: String, point_day: Int
    135 ) raises -> Bool:
    136     if boundary == "inclusive":
    137         return point_day >= start_day and point_day <= end_day
    138     if boundary == "exclusive":
    139         return point_day >= start_day and point_day < end_day
    140     raise Error("window boundary must be 'inclusive' or 'exclusive'")