hare

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

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 };