hare

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

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