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