chronology.ha (4546B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use time; 5 6 // Invalid [[moment]]. 7 export type invalid = !void; 8 9 // A moment in time within a [[locality]]. Create one with [[new]]. This type 10 // extends the [[time::instant]] type and couples it with a [[timescale]] via 11 // its [[locality]] field. 12 // 13 // This object should be treated as private and immutable. Directly mutating its 14 // fields causes undefined behavour when used with module functions. Likewise, 15 // interrogating the fields' type and value (e.g. using match statements) is 16 // also improper. 17 // 18 // Moments observe a daydate, time-of-day, and [[zone]], which are evaluated, 19 // cached and obtained with the observer functions [[daydate]], [[daytime]], and 20 // [[ozone]]. These values are derived from the embedded instant and locality 21 // information, and thus are guaranteed to be valid. 22 export type moment = struct { 23 // The embedded [[time::instant]]. 24 time::instant, 25 26 // The [[locality]] with which to interpret this moment. 27 loc: locality, 28 29 // The observed [[zone]]. 30 zone: nullable *zone, 31 32 // The observed daydate (scalar day number) 33 // since an abitrary epoch (e.g. the Unix epoch 1970-01-01). 34 daydate: (void | i64), 35 36 // The observed time-of-day (amount of daytime progressed in a day) 37 // as nanoseconds. 38 daytime: (void | i64), 39 }; 40 41 // Creates a new [[moment]]. Uses a given [[time::instant]] with a [[timescale]] 42 // associated with a given [[locality]]. 43 export fn new(loc: locality, i: time::instant) moment = { 44 return moment { 45 sec = i.sec, 46 nsec = i.nsec, 47 loc = loc, 48 zone = null, 49 daydate = void, 50 daytime = void, 51 }; 52 }; 53 54 // Observes a [[moment]]'s observed [[zone]]. 55 export fn ozone(m: *moment) zone = { 56 match (m.zone) { 57 case let z: *zone => 58 return *z; 59 case null => 60 const z = lookupzone(m.loc, *(m: *time::instant)); 61 m.zone = z; 62 return *z; 63 }; 64 }; 65 66 // Observes a [[moment]]'s observed daydate (day number since epoch). 67 // 68 // For moments with [[locality]]s based on the [[utc]], [[tai]], [[gps]], and 69 // similar timescales, their epoch date should be interpreted as the Unix epoch 70 // (1970 Janurary 1st). Other timescales may suggest their own interpretations 71 // applicable to other chronologies. 72 export fn daydate(m: *moment) i64 = { 73 match (m.daydate) { 74 case let dd: i64 => 75 return dd; 76 case void => 77 const (dd, dt) = calc_datetime( 78 m.loc, *(m: *time::instant), ozone(m).zoff, 79 ); 80 m.daytime = dt; 81 m.daydate = dd; 82 return dd; 83 }; 84 }; 85 86 // Observes a [[moment]]'s observed time-of-day (amount of daytime progressed in 87 // a day) as nanoseconds. 88 export fn daytime(m: *moment) i64 = { 89 match (m.daytime) { 90 case let dt: i64 => 91 return dt; 92 case void => 93 const (dd, dt) = calc_datetime( 94 m.loc, *(m: *time::instant), ozone(m).zoff, 95 ); 96 m.daytime = dt; 97 m.daydate = dd; 98 return dt; 99 }; 100 }; 101 102 // Calculates the observed daydate and time-of-day of a [[time::instant]] in a 103 // [[locality]] at a particular zone offset. 104 fn calc_datetime( 105 loc: locality, 106 inst: time::instant, 107 zoff: time::duration, 108 ) (i64, time::duration) = { 109 const i = time::add(inst, zoff); 110 const day = loc.daylength; 111 const daysec = day / time::SECOND; 112 const dd = if (i.sec >= 0) i.sec / daysec else (i.sec + 1) / daysec - 1; 113 const dt = ((i.sec % daysec + daysec) * time::SECOND + i.nsec) % day; 114 return (dd, dt); 115 }; 116 117 // Creates a [[moment]] from a given [[locality]], zone offset, daydate, and 118 // time-of-day. 119 export fn from_datetime( 120 loc: locality, 121 zo: time::duration, 122 dd: i64, 123 dt: i64, 124 ) moment = { 125 const inst = calc_instant(loc.daylength, zo, dd, dt); 126 return moment { 127 sec = inst.sec, 128 nsec = inst.nsec, 129 loc = loc, 130 zone = null, 131 daydate = dd, 132 daytime = dt, 133 }; 134 }; 135 136 fn calc_instant( 137 day: time::duration, // length of a day 138 zo: time::duration, // zone offset 139 dd: i64, // date since epoch 140 dt: i64, // time since start of day (ns) 141 ) time::instant = { 142 const daysec = (day / time::SECOND): i64; 143 const dayrem = day % time::SECOND; 144 let i = time::instant { 145 sec = dd * daysec, 146 nsec = 0, 147 }; 148 i = time::add(i, dd * dayrem); 149 i = time::add(i, dt); 150 i = time::add(i, -zo); 151 return i; 152 }; 153 154 // The duration of a day on Earth, in terrestrial (SI) seconds. 155 export def EARTH_DAY: time::duration = 86400 * time::SECOND; 156 157 // The duration of a solar day on Mars, in Martian seconds. 158 export def MARS_SOL_MARTIAN: time::duration = 86400 * time::SECOND; 159 160 // The duration of a solar day on Mars, in terrestrial (SI) seconds. 161 export def MARS_SOL_TERRESTRIAL: time::duration = 88775244147000 * time::NANOSECOND;