parithm.ha (9688B)
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 // The [[zflag]] parameter affects the final result. Example: 123 // 124 // // On this day in Sao Paulo, a +1 hour jump occurs at 00:00. 125 // // The time range 00:00..00:59 is never observed. 126 // // 127 // // 2000-10-08 12:00:00.000000000 -0200 -02 America/Sao_Paulo 128 // let a = date::new(chrono::tz("America/Sao_Paulo")!, -2 * time::HOUR, 129 // 2000, 10, 8, 12)! 130 // // 131 // // 2000-10-08 01:00:00.000000000 -0200 -02 America/Sao_Paulo 132 // let b = date::truncate(a, date::zflag::GAP_END, date::unit::DAY)!; 133 // 134 export fn truncate(d: date, zf: zflag, u: unit) (date | invalid | zfunresolved) = { 135 switch (u) { 136 case unit::ERA => 137 return new(d.loc, zf, 138 1, 1, 1, 139 0, 0, 0, 0, 140 ); 141 case unit::YEAR => 142 return new(d.loc, zf, 143 _year(&d), 1, 1, 144 0, 0, 0, 0, 145 ); 146 case unit::MONTH => 147 return new(d.loc, zf, 148 _year(&d), _month(&d), 1, 149 0, 0, 0, 0, 150 ); 151 case unit::WEEK => 152 const dd = chrono::daydate(&d) - _weekday(&d); 153 const ymd = calc_ymd(dd); 154 return new(d.loc, zf, 155 ymd.0, ymd.1, ymd.2, 156 0, 0, 0, 0, 157 ); 158 case unit::DAY => 159 return new(d.loc, zf, 160 _year(&d), _month(&d), _day(&d), 161 0, 0, 0, 0, 162 ); 163 case unit::HOUR => 164 return new(d.loc, zf, 165 _year(&d), _month(&d), _day(&d), 166 _hour(&d), 0, 0, 0, 167 ); 168 case unit::MINUTE => 169 return new(d.loc, zf, 170 _year(&d), _month(&d), _day(&d), 171 _hour(&d), _minute(&d), 0, 0, 172 ); 173 case unit::SECOND => 174 return new(d.loc, zf, 175 _year(&d), _month(&d), _day(&d), 176 _hour(&d), _minute(&d), _second(&d), 0, 177 ); 178 case unit::NANOSECOND => 179 return d; 180 }; 181 }; 182 183 @test fn pdiff() void = { 184 const cases = [ 185 ( 186 new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!, 187 new(chrono::UTC, 0, 2022, 2, 16, 0, 0, 0, 0)!, 188 period { 189 years = 1, 190 months = 1, 191 days = 1, 192 ... 193 }, 194 ), 195 ( 196 new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!, 197 new(chrono::UTC, 0, 2022, 3, 27, 0, 0, 0, 0)!, 198 period { 199 years = 1, 200 months = 2, 201 days = 12, 202 ... 203 }, 204 ), 205 ( 206 new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!, 207 new(chrono::UTC, 0, 2022, 3, 14, 0, 0, 0, 0)!, 208 period { 209 years = 1, 210 months = 1, 211 days = 27, 212 ... 213 }, 214 ), 215 ( 216 new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!, 217 new(chrono::UTC, 0, 2021, 1, 16, 0, 0, 0, 0)!, 218 period { 219 days = 1, 220 ... 221 }, 222 ), 223 ( 224 new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!, 225 new(chrono::UTC, 0, 2021, 1, 16, 1, 3, 2, 4)!, 226 period { 227 days = 1, 228 hours = 1, 229 minutes = 3, 230 seconds = 2, 231 nanoseconds = 4, 232 ... 233 }, 234 ), 235 ( 236 new(chrono::UTC, 0, 2021, 1, 15, 2, 3, 2, 2)!, 237 new(chrono::UTC, 0, 2021, 1, 16, 1, 1, 2, 4)!, 238 period { 239 hours = 22, 240 minutes = 58, 241 nanoseconds = 2, 242 ... 243 }, 244 ), 245 ( 246 new(chrono::UTC, 0, 500, 1, 1, 0, 0, 0, 0)!, 247 new(chrono::UTC, 0, 3500, 1, 1, 0, 6, 0, 0)!, 248 period { 249 years = 3000, 250 minutes = 6, 251 ... 252 }, 253 ), 254 ( 255 new(chrono::UTC, 0, -500, 1, 1, 0, 0, 0, 0)!, 256 new(chrono::UTC, 0, 2500, 1, 1, 0, 6, 0, 0)!, 257 period { 258 years = 3000, 259 minutes = 6, 260 ... 261 }, 262 ), 263 ( 264 new(chrono::UTC, 0, 2000, 1, 1, 0, 0, 0, 0)!, 265 new(chrono::UTC, 0, 2000, 1, 1, 0, 6, 0, 999999999)!, 266 period { 267 minutes = 6, 268 nanoseconds = 999999999, 269 ... 270 }, 271 ), 272 ( 273 new(chrono::UTC, 0, 2000, 1, 1, 0, 6, 0, 999999999)!, 274 new(chrono::UTC, 0, 2000, 1, 1, 0, 6, 1, 0)!, 275 period { 276 nanoseconds = 1, 277 ... 278 }, 279 ), 280 ( 281 new(chrono::UTC, 0, -4000, 1, 1, 0, 6, 0, 999999999)!, 282 new(chrono::UTC, 0, 4000, 1, 1, 0, 6, 1, 0)!, 283 period { 284 years = 8000, 285 nanoseconds = 1, 286 ... 287 }, 288 ), 289 ]; 290 for (let (da, db, expected) .. cases) { 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 (da, db, expected) .. cases) { 326 assert(unitdiff(da, db, unit::YEAR) == expected.0, 327 "invalid diff_in_years() result"); 328 assert(unitdiff(da, db, unit::MONTH) == expected.1, 329 "invalid diff_in_months() result"); 330 assert(unitdiff(da, db, unit::WEEK) == expected.2, 331 "invalid diff_in_weeks() result"); 332 assert(unitdiff(da, db, unit::DAY) == expected.3, 333 "invalid diff_in_days() result"); 334 assert(unitdiff(da, db, unit::HOUR) == expected.4, 335 "invalid diff_in_hours() result"); 336 assert(unitdiff(da, db, unit::MINUTE) == expected.5, 337 "invalid diff_in_minutes() result"); 338 assert(unitdiff(da, db, unit::SECOND) == expected.6, 339 "invalid diff_in_seconds() result"); 340 assert(unitdiff(da, db, unit::NANOSECOND) == expected.7, 341 "invalid diff_in_nanoseconds() result"); 342 }; 343 }; 344 345 @test fn truncate() void = { 346 const d = new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 2)!; 347 348 assert(chrono::simultaneous( 349 &truncate(d, zflag::CONTIG, unit::ERA)!, 350 &new(chrono::UTC, 0, 1, 1, 1, 0, 0, 0, 0)!)!, 351 "invalid truncate() result 01"); 352 353 assert(chrono::simultaneous( 354 &truncate(d, zflag::CONTIG, unit::YEAR)!, 355 &new(chrono::UTC, 0, 1994, 1, 1, 0, 0, 0, 0)!)!, 356 "invalid truncate() result 02"); 357 358 assert(chrono::simultaneous( 359 &truncate(d, zflag::CONTIG, unit::MONTH)!, 360 &new(chrono::UTC, 0, 1994, 8, 1, 0, 0, 0, 0)!)!, 361 "invalid truncate() result 03"); 362 363 assert(chrono::simultaneous( 364 &truncate(d, zflag::CONTIG, unit::WEEK)!, 365 &new(chrono::UTC, 0, 1994, 8, 22, 0, 0, 0, 0)!)!, 366 "invalid truncate() result 04"); 367 368 assert(chrono::simultaneous( 369 &truncate(d, zflag::CONTIG, unit::DAY)!, 370 &new(chrono::UTC, 0, 1994, 8, 27, 0, 0, 0, 0)!)!, 371 "invalid truncate() result 05"); 372 373 assert(chrono::simultaneous( 374 &truncate(d, zflag::CONTIG, unit::HOUR)!, 375 &new(chrono::UTC, 0, 1994, 8, 27, 11, 0, 0, 0)!)!, 376 "invalid truncate() result 06"); 377 378 assert(chrono::simultaneous( 379 &truncate(d, zflag::CONTIG, unit::MINUTE)!, 380 &new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 0, 0)!)!, 381 "invalid truncate() result 07"); 382 383 assert(chrono::simultaneous( 384 &truncate(d, zflag::CONTIG, unit::SECOND)!, 385 &new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 0)!)!, 386 "invalid truncate() result 08"); 387 388 assert(chrono::simultaneous( 389 &truncate(d, zflag::CONTIG, unit::NANOSECOND)!, 390 &d)!, 391 "invalid truncate() result 09"); 392 };