arithmetic.ha (23279B)
1 // License: MPL-2.0 2 // (c) 2021-2022 Byron Torres <b@torresjrjr.com> 3 // (c) 2022 Drew DeVault <sir@cmpwn.com> 4 // (c) 2021-2022 Vlad-Stefan Harbuz <vlad@vladh.net> 5 use fmt; 6 use math; 7 use time; 8 use time::chrono; 9 10 // Represents a span of time in the Gregorian chronology, using nominal units of 11 // time. Used for datetime arithmetic. 12 export type period = struct { 13 eras: int, 14 years: int, 15 16 // Can be 28, 29, 30, or 31 days long 17 months: int, 18 19 // Weeks start on Monday 20 weeks: int, 21 22 days: int, 23 hours: int, 24 minutes: int, 25 seconds: int, 26 nanoseconds: i64, 27 }; 28 29 // Specifies the behaviour of calendar arithmetic. 30 export type calculus = enum int { 31 // Units are added in the order of largest (years) to smallest 32 // (nanoseconds). If the resulting date does not exist, the first extant 33 // date previous to the initial result is returned. 34 DEFAULT, 35 }; 36 // TODO: ^ Expand this 37 38 // The nominal units of the Gregorian chronology. Used for datetime arithmetic. 39 export type unit = enum int { 40 ERA, 41 YEAR, 42 MONTH, 43 WEEK, 44 DAY, 45 HOUR, 46 MINUTE, 47 SECOND, 48 NANOSECOND, 49 }; 50 51 // Returns true if two [[datetime]]s are equivalent. 52 // 53 // Equivalence means they represent the same moment in time, regardless of their 54 // locality or observed chronological values. 55 export fn eq(a: datetime, b: datetime) bool = { 56 return a.date == b.date && a.time == b.time; 57 }; 58 59 // Returns true if [[datetime]] "a" succeeds [[datetime]] "b". 60 // 61 // Temporal order is evaluated in a universal frame of reference, regardless of 62 // their locality or observed chronological values. 63 export fn after(a: datetime, b: datetime) bool = { 64 return !eq(a, b) && 65 (a.date > b.date || a.date == b.date && a.time > b.time); 66 }; 67 68 // Returns true if [[datetime]] "a" precedes [[datetime]] "b". 69 // 70 // Temporal order is evaluated in a universal frame of reference, regardless of 71 // their locality or observed chronological values. 72 export fn before(a: datetime, b: datetime) bool = { 73 return !eq(a, b) && !after(a, b); 74 }; 75 76 // Calculates the [[period]] between two [[datetime]]s. 77 export fn diff(a: datetime, b: datetime) period = { 78 let res = period { ... }; 79 if (eq(a, b)) { 80 return res; 81 }; 82 if (after(b, a)) { 83 const tmp = a; 84 a = b; 85 b = tmp; 86 }; 87 88 res.years = year(&a) - year(&b); 89 90 res.months = month(&a) - month(&b); 91 if (res.months < 0) { 92 res.years -= 1; 93 res.months = 12 + res.months; 94 }; 95 96 res.days = day(&a) - day(&b); 97 if (res.days < 0) { 98 let prev_month_year = year(&a); 99 let prev_month = month(&a) - 1; 100 if (prev_month == 0) { 101 prev_month_year -= 1; 102 prev_month = 12; 103 }; 104 const n_days_in_prev_month = calc_n_days_in_month( 105 prev_month_year, prev_month); 106 res.months -= 1; 107 res.days = n_days_in_prev_month + res.days; 108 }; 109 110 res.hours = hour(&a) - hour(&b); 111 if (res.hours < 0) { 112 res.days -= 1; 113 res.hours = 24 + res.hours; 114 }; 115 116 res.minutes = min(&a) - min(&b); 117 if (res.minutes < 0) { 118 res.hours -= 1; 119 res.minutes = 60 + res.minutes; 120 }; 121 122 res.seconds = sec(&a) - sec(&b); 123 if (res.seconds < 0) { 124 res.minutes -= 1; 125 res.seconds = 60 + res.seconds; 126 }; 127 128 res.nanoseconds = nsec(&a) - nsec(&b); 129 if (res.nanoseconds < 0) { 130 res.seconds -= 1; 131 res.nanoseconds = time::SECOND + res.nanoseconds; 132 }; 133 134 return res; 135 }; 136 137 // Calculates the difference between two [[datetime]]s using the given nominal 138 // [[unit]], truncating towards zero. 139 export fn unitdiff(a: datetime, b: datetime, u: unit) i64 = { 140 return switch (u) { 141 case unit::ERA => 142 yield math::absi(era(&a) - era(&b)): i64; 143 case unit::YEAR => 144 yield diff(a, b).years; 145 case unit::MONTH => 146 const full_diff = diff(a, b); 147 yield full_diff.years * 12 + full_diff.months; 148 case unit::WEEK => 149 yield unitdiff(a, b, unit::DAY) / 7; 150 case unit::DAY => 151 yield math::absi(a.date - b.date): int; 152 case unit::HOUR => 153 const full_diff = diff(a, b); 154 yield (unitdiff(a, b, unit::DAY) * 24) + full_diff.hours; 155 case unit::MINUTE => 156 const full_diff = diff(a, b); 157 yield unitdiff(a, b, unit::HOUR) * 60 + full_diff.minutes; 158 case unit::SECOND => 159 const full_diff = diff(a, b); 160 yield unitdiff(a, b, unit::MINUTE) * 60 + full_diff.seconds; 161 case unit::NANOSECOND => 162 const full_diff = diff(a, b); 163 yield unitdiff(a, b, unit::SECOND) * time::SECOND + 164 full_diff.nanoseconds; 165 }; 166 }; 167 168 // Returns true if two [[period]]s are numerically equal. 169 export fn period_eq(a: period, b: period) bool = { 170 return a.eras == b.eras && 171 a.years == b.years && 172 a.months == b.months && 173 a.weeks == b.weeks && 174 a.days == b.days && 175 a.hours == b.hours && 176 a.minutes == b.minutes && 177 a.seconds == b.seconds && 178 a.nanoseconds == b.nanoseconds; 179 }; 180 181 // Truncates the given [[datetime]] at the provided nominal [[unit]]. 182 // 183 // For example, truncating to the nearest [[unit::MONTH]] will set the day, 184 // hour, minute, seconds, and nanoseconds fields to their minimum values. 185 export fn truncate(dt: datetime, u: unit) datetime = { 186 // TODO: Replace all of the 0s for the zoffset with the actual 187 // zoffset once the API is solidified a bit 188 return switch (u) { 189 case unit::ERA => 190 yield new(dt.loc, 0, 191 1, 1, 1, 192 0, 0, 0, 0, 193 )!; 194 case unit::YEAR => 195 yield new(dt.loc, 0, 196 year(&dt), 1, 1, 197 0, 0, 0, 0, 198 )!; 199 case unit::MONTH => 200 yield new(dt.loc, 0, 201 year(&dt), month(&dt), 1, 202 0, 0, 0, 0, 203 )!; 204 case unit::WEEK => 205 const date = dt.date - (weekday(&dt) - 1); 206 const ymd = calc_ymd(date); 207 yield new(dt.loc, 0, 208 ymd.0, ymd.1, ymd.2, 209 0, 0, 0, 0, 210 )!; 211 case unit::DAY => 212 yield new(dt.loc, 0, 213 year(&dt), month(&dt), day(&dt), 214 0, 0, 0, 0, 215 )!; 216 case unit::HOUR => 217 yield new(dt.loc, 0, 218 year(&dt), month(&dt), day(&dt), 219 hour(&dt), 0, 0, 0, 220 )!; 221 case unit::MINUTE => 222 yield new(dt.loc, 0, 223 year(&dt), month(&dt), day(&dt), 224 hour(&dt), min(&dt), 0, 0, 225 )!; 226 case unit::SECOND => 227 yield new(dt.loc, 0, 228 year(&dt), month(&dt), day(&dt), 229 hour(&dt), min(&dt), sec(&dt), 0, 230 )!; 231 case unit::NANOSECOND => 232 yield dt; 233 }; 234 }; 235 236 // Given a [[datetime]] and a [[period]], "hops" to the minimum value of each 237 // field (years, months, days, etc) plus or minus an offset, and returns a new 238 // datetime. This can be used, for example, to find the start of last year. 239 // 240 // Consults each period's fields from most to least significant (from years to 241 // nanoseconds). 242 // 243 // If a period's field's value N is zero, it's a no-op. Otherwise, hop will 244 // reckon to the Nth inter-period point from where last reckoned. This repeats 245 // until all the given period's fields are exhausted. 246 // 247 // let dt = ... // 1999-05-13 12:30:45 248 // datetime::hop(dt, datetime::period { 249 // years = 22, // produces 2021-01-01 00:00:00 250 // months = -1, // produces 2020-11-01 00:00:00 251 // days = -4, // produces 2020-10-27 00:00:00 252 // }); 253 // 254 export fn hop(dt: datetime, pp: period...) datetime = { 255 let new_dt = dt; 256 for (let i = 0z; i < len(pp); i += 1) { 257 const p = pp[i]; 258 259 if (p.years != 0) { 260 const dt_inc = add(new_dt, calculus::DEFAULT, 261 period { years = p.years, ... }); 262 new_dt = truncate(dt_inc, unit::YEAR); 263 }; 264 if (p.months != 0) { 265 const dt_inc = add(new_dt, calculus::DEFAULT, 266 period { months = p.months, ... }); 267 new_dt = truncate(dt_inc, unit::MONTH); 268 }; 269 if (p.weeks != 0) { 270 const dt_inc = add(new_dt, calculus::DEFAULT, 271 period { weeks = p.weeks, ... }); 272 new_dt = truncate(dt_inc, unit::WEEK); 273 }; 274 if (p.days != 0) { 275 const dt_inc = add(new_dt, calculus::DEFAULT, 276 period { days = p.days, ... }); 277 new_dt = truncate(dt_inc, unit::DAY); 278 }; 279 if (p.hours != 0) { 280 const dt_inc = add(new_dt, calculus::DEFAULT, 281 period { hours = p.hours, ... }); 282 new_dt = truncate(dt_inc, unit::HOUR); 283 }; 284 if (p.minutes != 0) { 285 const dt_inc = add(new_dt, calculus::DEFAULT, 286 period { minutes = p.minutes, ... }); 287 new_dt = truncate(dt_inc, unit::MINUTE); 288 }; 289 if (p.seconds != 0) { 290 const dt_inc = add(new_dt, calculus::DEFAULT, 291 period { seconds = p.seconds, ... }); 292 new_dt = truncate(dt_inc, unit::SECOND); 293 }; 294 if (p.nanoseconds != 0) { 295 new_dt = add(new_dt, calculus::DEFAULT, 296 period { nanoseconds = p.nanoseconds, ... }); 297 }; 298 }; 299 return new_dt; 300 }; 301 302 // Adds a period of time to a datetime, most significant units first. Conserves 303 // relative distance from cyclical points on the calendar when possible. This 304 // can be used, for example, to find the date one year from now. 305 // 306 // let dt = ... // 1999-05-13 12:30:45 307 // datetime::add(dt, datetime::calculus::DEFAULT, datetime::period { 308 // years = 22, // 2021-05-13 12:30:45 309 // months = -1, // 2021-04-13 12:30:45 310 // days = -4, // 2020-04-09 12:30:45 311 // }); 312 // 313 export fn add(dt: datetime, flag: calculus, pp: period...) datetime = { 314 // TODO: Use [[builder]] to simplify some code. 315 let d_year = year(&dt); 316 let d_month = month(&dt); 317 let d_day = day(&dt); 318 let d_hour = hour(&dt); 319 let d_min = min(&dt); 320 let d_sec = sec(&dt); 321 let d_nsec = ((nsec(&dt)): i64); 322 for (let i = 0z; i < len(pp); i += 1) { 323 const p = pp[i]; 324 325 let latest_date = dt.date; 326 327 if (p.years != 0) { 328 d_year += p.years; 329 }; 330 if (p.months != 0) { 331 d_month += p.months; 332 }; 333 if (d_month > 12) { 334 d_year += (d_month - 1) / 12; 335 d_month = d_month % 12; 336 }; 337 if (d_month < 1) { 338 d_year -= (12 + -(d_month - 1)) / 12; 339 d_month = 12 - (-d_month % 12); 340 }; 341 const n_days_in_month = calc_n_days_in_month(d_year, d_month); 342 if (d_day > n_days_in_month) { 343 d_day = n_days_in_month; 344 }; 345 346 if (p.weeks != 0) { 347 p.days += p.weeks * 7; 348 }; 349 latest_date = calc_date_from_ymd( 350 d_year, d_month, d_day)!; 351 if (p.days != 0) { 352 const new_ymd = calc_ymd(latest_date + p.days); 353 d_year = new_ymd.0; 354 d_month = new_ymd.1; 355 d_day = new_ymd.2; 356 latest_date = calc_date_from_ymd( 357 d_year, d_month, d_day)!; 358 }; 359 360 if (p.hours != 0) { 361 p.nanoseconds += p.hours * time::HOUR; 362 }; 363 if (p.minutes != 0) { 364 p.nanoseconds += p.minutes * time::MINUTE; 365 }; 366 if (p.seconds != 0) { 367 p.nanoseconds += p.seconds * time::SECOND; 368 }; 369 if (p.nanoseconds != 0) { 370 const ns_in_day = 24 * time::HOUR; 371 let overflowed_days = 0; 372 373 if (math::absi(p.nanoseconds): i64 > ns_in_day) { 374 overflowed_days += 375 ((p.nanoseconds / ns_in_day): int); 376 p.nanoseconds %= ns_in_day; 377 }; 378 379 let new_time = dt.time + p.nanoseconds; 380 381 if (new_time >= ns_in_day) { 382 overflowed_days += 1; 383 new_time -= ns_in_day; 384 } else if (new_time < 0) { 385 overflowed_days -= 1; 386 new_time += ns_in_day; 387 }; 388 389 if (overflowed_days != 0) { 390 const new_date = latest_date + 391 overflowed_days; 392 const new_ymd = calc_ymd(new_date); 393 d_year = new_ymd.0; 394 d_month = new_ymd.1; 395 d_day = new_ymd.2; 396 }; 397 const new_hmsn = calc_hmsn(new_time); 398 d_hour = new_hmsn.0; 399 d_min = new_hmsn.1; 400 d_sec = new_hmsn.2; 401 d_nsec = new_hmsn.3; 402 }; 403 }; 404 // TODO: Add zoffset back in here once API is settled 405 return new(dt.loc, 0, 406 d_year, d_month, d_day, d_hour, d_min, d_sec, d_nsec: int, 407 )!; 408 }; 409 410 // Subtracts a calendrical period of time to a datetime, most significant units 411 // first. Conserves relative distance from cyclical points on the calendar when 412 // possible. 413 // 414 // let dt = ... // 1999-05-13 12:30:45 415 // datetime::subtract(dt, datetime::calculus::DEFAULT, datetime::period { 416 // years = 22, // 1977-05-13 12:30:45 417 // months = -1, // 1977-06-13 12:30:45 418 // days = -4, // 1977-06-17 12:30:45 419 // }); 420 // 421 export fn sub(dt: datetime, flag: calculus, pp: period...) datetime = { 422 for (let i = 0z; i < len(pp); i += 1) { 423 pp[i].eras *= -1; 424 pp[i].years *= -1; 425 pp[i].months *= -1; 426 pp[i].weeks *= -1; 427 pp[i].days *= -1; 428 pp[i].minutes *= -1; 429 pp[i].seconds *= -1; 430 pp[i].nanoseconds *= -1; 431 }; 432 return add(dt, flag, pp...); 433 }; 434 435 @test fn eq() void = { 436 const dt = new(chrono::UTC, 0, 2022, 2, 4, 3, 14, 7, 0)!; 437 const cases = [ 438 ((-768, 1, 1, 3, 14, 7, 0), false), 439 (( 1, 1, 1, 14, 0, 0, 1234), false), 440 ((2022, 2, 4, 3, 14, 7, 0), true), 441 ((2022, 2, 4, 3, 14, 7, 1), false), 442 ((2038, 1, 19, 3, 14, 7, 0), false), 443 ((5555, 5, 5, 5, 55, 55, 5555), false), 444 ]; 445 for (let i = 0z; i < len(cases); i += 1) { 446 const c = cases[i].0; 447 const expected = cases[i].1; 448 const case_dt = new(chrono::UTC, 0, 449 c.0, c.1, c.2, c.3, c.4, c.5, c.6)!; 450 assert(eq(dt, case_dt) == expected, 451 "equality comparison failed"); 452 }; 453 }; 454 455 @test fn after() void = { 456 const dt = new(chrono::UTC, 0, 2022, 2, 4, 3, 14, 7, 0)!; 457 const cases = [ 458 ((-768, 1, 1, 3, 14, 7, 0), false), 459 (( 1, 1, 1, 14, 0, 0, 1234), false), 460 ((2020, 2, 4, 3, 14, 7, 1), false), 461 ((2022, 2, 4, 3, 14, 7, 0), false), 462 ((2022, 2, 4, 4, 1, 1, 0), true), 463 ((2038, 1, 19, 3, 14, 7, 0), true), 464 ((5555, 5, 5, 5, 55, 55, 5555), true), 465 ]; 466 for (let i = 0z; i < len(cases); i += 1) { 467 const c = cases[i].0; 468 const expected = cases[i].1; 469 const case_dt = new(chrono::UTC, 0, 470 c.0, c.1, c.2, c.3, c.4, c.5, c.6)!; 471 assert(after(case_dt, dt) == expected, 472 "incorrect date ordering in after()"); 473 }; 474 }; 475 476 @test fn before() void = { 477 const dt = new(chrono::UTC, 0, 2022, 2, 4, 3, 14, 7, 0)!; 478 const cases = [ 479 ((-768, 1, 1, 3, 14, 7, 0), true), 480 (( 1, 1, 1, 14, 0, 0, 1234), true), 481 ((2020, 2, 4, 3, 14, 7, 1), true), 482 ((2022, 2, 4, 3, 14, 7, 0), false), 483 ((2022, 2, 4, 4, 1, 1, 0), false), 484 ((2038, 1, 19, 3, 14, 7, 0), false), 485 ((5555, 5, 5, 5, 55, 55, 5555), false), 486 ]; 487 for (let i = 0z; i < len(cases); i += 1) { 488 const c = cases[i].0; 489 const expected = cases[i].1; 490 const case_dt = new(chrono::UTC, 0, 491 c.0, c.1, c.2, c.3, c.4, c.5, c.6)!; 492 assert(before(case_dt, dt) == expected, 493 "incorrect date ordering in before()"); 494 }; 495 }; 496 497 @test fn diff() void = { 498 const cases = [ 499 ( 500 new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!, 501 new(chrono::UTC, 0, 2022, 2, 16, 0, 0, 0, 0)!, 502 period { 503 years = 1, 504 months = 1, 505 days = 1, 506 ... 507 }, 508 ), 509 ( 510 new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!, 511 new(chrono::UTC, 0, 2022, 3, 27, 0, 0, 0, 0)!, 512 period { 513 years = 1, 514 months = 2, 515 days = 12, 516 ... 517 }, 518 ), 519 ( 520 new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!, 521 new(chrono::UTC, 0, 2022, 3, 14, 0, 0, 0, 0)!, 522 period { 523 years = 1, 524 months = 1, 525 days = 27, 526 ... 527 }, 528 ), 529 ( 530 new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!, 531 new(chrono::UTC, 0, 2021, 1, 16, 0, 0, 0, 0)!, 532 period { 533 days = 1, 534 ... 535 }, 536 ), 537 ( 538 new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!, 539 new(chrono::UTC, 0, 2021, 1, 16, 1, 3, 2, 4)!, 540 period { 541 days = 1, 542 hours = 1, 543 minutes = 3, 544 seconds = 2, 545 nanoseconds = 4, 546 ... 547 }, 548 ), 549 ( 550 new(chrono::UTC, 0, 2021, 1, 15, 2, 3, 2, 2)!, 551 new(chrono::UTC, 0, 2021, 1, 16, 1, 1, 2, 4)!, 552 period { 553 hours = 22, 554 minutes = 58, 555 nanoseconds = 2, 556 ... 557 }, 558 ), 559 ( 560 new(chrono::UTC, 0, 500, 1, 1, 0, 0, 0, 0)!, 561 new(chrono::UTC, 0, 3500, 1, 1, 0, 6, 0, 0)!, 562 period { 563 years = 3000, 564 minutes = 6, 565 ... 566 }, 567 ), 568 ( 569 new(chrono::UTC, 0, -500, 1, 1, 0, 0, 0, 0)!, 570 new(chrono::UTC, 0, 2500, 1, 1, 0, 6, 0, 0)!, 571 period { 572 years = 3000, 573 minutes = 6, 574 ... 575 }, 576 ), 577 ( 578 new(chrono::UTC, 0, 2000, 1, 1, 0, 0, 0, 0)!, 579 new(chrono::UTC, 0, 2000, 1, 1, 0, 6, 0, 999999999)!, 580 period { 581 minutes = 6, 582 nanoseconds = 999999999, 583 ... 584 }, 585 ), 586 ( 587 new(chrono::UTC, 0, 2000, 1, 1, 0, 6, 0, 999999999)!, 588 new(chrono::UTC, 0, 2000, 1, 1, 0, 6, 1, 0)!, 589 period { 590 nanoseconds = 1, 591 ... 592 }, 593 ), 594 ( 595 new(chrono::UTC, 0, -4000, 1, 1, 0, 6, 0, 999999999)!, 596 new(chrono::UTC, 0, 4000, 1, 1, 0, 6, 1, 0)!, 597 period { 598 years = 8000, 599 nanoseconds = 1, 600 ... 601 }, 602 ), 603 ]; 604 for (let i = 0z; i < len(cases); i += 1) { 605 const dta = cases[i].0; 606 const dtb = cases[i].1; 607 const expected = cases[i].2; 608 const actual = diff(dta, dtb); 609 assert(period_eq(actual, expected), "diff miscalculation"); 610 }; 611 }; 612 613 @test fn unitdiff() void = { 614 const cases = [ 615 ( 616 new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 2)!, 617 new(chrono::UTC, 0, 2022, 1, 5, 13, 53, 30, 20)!, 618 (27, 328, 1427, 9993, 239834, 14390073, 863404409i64, 619 (863404409i64 * time::SECOND) + 18), 620 ), 621 ( 622 new(chrono::UTC, 0, 1994, 8, 28, 11, 20, 1, 2)!, 623 new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 0)!, 624 (0, 0, 0, 1, 24, 1440, 86400i64, 625 (86400i64 * time::SECOND) + 2), 626 ), 627 ( 628 new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 0)!, 629 new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 0)!, 630 (0, 0, 0, 0, 0, 0, 0i64, 0i64), 631 ), 632 ( 633 new(chrono::UTC, 0, -500, 1, 1, 0, 59, 1, 0)!, 634 new(chrono::UTC, 0, 2000, 1, 1, 23, 1, 1, 0)!, 635 (2500, 30000, 130443, 913106, 913106 * 24 + 22, 636 (913106 * 24 + 22) * 60 + 2, 637 ((913106 * 24 + 22) * 60 + 2) * 60i64, 638 (((913106 * 24 + 22) * 60 + 2) * 60i64 * 639 time::SECOND)), 640 ), 641 ]; 642 for (let i = 0z; i < len(cases); i += 1) { 643 const dta = cases[i].0; 644 const dtb = cases[i].1; 645 const expected = cases[i].2; 646 assert(unitdiff(dtb, dta, unit::YEAR) == expected.0, 647 "invalid diff_in_years() result"); 648 assert(unitdiff(dtb, dta, unit::MONTH) == expected.1, 649 "invalid diff_in_months() result"); 650 assert(unitdiff(dtb, dta, unit::WEEK) == expected.2, 651 "invalid diff_in_weeks() result"); 652 assert(unitdiff(dtb, dta, unit::DAY) == expected.3, 653 "invalid diff_in_days() result"); 654 assert(unitdiff(dtb, dta, unit::HOUR) == expected.4, 655 "invalid diff_in_hours() result"); 656 assert(unitdiff(dtb, dta, unit::MINUTE) == expected.5, 657 "invalid diff_in_minutes() result"); 658 assert(unitdiff(dtb, dta, unit::SECOND) == expected.6, 659 "invalid diff_in_seconds() result"); 660 assert(unitdiff(dtb, dta, unit::NANOSECOND) == expected.7, 661 "invalid diff_in_nanoseconds() result"); 662 }; 663 }; 664 665 @test fn truncate() void = { 666 const dt = new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 2)!; 667 assert(eq(truncate(dt, unit::ERA), 668 new(chrono::UTC, 0, 1, 1, 1, 0, 0, 0, 0)!), 669 "invalid truncate() result"); 670 assert(eq(truncate(dt, unit::YEAR), 671 new(chrono::UTC, 0, 1994, 1, 1, 0, 0, 0, 0)!), 672 "invalid truncate() result"); 673 assert(eq(truncate(dt, unit::MONTH), 674 new(chrono::UTC, 0, 1994, 8, 1, 0, 0, 0, 0)!), 675 "invalid truncate() result"); 676 assert(eq(truncate(dt, unit::WEEK), 677 new(chrono::UTC, 0, 1994, 8, 22, 0, 0, 0, 0)!), 678 "invalid truncate() result"); 679 assert(eq(truncate(dt, unit::DAY), 680 new(chrono::UTC, 0, 1994, 8, 27, 0, 0, 0, 0)!), 681 "invalid truncate() result"); 682 assert(eq(truncate(dt, unit::HOUR), 683 new(chrono::UTC, 0, 1994, 8, 27, 11, 0, 0, 0)!), 684 "invalid truncate() result"); 685 assert(eq(truncate(dt, unit::MINUTE), 686 new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 0, 0)!), 687 "invalid truncate() result"); 688 assert(eq(truncate(dt, unit::SECOND), 689 new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 0)!), 690 "invalid truncate() result"); 691 assert(eq(truncate(dt, unit::NANOSECOND), dt), 692 "invalid truncate() result"); 693 }; 694 695 @test fn add() void = { 696 const d = new(chrono::UTC, 0, 2022, 2, 4, 3, 14, 7, 0)!; 697 const cases = [ 698 ( 699 period { years = 1, ... }, 700 new(chrono::UTC, 0, 2023, 2, 4, 3, 14, 7, 0)!, 701 ), 702 ( 703 period { years = -23, ... }, 704 new(chrono::UTC, 0, 1999, 2, 4, 3, 14, 7, 0)!, 705 ), 706 ( 707 period { months = 2, ... }, 708 new(chrono::UTC, 0, 2022, 4, 4, 3, 14, 7, 0)!, 709 ), 710 ( 711 period { months = 11, ... }, 712 new(chrono::UTC, 0, 2023, 1, 4, 3, 14, 7, 0)!, 713 ), 714 ( 715 period { months = -1, ... }, 716 new(chrono::UTC, 0, 2022, 1, 4, 3, 14, 7, 0)!, 717 ), 718 ( 719 period { months = -2, ... }, 720 new(chrono::UTC, 0, 2021, 12, 4, 3, 14, 7, 0)!, 721 ), 722 ( 723 period { days = 3, ... }, 724 new(chrono::UTC, 0, 2022, 2, 7, 3, 14, 7, 0)!, 725 ), 726 ( 727 period { days = 33, ... }, 728 new(chrono::UTC, 0, 2022, 3, 9, 3, 14, 7, 0)!, 729 ), 730 ( 731 period { days = 333, ... }, 732 new(chrono::UTC, 0, 2023, 1, 3, 3, 14, 7, 0)!, 733 ), 734 ( 735 period { days = -2, ... }, 736 new(chrono::UTC, 0, 2022, 2, 2, 3, 14, 7, 0)!, 737 ), 738 ( 739 period { days = -4, ... }, 740 new(chrono::UTC, 0, 2022, 1, 31, 3, 14, 7, 0)!, 741 ), 742 ( 743 period { days = -1337, ... }, 744 new(chrono::UTC, 0, 2018, 6, 8, 3, 14, 7, 0)!, 745 ), 746 ( 747 period { hours = 1, ... }, 748 new(chrono::UTC, 0, 2022, 2, 4, 4, 14, 7, 0)!, 749 ), 750 ( 751 period { hours = 24, ... }, 752 new(chrono::UTC, 0, 2022, 2, 5, 3, 14, 7, 0)!, 753 ), 754 ( 755 period { hours = 25, ... }, 756 new(chrono::UTC, 0, 2022, 2, 5, 4, 14, 7, 0)!, 757 ), 758 ( 759 period { hours = 123456, ... }, 760 new(chrono::UTC, 0, 2036, 3, 6, 3, 14, 7, 0)!, 761 ), 762 ( 763 period { hours = -2, ... }, 764 new(chrono::UTC, 0, 2022, 2, 4, 1, 14, 7, 0)!, 765 ), 766 ( 767 period { hours = -24, ... }, 768 new(chrono::UTC, 0, 2022, 2, 3, 3, 14, 7, 0)!, 769 ), 770 ( 771 period { hours = -123456, ... }, 772 new(chrono::UTC, 0, 2008, 1, 5, 3, 14, 7, 0)!, 773 ), 774 ( 775 period { seconds = 2, ... }, 776 new(chrono::UTC, 0, 2022, 2, 4, 3, 14, 9, 0)!, 777 ), 778 ( 779 period { seconds = 666666666, ... }, 780 new(chrono::UTC, 0, 2043, 3, 22, 4, 25, 13, 0)!, 781 ), 782 ( 783 period { seconds = -2, ... }, 784 new(chrono::UTC, 0, 2022, 2, 4, 3, 14, 5, 0)!, 785 ), 786 ( 787 period { seconds = -666666666, ... }, 788 new(chrono::UTC, 0, 2000, 12, 20, 2, 3, 1, 0)!, 789 ), 790 ( 791 period { nanoseconds = 123, ... }, 792 new(chrono::UTC, 0, 2022, 2, 4, 3, 14, 7, 123)!, 793 ), 794 ( 795 period { nanoseconds = 1361661361461, ... }, 796 new(chrono::UTC, 0, 2022, 2, 4, 3, 36, 48, 661361461)!, 797 ), 798 ( 799 period { nanoseconds = -1361661361461, ... }, 800 new(chrono::UTC, 0, 2022, 2, 4, 2, 51, 25, 338638539)!, 801 ), 802 ( 803 period { months = 1, seconds = -666666666, ... }, 804 new(chrono::UTC, 0, 2001, 1, 17, 2, 3, 1, 0)!, 805 ), 806 ( 807 period { months = 1, seconds = -666666666, ... }, 808 new(chrono::UTC, 0, 2001, 1, 17, 2, 3, 1, 0)!, 809 ), 810 ( 811 period { 812 years = -1, 813 months = -2, 814 weeks = -3, 815 days = -4, 816 hours = -5, 817 minutes = -6, 818 seconds = -7, 819 nanoseconds = -8, 820 ... 821 }, 822 new(chrono::UTC, 0, 2020, 11, 8, 22, 7, 59, 999999992)!, 823 ), 824 ( 825 period { 826 years = 1, 827 months = 2, 828 weeks = 3, 829 days = 4, 830 hours = 5, 831 minutes = 6, 832 seconds = 7, 833 nanoseconds = 8, 834 ... 835 }, 836 new(chrono::UTC, 0, 2023, 4, 29, 8, 20, 14, 8)!, 837 ), 838 ( 839 period { 840 years = 1, 841 months = -2, 842 weeks = 3, 843 days = -5, 844 hours = 8, 845 minutes = -13, 846 seconds = 21, 847 nanoseconds = -34, 848 ... 849 }, 850 new(chrono::UTC, 0, 2022, 12, 20, 11, 1, 27, 999999966)!, 851 ), 852 ( 853 period { 854 years = -1, 855 months = 12, 856 weeks = -52, 857 days = -31, 858 hours = 24, 859 minutes = -3600, 860 seconds = 3600, 861 nanoseconds = -86400000000000, 862 ... 863 }, 864 new(chrono::UTC, 0, 2021, 1, 2, 16, 14, 7, 0)!, 865 ), 866 ]; 867 for (let i = 0z; i < len(cases); i += 1) { 868 const p = cases[i].0; 869 const expected = cases[i].1; 870 const actual = add(d, calculus::DEFAULT, p); 871 assert(eq(actual, expected), "addition miscalculation"); 872 }; 873 }; 874 875 @test fn sub() void = { 876 const d = new(chrono::UTC, 0, 2022, 2, 4, 3, 14, 7, 0)!; 877 const cases = [ 878 ( 879 period { years = 1, ... }, 880 new(chrono::UTC, 0, 2021, 2, 4, 3, 14, 7, 0)!, 881 ), 882 ( 883 period { months = 2, ... }, 884 new(chrono::UTC, 0, 2021, 12, 4, 3, 14, 7, 0)!, 885 ), 886 ( 887 period { months = 14, ... }, 888 new(chrono::UTC, 0, 2020, 12, 4, 3, 14, 7, 0)!, 889 ), 890 ]; 891 for (let i = 0z; i < len(cases); i += 1) { 892 const p = cases[i].0; 893 const expected = cases[i].1; 894 const actual = sub(d, calculus::DEFAULT, p); 895 assert(eq(actual, expected), "subtraction miscalculation"); 896 }; 897 };