hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

reckon.ha (13782B)


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