reckon.ha (14793B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use time; 5 use time::chrono; 6 7 // Specifies the behaviour of [[reckon]] when doing chronological arithmetic. 8 // 9 // The FLOOR, CEIL, HOP, and FOLD specifies how to resolve sub-significant 10 // overflows -- when a field's change in value causes any sub-significant 11 // field's range to shrink below its current value and become invalid. For 12 // example, adding 1 month to January 31st results in February 31st, a date with 13 // an unresolved day field, since February permits only 28 or 29 days. 14 export type rflag = enum uint { 15 // The default behaviour. Equivalent to CEIL. 16 DEFAULT = 0, 17 18 // Apply units in reverse order, from least to most significant. 19 REVSIG = 1 << 0, 20 21 // When a sub-significant overflow occurs, the unresolved field is set 22 // to its minimum valid value. 23 // 24 // Feb 31 -> Feb 01 25 // Aug 64 -> Aug 01 26 FLOOR = 1 << 1, 27 28 // When a sub-significant overflow occurs, the unresolved field is set 29 // to its maximum valid value. 30 // 31 // Feb 31 -> Feb 28 / Feb 29 (leap year dependent) 32 // Aug 64 -> Aug 31 33 CEIL = 1 << 2, 34 35 // When a sub-significant overflow occurs, the unresolved field is set 36 // to its new minimum valid value after the next super-significant field 37 // increments by one. 38 // 39 // Feb 31 -> Mar 01 40 // Aug 64 -> Sep 01 41 HOP = 1 << 3, 42 43 // When a sub-significant overflow occurs, the unresolved field's 44 // maximum valid value is subtracted from it's current value, and the 45 // next super-significant field increments by one. This process repeats 46 // until the unresolved field's value becomes valid (falls in range). 47 // 48 // Feb 31 -> Mar 03 / Mar 02 (leap year dependent) 49 // Aug 64 -> Sep 33 -> Oct 03 50 FOLD = 1 << 4, 51 }; 52 53 // Reckons from a given [[date]] to a new one, via a given set of [[period]]s. 54 // This is a chronological arithmetic operation. Each period is reckoned 55 // independently in succession, applying (adding) their units from most to least 56 // significant. 57 // 58 // The [[rflag]] parameter handles field overflows and other behaviours. 59 // The [[zflag]] parameter affects the final result. Example: 60 // 61 // // 2000-02-29 00:00:00.000000000 -1100 -11 Pacific/Apia 62 // let a = date::new(chrono::tz("Pacific/Apia")!, -11 * time::HOUR, 63 // 2000, 2, 29)!; 64 // 65 // let b = date::reckon(a, // starts as: 2000-Feb-29 00:00 -1100 66 // date::zflag::GAP_END, 67 // date::rflag::DEFAULT, 68 // date::period { 69 // years = 11, // becomes: 2011-Feb-28 00:00 -1100 70 // months = 10, // becomes: 2011-Dec-28 00:00 -1100 71 // days = 1, // becomes: 2011-Dec-29 00:00 -1100 72 // hours = 36, // becomes: 2011-Dec-30 12:00 -1100 73 // }, 74 // // In Samoa, Apia, the day 2011-Dec-30 was skipped entirely. 75 // // Thus, after applying date::zflag::GAP_END for adjustment, 76 // // we arrive at the final date, time, and zone-offset: 77 // // 2011-12-31 00:00:00.000000000 +1400 +14 Pacific/Apia 78 // ); 79 // 80 // See [[add]]. 81 export fn reckon( 82 d: date, 83 zoff: (time::duration | zflag), 84 rf: rflag, 85 ps: period... 86 ) (date | invalid | zfunresolved) = { 87 let r = newvirtual(); // our reckoner 88 r.vloc = d.loc; 89 r.zoff = zoff; 90 r.year = _year(&d); 91 r.month = _month(&d); 92 r.day = _day(&d); 93 r.hour = _hour(&d); 94 r.minute = _minute(&d); 95 r.second = _second(&d); 96 r.nanosecond = _nanosecond(&d); 97 98 if (rf == rflag::DEFAULT) { 99 rf |= rflag::CEIL; 100 }; 101 102 for (let p .. ps) if (rf & rflag::REVSIG == 0) { 103 const fold = rflag::FOLD; 104 105 r.year = r.year as int + p.years: int; 106 reckon_days(&r, 0, rf); // bubble up potential Feb 29 overflow 107 108 reckon_months(&r, p.months); 109 reckon_days(&r, 0, rf); // bubble up potential overflows 110 111 reckon_days(&r, p.weeks * 7, fold); 112 reckon_days(&r, p.days, fold); 113 114 // TODO: These functions aren't aware of top-down overflows. 115 // Handle overflows (e.g. [[zone]] changes). 116 reckon_hours(&r, p.hours, fold); 117 reckon_minutes(&r, p.minutes, fold); 118 reckon_seconds(&r, p.seconds, fold); 119 reckon_nanoseconds(&r, p.nanoseconds, fold); 120 } else { 121 const fold = rflag::FOLD | rflag::REVSIG; 122 123 reckon_nanoseconds(&r, p.nanoseconds, fold); 124 reckon_seconds(&r, p.seconds, fold); 125 reckon_minutes(&r, p.minutes, fold); 126 reckon_hours(&r, p.hours, fold); 127 reckon_days(&r, p.days, fold); 128 reckon_days(&r, p.weeks * 7, fold); 129 130 reckon_months(&r, p.months); 131 reckon_days(&r, 0, rf); // bubble up potential overflows 132 133 r.year = r.year as int + p.years: int; 134 reckon_days(&r, 0, rf); // bubble up potential Feb 29 overflow 135 }; 136 137 return realize(r) as (date | invalid | zfunresolved); 138 }; 139 140 fn reckon_months(r: *virtual, months: i64) void = { 141 let year = r.year as int; 142 let month = r.month as int; 143 144 month += months: int; 145 146 // month overflow 147 for (month > 12) { 148 month -= 12; 149 year += 1; 150 }; 151 for (month < 1) { 152 month += 12; 153 year -= 1; 154 }; 155 156 r.year = year; 157 r.month = month; 158 }; 159 160 fn reckon_days(r: *virtual, days: i64, rf: rflag) void = { 161 let year = r.year as int; 162 let month = r.month as int; 163 let day = r.day as int; 164 165 day += days: int; 166 167 // day overflow 168 let monthdays = calc_days_in_month(year, month); 169 for (day > monthdays) { 170 if (rf & rflag::FLOOR != 0) { 171 day = 1; 172 } else if (rf & rflag::CEIL != 0) { 173 day = monthdays; 174 } else if (rf & rflag::HOP != 0) { 175 r.year = year; 176 r.month = month; 177 178 reckon_months(r, 1); 179 180 year = r.year as int; 181 month = r.month as int; 182 day = 1; 183 } else if (rf & rflag::FOLD != 0) { 184 r.year = year; 185 r.month = month; 186 187 reckon_months(r, 1); 188 189 year = r.year as int; 190 month = r.month as int; 191 day -= monthdays; 192 }; 193 monthdays = calc_days_in_month(year, month); 194 }; 195 for (day < 1) { 196 r.year = year; 197 r.month = month; 198 199 reckon_months(r, -1); 200 201 year = r.year as int; 202 month = r.month as int; 203 day += calc_days_in_month(year, month); 204 }; 205 206 r.year = year; 207 r.month = month; 208 r.day = day; 209 }; 210 211 fn reckon_hours(r: *virtual, hours: i64, rf: rflag) void = { 212 let hour = r.hour as int; 213 214 hour += hours: int; 215 216 // hour overflow 217 for (hour >= 24) { 218 reckon_days(r, 1, rf); 219 hour -= 24; 220 }; 221 for (hour < 0) { 222 reckon_days(r, -1, rf); 223 hour += 24; 224 }; 225 226 r.hour = hour; 227 }; 228 229 fn reckon_minutes(r: *virtual, mins: i64, rf: rflag) void = { 230 let min = r.minute as int; 231 232 min += mins: int; 233 234 // minute overflow 235 for (min >= 60) { 236 reckon_hours(r, 1, rf); 237 min -= 60; 238 }; 239 for (min < 0) { 240 reckon_hours(r, -1, rf); 241 min += 60; 242 }; 243 244 r.minute = min; 245 }; 246 247 fn reckon_seconds(r: *virtual, secs: i64, rf: rflag) void = { 248 let s = r.second as int; 249 250 s += secs: int; 251 252 // second overflow 253 for (s >= 60) { 254 reckon_minutes(r, 1, rf); 255 s -= 60; 256 }; 257 for (s < 0) { 258 reckon_minutes(r, -1, rf); 259 s += 60; 260 }; 261 262 r.second = s; 263 }; 264 265 fn reckon_nanoseconds(r: *virtual, nsecs: i64, rf: rflag) void = { 266 let ns = r.nanosecond as int; 267 268 ns += nsecs: int; 269 270 // nanosecond overflow 271 for (ns >= 1000000000) { // 1E9 nanoseconds (1 second) 272 reckon_seconds(r, 1, rf); 273 ns -= 1000000000; 274 }; 275 for (ns < 0) { 276 reckon_seconds(r, -1, rf); 277 ns += 1000000000; 278 }; 279 280 r.nanosecond = ns; 281 }; 282 283 @test fn reckon() void = { 284 // no-op period, rflag::CEIL 285 286 let p = period { ... }; 287 288 let a = new(chrono::UTC, 0)!; 289 let r = reckon(a, zflag::CONTIG, 0, p)!; 290 assert(chrono::simultaneous(&a, &r)!, "01. incorrect result"); 291 292 let a = new(chrono::UTC, 0, 2019, 12, 27, 21, 7, 8, 0)!; 293 let r = reckon(a, zflag::CONTIG, 0, p)!; 294 assert(chrono::simultaneous(&a, &r)!, "02. incorrect result"); 295 296 let a = new(chrono::UTC, 0, 1970, 1, 1, 0, 0, 0, 0)!; 297 let r = reckon(a, zflag::CONTIG, 0, p)!; 298 assert(chrono::simultaneous(&a, &r)!, "03. incorrect result"); 299 300 // generic periods, rflag::CEIL 301 302 let a = new(chrono::UTC, 0, 2019, 12, 27, 21, 7, 8, 0)!; 303 304 let r = reckon(a, zflag::CONTIG, 0, period { 305 years = 1, 306 months = 1, 307 days = 1, 308 hours = 1, 309 minutes = 1, 310 seconds = 1, 311 nanoseconds = 1, 312 ... 313 })!; 314 let b = new(chrono::UTC, 0, 2021, 1, 28, 22, 8, 9, 1)!; 315 assert(chrono::simultaneous(&b, &r)!, "04. incorrect result"); 316 317 let r = reckon(a, zflag::CONTIG, 0, period { 318 years = -1, 319 months = -1, 320 days = -1, 321 hours = -1, 322 minutes = -1, 323 seconds = -1, 324 nanoseconds = -1, 325 ... 326 })!; 327 let b = new(chrono::UTC, 0, 2018, 11, 26, 20, 6, 6, 999999999)!; 328 assert(chrono::simultaneous(&b, &r)!, "05. incorrect result"); 329 330 let r = reckon(a, zflag::CONTIG, 0, period { 331 years = 100, 332 months = 100, 333 days = 100, 334 hours = 100, 335 minutes = 100, 336 seconds = 100, 337 nanoseconds = 100, 338 ... 339 })!; 340 let b = new(chrono::UTC, 0, 2128, 8, 10, 2, 48, 48, 100)!; 341 assert(chrono::simultaneous(&b, &r)!, "06. incorrect result"); 342 343 let r = reckon(a, zflag::CONTIG, 0, period { 344 years = -100, 345 months = -100, 346 days = -100, 347 hours = -100, 348 minutes = -100, 349 seconds = -100, 350 nanoseconds = -100, 351 ... 352 })!; 353 let b = new(chrono::UTC, 0, 1911, 5, 15, 15, 25, 27, 999999900)!; 354 assert(chrono::simultaneous(&b, &r)!, "07. incorrect result"); 355 356 let r = reckon(a, zflag::CONTIG, 0, period { 357 weeks = 100, 358 ... 359 })!; 360 let b = new(chrono::UTC, 0, 2021, 11, 26, 21, 7, 8, 0)!; 361 assert(chrono::simultaneous(&b, &r)!, "08. incorrect result"); 362 363 // rflag, February 29 overflows 364 365 let a = new(chrono::UTC, 0, 2000, 1, 31)!; // leap year 366 let p = period { months = 1, ... }; 367 368 let r = reckon(a, zflag::CONTIG, rflag::FLOOR, p)!; 369 let b = new(chrono::UTC, 0, 2000, 2, 1)!; 370 assert(chrono::simultaneous(&b, &r)!, "09. incorrect result"); 371 372 let r = reckon(a, zflag::CONTIG, rflag::CEIL, p)!; 373 let b = new(chrono::UTC, 0, 2000, 2, 29)!; 374 assert(chrono::simultaneous(&b, &r)!, "10. incorrect result"); 375 376 let r = reckon(a, zflag::CONTIG, rflag::HOP, p)!; 377 let b = new(chrono::UTC, 0, 2000, 3, 1)!; 378 assert(chrono::simultaneous(&b, &r)!, "11. incorrect result"); 379 380 let r = reckon(a, zflag::CONTIG, rflag::FOLD, p)!; 381 let b = new(chrono::UTC, 0, 2000, 3, 2)!; 382 assert(chrono::simultaneous(&b, &r)!, "12. incorrect result"); 383 384 // rflag, February 28 overflows 385 386 let a = new(chrono::UTC, 0, 2000, 1, 31)!; // leap year 387 let p = period { years = 1, months = 1, ... }; 388 389 let r = reckon(a, zflag::CONTIG, rflag::FLOOR, p)!; 390 let b = new(chrono::UTC, 0, 2001, 2, 1)!; 391 assert(chrono::simultaneous(&b, &r)!, "13. incorrect result"); 392 393 let r = reckon(a, zflag::CONTIG, rflag::CEIL, p)!; 394 let b = new(chrono::UTC, 0, 2001, 2, 28)!; 395 assert(chrono::simultaneous(&b, &r)!, "14. incorrect result"); 396 397 let r = reckon(a, zflag::CONTIG, rflag::HOP, p)!; 398 let b = new(chrono::UTC, 0, 2001, 3, 1)!; 399 assert(chrono::simultaneous(&b, &r)!, "15. incorrect result"); 400 401 let r = reckon(a, zflag::CONTIG, rflag::FOLD, p)!; 402 let b = new(chrono::UTC, 0, 2001, 3, 3)!; 403 assert(chrono::simultaneous(&b, &r)!, "16. incorrect result"); 404 405 // multiple periods 406 407 let a = new(chrono::UTC, 0, 2000, 12, 31)!; 408 let ps = [ 409 period { years = 1, months = 1, days = 1, ... }, 410 period { years = -1, months = -1, days = -1, ... }, 411 period { years = -1, months = -1, days = -1, ... }, 412 period { years = 1, months = 1, days = 1, ... }, 413 period { hours = 1, minutes = 1, seconds = 1, ... }, 414 period { hours = -1, minutes = -1, seconds = -1, ... }, 415 period { hours = -1, minutes = -1, seconds = -1, ... }, 416 period { hours = 1, minutes = 1, seconds = 1, ... }, 417 ]; 418 419 let r = reckon(a, zflag::CONTIG, 0, ps[..1]...)!; 420 let b = new(chrono::UTC, 0, 2002, 2, 1)!; 421 assert(chrono::simultaneous(&b, &r)!, "17. incorrect result"); 422 423 let r = reckon(a, zflag::CONTIG, 0, ps[..2]...)!; 424 let b = new(chrono::UTC, 0, 2000, 12, 31)!; 425 assert(chrono::simultaneous(&b, &r)!, "18. incorrect result"); 426 427 let r = reckon(a, zflag::CONTIG, 0, ps[..3]...)!; 428 let b = new(chrono::UTC, 0, 1999, 11, 29)!; 429 assert(chrono::simultaneous(&b, &r)!, "19. incorrect result"); 430 431 let r = reckon(a, zflag::CONTIG, 0, ps[..4]...)!; 432 let b = new(chrono::UTC, 0, 2000, 12, 30)!; 433 assert(chrono::simultaneous(&b, &r)!, "20. incorrect result"); 434 435 let r = reckon(a, zflag::CONTIG, 0, ps[..5]...)!; 436 let b = new(chrono::UTC, 0, 2000, 12, 30, 1, 1, 1)!; 437 assert(chrono::simultaneous(&b, &r)!, "21. incorrect result"); 438 439 let r = reckon(a, zflag::CONTIG, 0, ps[..6]...)!; 440 let b = new(chrono::UTC, 0, 2000, 12, 30)!; 441 assert(chrono::simultaneous(&b, &r)!, "22. incorrect result"); 442 443 let r = reckon(a, zflag::CONTIG, 0, ps[..7]...)!; 444 let b = new(chrono::UTC, 0, 2000, 12, 29, 22, 58, 59)!; 445 assert(chrono::simultaneous(&b, &r)!, "23. incorrect result"); 446 447 let r = reckon(a, zflag::CONTIG, 0, ps[..8]...)!; 448 let b = new(chrono::UTC, 0, 2000, 12, 30)!; 449 assert(chrono::simultaneous(&b, &r)!, "24. incorrect result"); 450 451 // multiple periods, rflag::REVSIG 452 453 let a = new(chrono::UTC, 0, 2000, 12, 31)!; 454 let ps = [ 455 period { years = 1, months = 1, days = 1, ... }, 456 period { years = -1, months = -1, days = -1, ... }, 457 period { years = -1, months = -1, days = -1, ... }, 458 period { years = 1, months = 1, days = 1, ... }, 459 period { hours = 1, minutes = 1, seconds = 1, ... }, 460 period { hours = -1, minutes = -1, seconds = -1, ... }, 461 period { hours = -1, minutes = -1, seconds = -1, ... }, 462 period { hours = 1, minutes = 1, seconds = 1, ... }, 463 ]; 464 465 let r = reckon(a, zflag::CONTIG, rflag::REVSIG, ps[..1]...)!; 466 let b = new(chrono::UTC, 0, 2002, 2, 1)!; 467 assert(chrono::simultaneous(&b, &r)!, "25. incorrect result"); 468 469 let r = reckon(a, zflag::CONTIG, rflag::REVSIG, ps[..2]...)!; 470 let b = new(chrono::UTC, 0, 2000, 12, 31)!; 471 assert(chrono::simultaneous(&b, &r)!, "26. incorrect result"); 472 473 let r = reckon(a, zflag::CONTIG, rflag::REVSIG, ps[..3]...)!; 474 let b = new(chrono::UTC, 0, 1999, 11, 30)!; 475 assert(chrono::simultaneous(&b, &r)!, "27. incorrect result"); 476 477 let r = reckon(a, zflag::CONTIG, rflag::REVSIG, ps[..4]...)!; 478 let b = new(chrono::UTC, 0, 2001, 1, 1)!; 479 assert(chrono::simultaneous(&b, &r)!, "28. incorrect result"); 480 481 let r = reckon(a, zflag::CONTIG, rflag::REVSIG, ps[..5]...)!; 482 let b = new(chrono::UTC, 0, 2001, 1, 1, 1, 1, 1)!; 483 assert(chrono::simultaneous(&b, &r)!, "29. incorrect result"); 484 485 let r = reckon(a, zflag::CONTIG, rflag::REVSIG, ps[..6]...)!; 486 let b = new(chrono::UTC, 0, 2001, 1, 1)!; 487 assert(chrono::simultaneous(&b, &r)!, "30. incorrect result"); 488 489 let r = reckon(a, zflag::CONTIG, rflag::REVSIG, ps[..7]...)!; 490 let b = new(chrono::UTC, 0, 2000, 12, 31, 22, 58, 59)!; 491 assert(chrono::simultaneous(&b, &r)!, "31. incorrect result"); 492 493 let r = reckon(a, zflag::CONTIG, rflag::REVSIG, ps[..8]...)!; 494 let b = new(chrono::UTC, 0, 2001, 1, 1)!; 495 assert(chrono::simultaneous(&b, &r)!, "32. incorrect result"); 496 497 return; 498 };