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'")