parithm.ha (9892B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use fmt; 5 use math; 6 use time; 7 use time::chrono; 8 9 // The nominal units of the Gregorian chronology. Used for chronological 10 // arithmetic. 11 export type unit = enum int { 12 ERA, 13 YEAR, 14 MONTH, 15 WEEK, 16 DAY, 17 HOUR, 18 MINUTE, 19 SECOND, 20 NANOSECOND, 21 }; 22 23 // Calculates the [[period]] between two [[date]]s, from A to B. 24 // The returned period, provided to [[reckon]] along with A, will produce B, 25 // regardless of the [[calculus]] used. All the period's non-zero fields will 26 // have the same sign. 27 export fn pdiff(a: date, b: date) period = { 28 let p = period { ... }; 29 30 if (chrono::compare(&a, &b) == 0) { 31 return p; 32 }; 33 34 let reverse = if (chrono::compare(&a, &b) > 0) true else false; 35 if (reverse) { 36 let tmp = a; 37 a = b; 38 b = tmp; 39 }; 40 41 p.years = _year(&b) - _year(&a); 42 43 p.months = _month(&b) - _month(&a); 44 if (p.months < 0) { 45 p.years -= 1; 46 p.months += 12; 47 }; 48 49 p.days = _day(&b) - _day(&a); 50 let year = _year(&b); 51 let month = _month(&b); 52 let monthdays = calc_days_in_month(year, month); 53 for (_day(&a) > monthdays || p.days < 0) { 54 month -= 1; 55 if (month == 0) { 56 year -= 1; 57 month = 12; 58 }; 59 monthdays = calc_days_in_month(year, month); 60 61 p.months -= 1; 62 if (p.months < 0) { 63 p.years -= 1; 64 p.months += 12; 65 }; 66 p.days += monthdays; 67 }; 68 69 p.hours = _hour(&b) - _hour(&a); 70 if (p.hours < 0) { 71 p.days -= 1; 72 p.hours += 24; 73 }; 74 75 p.minutes = _minute(&b) - _minute(&a); 76 if (p.minutes < 0) { 77 p.hours -= 1; 78 p.minutes += 60; 79 }; 80 81 p.seconds = _second(&b) - _second(&a); 82 if (p.seconds < 0) { 83 p.minutes -= 1; 84 p.seconds += 60; 85 }; 86 87 p.nanoseconds = _nanosecond(&b) - _nanosecond(&a); 88 if (p.nanoseconds < 0) { 89 p.seconds -= 1; 90 p.nanoseconds += 1000000000; // 10E9 91 }; 92 93 return if (reverse) neg(p) else p; 94 }; 95 96 // Calculates the nominal [[unit]] difference between two [[date]]s. 97 export fn unitdiff(a: date, b: date, u: unit) i64 = { 98 switch (u) { 99 case unit::ERA => 100 return era(&b) - era(&a); 101 case unit::YEAR => 102 return pdiff(a, b).years; 103 case unit::MONTH => 104 const d = pdiff(a, b); 105 return d.years * 12 + d.months; 106 case unit::WEEK => 107 return unitdiff(a, b, unit::DAY) / 7; 108 case unit::DAY => 109 return chrono::daydate(&b) - chrono::daydate(&a); 110 case unit::HOUR => 111 return unitdiff(a, b, unit::DAY) * 24 + pdiff(a, b).hours; 112 case unit::MINUTE => 113 return unitdiff(a, b, unit::HOUR) * 60 + pdiff(a, b).minutes; 114 case unit::SECOND => 115 return unitdiff(a, b, unit::MINUTE) * 60 + pdiff(a, b).seconds; 116 case unit::NANOSECOND => 117 return unitdiff(a, b, unit::SECOND) * 1000000000 + pdiff(a, b).nanoseconds; 118 }; 119 }; 120 121 // Truncates the given [[date]] at the provided nominal [[unit]]. 122 // 123 // For example, truncating to the nearest unit::MONTH will set the 'day', 124 // 'hour', 'minute', 'second', and 'nanosecond' fields to their minimum values. 125 export fn truncate(d: date, u: unit) date = { 126 // TODO: There exist timezones where midnight is invalid on certain 127 // days. The new()! calls will fail, but we probably don't want to '?' 128 // propagate [[invalid]] to keep this function's use simple. The minimum 129 // values (the zeroes and ones here) can't be hardcoded. They need 130 // calculation. We should either handle this here; or probably in 131 // realize(), and then use realize() here. 132 return switch (u) { 133 case unit::ERA => 134 yield new(d.loc, chrono::ozone(&d).zoff, 135 1, 1, 1, 136 0, 0, 0, 0, 137 )!; 138 case unit::YEAR => 139 yield new(d.loc, chrono::ozone(&d).zoff, 140 _year(&d), 1, 1, 141 0, 0, 0, 0, 142 )!; 143 case unit::MONTH => 144 yield new(d.loc, chrono::ozone(&d).zoff, 145 _year(&d), _month(&d), 1, 146 0, 0, 0, 0, 147 )!; 148 case unit::WEEK => 149 const dd = chrono::daydate(&d) - _weekday(&d); 150 const ymd = calc_ymd(dd); 151 yield new(d.loc, chrono::ozone(&d).zoff, 152 ymd.0, ymd.1, ymd.2, 153 0, 0, 0, 0, 154 )!; 155 case unit::DAY => 156 yield new(d.loc, chrono::ozone(&d).zoff, 157 _year(&d), _month(&d), _day(&d), 158 0, 0, 0, 0, 159 )!; 160 case unit::HOUR => 161 yield new(d.loc, chrono::ozone(&d).zoff, 162 _year(&d), _month(&d), _day(&d), 163 _hour(&d), 0, 0, 0, 164 )!; 165 case unit::MINUTE => 166 yield new(d.loc, chrono::ozone(&d).zoff, 167 _year(&d), _month(&d), _day(&d), 168 _hour(&d), _minute(&d), 0, 0, 169 )!; 170 case unit::SECOND => 171 yield new(d.loc, chrono::ozone(&d).zoff, 172 _year(&d), _month(&d), _day(&d), 173 _hour(&d), _minute(&d), _second(&d), 0, 174 )!; 175 case unit::NANOSECOND => 176 yield d; 177 }; 178 }; 179 180 @test fn pdiff() void = { 181 const cases = [ 182 ( 183 new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!, 184 new(chrono::UTC, 0, 2022, 2, 16, 0, 0, 0, 0)!, 185 period { 186 years = 1, 187 months = 1, 188 days = 1, 189 ... 190 }, 191 ), 192 ( 193 new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!, 194 new(chrono::UTC, 0, 2022, 3, 27, 0, 0, 0, 0)!, 195 period { 196 years = 1, 197 months = 2, 198 days = 12, 199 ... 200 }, 201 ), 202 ( 203 new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!, 204 new(chrono::UTC, 0, 2022, 3, 14, 0, 0, 0, 0)!, 205 period { 206 years = 1, 207 months = 1, 208 days = 27, 209 ... 210 }, 211 ), 212 ( 213 new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!, 214 new(chrono::UTC, 0, 2021, 1, 16, 0, 0, 0, 0)!, 215 period { 216 days = 1, 217 ... 218 }, 219 ), 220 ( 221 new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!, 222 new(chrono::UTC, 0, 2021, 1, 16, 1, 3, 2, 4)!, 223 period { 224 days = 1, 225 hours = 1, 226 minutes = 3, 227 seconds = 2, 228 nanoseconds = 4, 229 ... 230 }, 231 ), 232 ( 233 new(chrono::UTC, 0, 2021, 1, 15, 2, 3, 2, 2)!, 234 new(chrono::UTC, 0, 2021, 1, 16, 1, 1, 2, 4)!, 235 period { 236 hours = 22, 237 minutes = 58, 238 nanoseconds = 2, 239 ... 240 }, 241 ), 242 ( 243 new(chrono::UTC, 0, 500, 1, 1, 0, 0, 0, 0)!, 244 new(chrono::UTC, 0, 3500, 1, 1, 0, 6, 0, 0)!, 245 period { 246 years = 3000, 247 minutes = 6, 248 ... 249 }, 250 ), 251 ( 252 new(chrono::UTC, 0, -500, 1, 1, 0, 0, 0, 0)!, 253 new(chrono::UTC, 0, 2500, 1, 1, 0, 6, 0, 0)!, 254 period { 255 years = 3000, 256 minutes = 6, 257 ... 258 }, 259 ), 260 ( 261 new(chrono::UTC, 0, 2000, 1, 1, 0, 0, 0, 0)!, 262 new(chrono::UTC, 0, 2000, 1, 1, 0, 6, 0, 999999999)!, 263 period { 264 minutes = 6, 265 nanoseconds = 999999999, 266 ... 267 }, 268 ), 269 ( 270 new(chrono::UTC, 0, 2000, 1, 1, 0, 6, 0, 999999999)!, 271 new(chrono::UTC, 0, 2000, 1, 1, 0, 6, 1, 0)!, 272 period { 273 nanoseconds = 1, 274 ... 275 }, 276 ), 277 ( 278 new(chrono::UTC, 0, -4000, 1, 1, 0, 6, 0, 999999999)!, 279 new(chrono::UTC, 0, 4000, 1, 1, 0, 6, 1, 0)!, 280 period { 281 years = 8000, 282 nanoseconds = 1, 283 ... 284 }, 285 ), 286 ]; 287 for (let i = 0z; i < len(cases); i += 1) { 288 const da = cases[i].0; 289 const db = cases[i].1; 290 const expected = cases[i].2; 291 const actual = pdiff(da, db); 292 assert(peq(actual, expected), "pdiff miscalculation"); 293 }; 294 }; 295 296 @test fn unitdiff() void = { 297 const cases = [ 298 ( 299 new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 2)!, 300 new(chrono::UTC, 0, 2022, 1, 5, 13, 53, 30, 20)!, 301 (27, 328, 1427, 9993, 239834, 14390073, 863404409i64, 302 (863404409i64 * time::SECOND) + 18), 303 ), 304 ( 305 new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 0)!, 306 new(chrono::UTC, 0, 1994, 8, 28, 11, 20, 1, 2)!, 307 (0, 0, 0, 1, 24, 1440, 86400i64, 308 (86400i64 * time::SECOND) + 2), 309 ), 310 ( 311 new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 0)!, 312 new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 0)!, 313 (0, 0, 0, 0, 0, 0, 0i64, 0i64), 314 ), 315 ( 316 new(chrono::UTC, 0, -500, 1, 1, 0, 59, 1, 0)!, 317 new(chrono::UTC, 0, 2000, 1, 1, 23, 1, 1, 0)!, 318 (2500, 30000, 130443, 913106, 913106 * 24 + 22, 319 (913106 * 24 + 22) * 60 + 2, 320 ((913106 * 24 + 22) * 60 + 2) * 60i64, 321 (((913106 * 24 + 22) * 60 + 2) * 60i64 * 322 time::SECOND)), 323 ), 324 ]; 325 for (let i = 0z; i < len(cases); i += 1) { 326 const da = cases[i].0; 327 const db = cases[i].1; 328 const expected = cases[i].2; 329 assert(unitdiff(da, db, unit::YEAR) == expected.0, 330 "invalid diff_in_years() result"); 331 assert(unitdiff(da, db, unit::MONTH) == expected.1, 332 "invalid diff_in_months() result"); 333 assert(unitdiff(da, db, unit::WEEK) == expected.2, 334 "invalid diff_in_weeks() result"); 335 assert(unitdiff(da, db, unit::DAY) == expected.3, 336 "invalid diff_in_days() result"); 337 assert(unitdiff(da, db, unit::HOUR) == expected.4, 338 "invalid diff_in_hours() result"); 339 assert(unitdiff(da, db, unit::MINUTE) == expected.5, 340 "invalid diff_in_minutes() result"); 341 assert(unitdiff(da, db, unit::SECOND) == expected.6, 342 "invalid diff_in_seconds() result"); 343 assert(unitdiff(da, db, unit::NANOSECOND) == expected.7, 344 "invalid diff_in_nanoseconds() result"); 345 }; 346 }; 347 348 @test fn truncate() void = { 349 const d = new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 2)!; 350 351 assert(chrono::simultaneous( 352 &truncate(d, unit::ERA), 353 &new(chrono::UTC, 0, 1, 1, 1, 0, 0, 0, 0)!)!, 354 "invalid truncate() result 01"); 355 356 assert(chrono::simultaneous( 357 &truncate(d, unit::YEAR), 358 &new(chrono::UTC, 0, 1994, 1, 1, 0, 0, 0, 0)!)!, 359 "invalid truncate() result 02"); 360 361 assert(chrono::simultaneous( 362 &truncate(d, unit::MONTH), 363 &new(chrono::UTC, 0, 1994, 8, 1, 0, 0, 0, 0)!)!, 364 "invalid truncate() result 03"); 365 366 assert(chrono::simultaneous( 367 &truncate(d, unit::WEEK), 368 &new(chrono::UTC, 0, 1994, 8, 22, 0, 0, 0, 0)!)!, 369 "invalid truncate() result 04"); 370 371 assert(chrono::simultaneous( 372 &truncate(d, unit::DAY), 373 &new(chrono::UTC, 0, 1994, 8, 27, 0, 0, 0, 0)!)!, 374 "invalid truncate() result 05"); 375 376 assert(chrono::simultaneous( 377 &truncate(d, unit::HOUR), 378 &new(chrono::UTC, 0, 1994, 8, 27, 11, 0, 0, 0)!)!, 379 "invalid truncate() result 06"); 380 381 assert(chrono::simultaneous( 382 &truncate(d, unit::MINUTE), 383 &new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 0, 0)!)!, 384 "invalid truncate() result 07"); 385 386 assert(chrono::simultaneous( 387 &truncate(d, unit::SECOND), 388 &new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 0)!)!, 389 "invalid truncate() result 08"); 390 391 assert(chrono::simultaneous( 392 &truncate(d, unit::NANOSECOND), 393 &d)!, 394 "invalid truncate() result 09"); 395 };