virtual.ha (5437B)
1 // License: MPL-2.0 2 // (c) 2021-2022 Byron Torres <b@torresjrjr.com> 3 use time; 4 use time::chrono; 5 6 // A [[virtual]] has insufficient information and cannot create a valid datetime. 7 export type insufficient = !void; 8 9 // A virtual datetime; a [[datetime]] wrapper interface, which represents a 10 // datetime of uncertain validity. Its fields need not be valid observed 11 // chronological values. It is meant as an intermediary container for datetime 12 // information to be resolved with the [[realize]] function. 13 // 14 // Unlike [[datetime]], a virtual's fields are meant to be treated as public and 15 // mutable. The embedded [[time::instant]] and [[time::chrono::locality]] fields 16 // (.sec .nsec .loc) are considered meaningless. Behaviour with the "observe" 17 // functions is undefined. 18 // 19 // This can be used to construct a new [[datetime]] piece-by-piece. Start with 20 // [[newvirtual]], then collect enough date/time information incrementally by 21 // direct field assignments and/or with [[parse]]. Finish with [[realize]]. 22 // 23 // let v = datetime::newvirtual(); 24 // v.vloc = time::chrono::UTC; 25 // v.zoff = 0; 26 // datetime::parse(&v, "Date: %Y-%m-%d", "Date: 2038-01-19")!; 27 // v.hour = 03; 28 // v.minute = 14; 29 // v.second = 07; 30 // v.nanosecond = 0; 31 // let dt = datetime::realize(v)!; 32 // 33 export type virtual = struct { 34 datetime, 35 // virtual's locality 36 vloc: (void | time::chrono::locality), 37 // locality name 38 locname: (void | str), 39 // zone offset 40 zoff: (void | time::duration), 41 // zone abbreviation 42 zabbr: (void | str), 43 // hour of 12 hour clock 44 halfhour: (void | int), 45 // AM/PM (false/true) 46 ampm: (void | bool), 47 }; 48 49 // Creates a new [[virtual]]. All its fields are voided or nulled appropriately. 50 export fn newvirtual() virtual = virtual { 51 sec = 0, 52 nsec = 0, 53 loc = chrono::UTC, 54 zone = null, 55 date = void, 56 time = void, 57 58 era = void, 59 year = void, 60 month = void, 61 day = void, 62 yearday = void, 63 isoweekyear = void, 64 isoweek = void, 65 week = void, 66 sundayweek = void, 67 weekday = void, 68 69 hour = void, 70 minute = void, 71 second = void, 72 nanosecond = void, 73 74 vloc = void, 75 locname = void, 76 zoff = void, 77 zabbr = void, 78 halfhour = void, 79 ampm = void, 80 }; 81 82 // Realizes a valid [[datetime]] from a [[virtual]], or fails appropriately. 83 // Four values require determination. Each has various determination strategies, 84 // each of which use a certain set of non-void fields from the given virtual. 85 // The following determination strategies will be attempted in order. 86 // 87 // Field sets for determining the date: 88 // 89 // 1. date 90 // 2. year, month, day 91 // 3. year, yearday 92 // 4. year, week, weekday 93 // 5. isoweekyear, isoweek, weekday 94 // 95 // Field sets for determining the time: 96 // 97 // 1. time 98 // 2. hour, minute, second, nanosecond 99 // 100 // Field sets for determining the zone offset: 101 // 102 // 1. zoff 103 // 104 // Field sets for determining the [[time::chrono::locality]]: 105 // 106 // 1. vloc 107 // 2. locname 108 // This is compared to each provided locality's 'name' field, 109 // or "UTC" if none are provided. The first match is used. 110 // 3. (none) 111 // Defaults to [[time::chrono::UTC]]. 112 // 113 // If for any of these values no determination strategy could be attempted, 114 // [[insufficient]] is returned. If the resultant datetime is invalid, 115 // [[invalid]] is returned. 116 export fn realize( 117 v: virtual, 118 locs: time::chrono::locality... 119 ) (datetime | insufficient | invalid) = { 120 // determine .date 121 if (v.date is i64) { 122 void; 123 } else if ( 124 v.year is int && 125 v.month is int && 126 v.day is int 127 ) { 128 v.date = calc_date__ymd( 129 v.year as int, 130 v.month as int, 131 v.day as int, 132 )?; 133 } else if ( 134 v.year is int && 135 v.yearday is int 136 ) { 137 v.date = calc_date__yd( 138 v.year as int, 139 v.yearday as int, 140 )?; 141 } else if ( 142 v.year is int && 143 v.week is int && 144 v.weekday is int 145 ) { 146 v.date = calc_date__ywd( 147 v.year as int, 148 v.week as int, 149 v.weekday as int, 150 )?; 151 } else if (false) { 152 // TODO: calendar.ha: calc_date__isoywd() 153 void; 154 } else { 155 // cannot deduce date 156 return insufficient; 157 }; 158 159 // determine .time 160 if (v.time is time::duration) { 161 void; 162 } else { 163 const hour = if (v.hour is int) { 164 yield v.hour as int; 165 } else if (v.halfhour is int && v.ampm is bool) { 166 const hr = v.halfhour as int; 167 const pm = v.ampm as bool; 168 yield if (pm) hr * 2 else hr; 169 } else { 170 return insufficient; 171 }; 172 173 if ( 174 v.minute is int && 175 v.second is int && 176 v.nanosecond is int 177 ) { 178 v.time = calc_time__hmsn( 179 hour, 180 v.minute as int, 181 v.second as int, 182 v.nanosecond as int, 183 )?; 184 } else { 185 return insufficient; 186 }; 187 }; 188 189 // determine zone offset 190 if (v.zoff is time::duration) { 191 void; 192 } else { 193 return insufficient; 194 }; 195 196 // determine .loc (defaults to time::chrono::UTC) 197 if (v.vloc is chrono::locality) { 198 v.loc = v.vloc as chrono::locality; 199 } else if (v.locname is str) { 200 v.loc = chrono::UTC; 201 for (let i = 0z; i < len(locs); i += 1) { 202 const loc = locs[i]; 203 if (loc.name == v.locname as str) { 204 v.loc = loc; 205 break; 206 }; 207 }; 208 }; 209 210 // determine .sec, .nsec 211 const dt = from_moment(chrono::from_datetime( 212 v.loc, 213 v.zoff as time::duration, 214 v.date as i64, 215 v.time as time::duration, 216 )); 217 218 // verify zone offset 219 const z = chrono::mzone(&dt); 220 if (z.zoff != v.zoff as time::duration) { 221 return invalid; 222 }; 223 224 return dt; 225 };