hare

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

parithm.ha (9892B)


      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 //
    123 // For example, truncating to the nearest unit::MONTH will set the 'day',
    124 // 'hour', 'minute', 'second', and 'nanosecond' fields to their minimum values.
    125 export fn truncate(d: date, u: unit) date = {
    126 	// TODO: There exist timezones where midnight is invalid on certain
    127 	// days. The new()! calls will fail, but we probably don't want to '?'
    128 	// propagate [[invalid]] to keep this function's use simple. The minimum
    129 	// values (the zeroes and ones here) can't be hardcoded. They need
    130 	// calculation. We should either handle this here; or probably in
    131 	// realize(), and then use realize() here.
    132 	return switch (u) {
    133 	case unit::ERA =>
    134 		yield new(d.loc, chrono::ozone(&d).zoff,
    135 			1, 1, 1,
    136 			0, 0, 0, 0,
    137 		)!;
    138 	case unit::YEAR =>
    139 		yield new(d.loc, chrono::ozone(&d).zoff,
    140 			_year(&d), 1, 1,
    141 			0, 0, 0, 0,
    142 		)!;
    143 	case unit::MONTH =>
    144 		yield new(d.loc, chrono::ozone(&d).zoff,
    145 			_year(&d), _month(&d), 1,
    146 			0, 0, 0, 0,
    147 		)!;
    148 	case unit::WEEK =>
    149 		const dd = chrono::daydate(&d) - _weekday(&d);
    150 		const ymd = calc_ymd(dd);
    151 		yield new(d.loc, chrono::ozone(&d).zoff,
    152 			ymd.0, ymd.1, ymd.2,
    153 			0, 0, 0, 0,
    154 		)!;
    155 	case unit::DAY =>
    156 		yield new(d.loc, chrono::ozone(&d).zoff,
    157 			_year(&d), _month(&d), _day(&d),
    158 			0, 0, 0, 0,
    159 		)!;
    160 	case unit::HOUR =>
    161 		yield new(d.loc, chrono::ozone(&d).zoff,
    162 			_year(&d), _month(&d), _day(&d),
    163 			_hour(&d), 0, 0, 0,
    164 		)!;
    165 	case unit::MINUTE =>
    166 		yield new(d.loc, chrono::ozone(&d).zoff,
    167 			_year(&d), _month(&d), _day(&d),
    168 			_hour(&d), _minute(&d), 0, 0,
    169 		)!;
    170 	case unit::SECOND =>
    171 		yield new(d.loc, chrono::ozone(&d).zoff,
    172 			_year(&d), _month(&d), _day(&d),
    173 			_hour(&d), _minute(&d), _second(&d), 0,
    174 		)!;
    175 	case unit::NANOSECOND =>
    176 		yield d;
    177 	};
    178 };
    179 
    180 @test fn pdiff() void = {
    181 	const cases = [
    182 		(
    183 			new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!,
    184 			new(chrono::UTC, 0, 2022, 2, 16, 0, 0, 0, 0)!,
    185 			period {
    186 				years = 1,
    187 				months = 1,
    188 				days = 1,
    189 				...
    190 			},
    191 		),
    192 		(
    193 			new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!,
    194 			new(chrono::UTC, 0, 2022, 3, 27, 0, 0, 0, 0)!,
    195 			period {
    196 				years = 1,
    197 				months = 2,
    198 				days = 12,
    199 				...
    200 			},
    201 		),
    202 		(
    203 			new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!,
    204 			new(chrono::UTC, 0, 2022, 3, 14, 0, 0, 0, 0)!,
    205 			period {
    206 				years = 1,
    207 				months = 1,
    208 				days = 27,
    209 				...
    210 			},
    211 		),
    212 		(
    213 			new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!,
    214 			new(chrono::UTC, 0, 2021, 1, 16, 0, 0, 0, 0)!,
    215 			period {
    216 				days = 1,
    217 				...
    218 			},
    219 		),
    220 		(
    221 			new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!,
    222 			new(chrono::UTC, 0, 2021, 1, 16, 1, 3, 2, 4)!,
    223 			period {
    224 				days = 1,
    225 				hours = 1,
    226 				minutes = 3,
    227 				seconds = 2,
    228 				nanoseconds = 4,
    229 				...
    230 			},
    231 		),
    232 		(
    233 			new(chrono::UTC, 0, 2021, 1, 15, 2, 3, 2, 2)!,
    234 			new(chrono::UTC, 0, 2021, 1, 16, 1, 1, 2, 4)!,
    235 			period {
    236 				hours = 22,
    237 				minutes = 58,
    238 				nanoseconds = 2,
    239 				...
    240 			},
    241 		),
    242 		(
    243 			new(chrono::UTC, 0,  500, 1, 1, 0, 0, 0, 0)!,
    244 			new(chrono::UTC, 0, 3500, 1, 1, 0, 6, 0, 0)!,
    245 			period {
    246 				years = 3000,
    247 				minutes = 6,
    248 				...
    249 			},
    250 		),
    251 		(
    252 			new(chrono::UTC, 0, -500, 1, 1, 0, 0, 0, 0)!,
    253 			new(chrono::UTC, 0, 2500, 1, 1, 0, 6, 0, 0)!,
    254 			period {
    255 				years = 3000,
    256 				minutes = 6,
    257 				...
    258 			},
    259 		),
    260 		(
    261 			new(chrono::UTC, 0, 2000, 1, 1, 0, 0, 0, 0)!,
    262 			new(chrono::UTC, 0, 2000, 1, 1, 0, 6, 0, 999999999)!,
    263 			period {
    264 				minutes = 6,
    265 				nanoseconds = 999999999,
    266 				...
    267 			},
    268 		),
    269 		(
    270 			new(chrono::UTC, 0, 2000, 1, 1, 0, 6, 0, 999999999)!,
    271 			new(chrono::UTC, 0, 2000, 1, 1, 0, 6, 1, 0)!,
    272 			period {
    273 				nanoseconds = 1,
    274 				...
    275 			},
    276 		),
    277 		(
    278 			new(chrono::UTC, 0, -4000, 1, 1, 0, 6, 0, 999999999)!,
    279 			new(chrono::UTC, 0, 4000,  1, 1, 0, 6, 1, 0)!,
    280 			period {
    281 				years = 8000,
    282 				nanoseconds = 1,
    283 				...
    284 			},
    285 		),
    286 	];
    287 	for (let i = 0z; i < len(cases); i += 1) {
    288 		const da = cases[i].0;
    289 		const db = cases[i].1;
    290 		const expected = cases[i].2;
    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 i = 0z; i < len(cases); i += 1) {
    326 		const da = cases[i].0;
    327 		const db = cases[i].1;
    328 		const expected = cases[i].2;
    329 		assert(unitdiff(da, db, unit::YEAR) == expected.0,
    330 			"invalid diff_in_years() result");
    331 		assert(unitdiff(da, db, unit::MONTH) == expected.1,
    332 			"invalid diff_in_months() result");
    333 		assert(unitdiff(da, db, unit::WEEK) == expected.2,
    334 			"invalid diff_in_weeks() result");
    335 		assert(unitdiff(da, db, unit::DAY) == expected.3,
    336 			"invalid diff_in_days() result");
    337 		assert(unitdiff(da, db, unit::HOUR) == expected.4,
    338 			"invalid diff_in_hours() result");
    339 		assert(unitdiff(da, db, unit::MINUTE) == expected.5,
    340 			"invalid diff_in_minutes() result");
    341 		assert(unitdiff(da, db, unit::SECOND) == expected.6,
    342 			"invalid diff_in_seconds() result");
    343 		assert(unitdiff(da, db, unit::NANOSECOND) == expected.7,
    344 			"invalid diff_in_nanoseconds() result");
    345 	};
    346 };
    347 
    348 @test fn truncate() void = {
    349 	const d = new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 2)!;
    350 
    351 	assert(chrono::simultaneous(
    352 			&truncate(d, unit::ERA),
    353 			&new(chrono::UTC, 0, 1, 1, 1, 0, 0, 0, 0)!)!,
    354 		"invalid truncate() result 01");
    355 
    356 	assert(chrono::simultaneous(
    357 			&truncate(d, unit::YEAR),
    358 			&new(chrono::UTC, 0, 1994, 1, 1, 0, 0, 0, 0)!)!,
    359 		"invalid truncate() result 02");
    360 
    361 	assert(chrono::simultaneous(
    362 			&truncate(d, unit::MONTH),
    363 			&new(chrono::UTC, 0, 1994, 8, 1, 0, 0, 0, 0)!)!,
    364 		"invalid truncate() result 03");
    365 
    366 	assert(chrono::simultaneous(
    367 			&truncate(d, unit::WEEK),
    368 			&new(chrono::UTC, 0, 1994, 8, 22, 0, 0, 0, 0)!)!,
    369 		"invalid truncate() result 04");
    370 
    371 	assert(chrono::simultaneous(
    372 			&truncate(d, unit::DAY),
    373 			&new(chrono::UTC, 0, 1994, 8, 27, 0, 0, 0, 0)!)!,
    374 		"invalid truncate() result 05");
    375 
    376 	assert(chrono::simultaneous(
    377 			&truncate(d, unit::HOUR),
    378 			&new(chrono::UTC, 0, 1994, 8, 27, 11, 0, 0, 0)!)!,
    379 		"invalid truncate() result 06");
    380 
    381 	assert(chrono::simultaneous(
    382 			&truncate(d, unit::MINUTE),
    383 			&new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 0, 0)!)!,
    384 		"invalid truncate() result 07");
    385 
    386 	assert(chrono::simultaneous(
    387 			&truncate(d, unit::SECOND),
    388 			&new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 0)!)!,
    389 		"invalid truncate() result 08");
    390 
    391 	assert(chrono::simultaneous(
    392 			&truncate(d, unit::NANOSECOND),
    393 			&d)!,
    394 		"invalid truncate() result 09");
    395 };