hare

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

parithm.ha (9688B)


      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 // The [[zflag]] parameter affects the final result. Example:
    123 //
    124 // 	// On this day in Sao Paulo, a +1 hour jump occurs at 00:00.
    125 // 	// The time range 00:00..00:59 is never observed.
    126 // 	//
    127 // 	// 2000-10-08 12:00:00.000000000 -0200 -02 America/Sao_Paulo
    128 // 	let a = date::new(chrono::tz("America/Sao_Paulo")!, -2 * time::HOUR,
    129 // 		2000, 10,  8, 12)!
    130 // 	//
    131 // 	// 2000-10-08 01:00:00.000000000 -0200 -02 America/Sao_Paulo
    132 // 	let b = date::truncate(a, date::zflag::GAP_END, date::unit::DAY)!;
    133 //
    134 export fn truncate(d: date, zf: zflag, u: unit) (date | invalid | zfunresolved) = {
    135 	switch (u) {
    136 	case unit::ERA =>
    137 		return new(d.loc, zf,
    138 			1, 1, 1,
    139 			0, 0, 0, 0,
    140 		);
    141 	case unit::YEAR =>
    142 		return new(d.loc, zf,
    143 			_year(&d), 1, 1,
    144 			0, 0, 0, 0,
    145 		);
    146 	case unit::MONTH =>
    147 		return new(d.loc, zf,
    148 			_year(&d), _month(&d), 1,
    149 			0, 0, 0, 0,
    150 		);
    151 	case unit::WEEK =>
    152 		const dd = chrono::daydate(&d) - _weekday(&d);
    153 		const ymd = calc_ymd(dd);
    154 		return new(d.loc, zf,
    155 			ymd.0, ymd.1, ymd.2,
    156 			0, 0, 0, 0,
    157 		);
    158 	case unit::DAY =>
    159 		return new(d.loc, zf,
    160 			_year(&d), _month(&d), _day(&d),
    161 			0, 0, 0, 0,
    162 		);
    163 	case unit::HOUR =>
    164 		return new(d.loc, zf,
    165 			_year(&d), _month(&d), _day(&d),
    166 			_hour(&d), 0, 0, 0,
    167 		);
    168 	case unit::MINUTE =>
    169 		return new(d.loc, zf,
    170 			_year(&d), _month(&d), _day(&d),
    171 			_hour(&d), _minute(&d), 0, 0,
    172 		);
    173 	case unit::SECOND =>
    174 		return new(d.loc, zf,
    175 			_year(&d), _month(&d), _day(&d),
    176 			_hour(&d), _minute(&d), _second(&d), 0,
    177 		);
    178 	case unit::NANOSECOND =>
    179 		return d;
    180 	};
    181 };
    182 
    183 @test fn pdiff() void = {
    184 	const cases = [
    185 		(
    186 			new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!,
    187 			new(chrono::UTC, 0, 2022, 2, 16, 0, 0, 0, 0)!,
    188 			period {
    189 				years = 1,
    190 				months = 1,
    191 				days = 1,
    192 				...
    193 			},
    194 		),
    195 		(
    196 			new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!,
    197 			new(chrono::UTC, 0, 2022, 3, 27, 0, 0, 0, 0)!,
    198 			period {
    199 				years = 1,
    200 				months = 2,
    201 				days = 12,
    202 				...
    203 			},
    204 		),
    205 		(
    206 			new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!,
    207 			new(chrono::UTC, 0, 2022, 3, 14, 0, 0, 0, 0)!,
    208 			period {
    209 				years = 1,
    210 				months = 1,
    211 				days = 27,
    212 				...
    213 			},
    214 		),
    215 		(
    216 			new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!,
    217 			new(chrono::UTC, 0, 2021, 1, 16, 0, 0, 0, 0)!,
    218 			period {
    219 				days = 1,
    220 				...
    221 			},
    222 		),
    223 		(
    224 			new(chrono::UTC, 0, 2021, 1, 15, 0, 0, 0, 0)!,
    225 			new(chrono::UTC, 0, 2021, 1, 16, 1, 3, 2, 4)!,
    226 			period {
    227 				days = 1,
    228 				hours = 1,
    229 				minutes = 3,
    230 				seconds = 2,
    231 				nanoseconds = 4,
    232 				...
    233 			},
    234 		),
    235 		(
    236 			new(chrono::UTC, 0, 2021, 1, 15, 2, 3, 2, 2)!,
    237 			new(chrono::UTC, 0, 2021, 1, 16, 1, 1, 2, 4)!,
    238 			period {
    239 				hours = 22,
    240 				minutes = 58,
    241 				nanoseconds = 2,
    242 				...
    243 			},
    244 		),
    245 		(
    246 			new(chrono::UTC, 0,  500, 1, 1, 0, 0, 0, 0)!,
    247 			new(chrono::UTC, 0, 3500, 1, 1, 0, 6, 0, 0)!,
    248 			period {
    249 				years = 3000,
    250 				minutes = 6,
    251 				...
    252 			},
    253 		),
    254 		(
    255 			new(chrono::UTC, 0, -500, 1, 1, 0, 0, 0, 0)!,
    256 			new(chrono::UTC, 0, 2500, 1, 1, 0, 6, 0, 0)!,
    257 			period {
    258 				years = 3000,
    259 				minutes = 6,
    260 				...
    261 			},
    262 		),
    263 		(
    264 			new(chrono::UTC, 0, 2000, 1, 1, 0, 0, 0, 0)!,
    265 			new(chrono::UTC, 0, 2000, 1, 1, 0, 6, 0, 999999999)!,
    266 			period {
    267 				minutes = 6,
    268 				nanoseconds = 999999999,
    269 				...
    270 			},
    271 		),
    272 		(
    273 			new(chrono::UTC, 0, 2000, 1, 1, 0, 6, 0, 999999999)!,
    274 			new(chrono::UTC, 0, 2000, 1, 1, 0, 6, 1, 0)!,
    275 			period {
    276 				nanoseconds = 1,
    277 				...
    278 			},
    279 		),
    280 		(
    281 			new(chrono::UTC, 0, -4000, 1, 1, 0, 6, 0, 999999999)!,
    282 			new(chrono::UTC, 0, 4000,  1, 1, 0, 6, 1, 0)!,
    283 			period {
    284 				years = 8000,
    285 				nanoseconds = 1,
    286 				...
    287 			},
    288 		),
    289 	];
    290 	for (let (da, db, expected) .. cases) {
    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 (da, db, expected) .. cases) {
    326 		assert(unitdiff(da, db, unit::YEAR) == expected.0,
    327 			"invalid diff_in_years() result");
    328 		assert(unitdiff(da, db, unit::MONTH) == expected.1,
    329 			"invalid diff_in_months() result");
    330 		assert(unitdiff(da, db, unit::WEEK) == expected.2,
    331 			"invalid diff_in_weeks() result");
    332 		assert(unitdiff(da, db, unit::DAY) == expected.3,
    333 			"invalid diff_in_days() result");
    334 		assert(unitdiff(da, db, unit::HOUR) == expected.4,
    335 			"invalid diff_in_hours() result");
    336 		assert(unitdiff(da, db, unit::MINUTE) == expected.5,
    337 			"invalid diff_in_minutes() result");
    338 		assert(unitdiff(da, db, unit::SECOND) == expected.6,
    339 			"invalid diff_in_seconds() result");
    340 		assert(unitdiff(da, db, unit::NANOSECOND) == expected.7,
    341 			"invalid diff_in_nanoseconds() result");
    342 	};
    343 };
    344 
    345 @test fn truncate() void = {
    346 	const d = new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 2)!;
    347 
    348 	assert(chrono::simultaneous(
    349 			&truncate(d, zflag::CONTIG, unit::ERA)!,
    350 			&new(chrono::UTC, 0, 1, 1, 1, 0, 0, 0, 0)!)!,
    351 		"invalid truncate() result 01");
    352 
    353 	assert(chrono::simultaneous(
    354 			&truncate(d, zflag::CONTIG, unit::YEAR)!,
    355 			&new(chrono::UTC, 0, 1994, 1, 1, 0, 0, 0, 0)!)!,
    356 		"invalid truncate() result 02");
    357 
    358 	assert(chrono::simultaneous(
    359 			&truncate(d, zflag::CONTIG, unit::MONTH)!,
    360 			&new(chrono::UTC, 0, 1994, 8, 1, 0, 0, 0, 0)!)!,
    361 		"invalid truncate() result 03");
    362 
    363 	assert(chrono::simultaneous(
    364 			&truncate(d, zflag::CONTIG, unit::WEEK)!,
    365 			&new(chrono::UTC, 0, 1994, 8, 22, 0, 0, 0, 0)!)!,
    366 		"invalid truncate() result 04");
    367 
    368 	assert(chrono::simultaneous(
    369 			&truncate(d, zflag::CONTIG, unit::DAY)!,
    370 			&new(chrono::UTC, 0, 1994, 8, 27, 0, 0, 0, 0)!)!,
    371 		"invalid truncate() result 05");
    372 
    373 	assert(chrono::simultaneous(
    374 			&truncate(d, zflag::CONTIG, unit::HOUR)!,
    375 			&new(chrono::UTC, 0, 1994, 8, 27, 11, 0, 0, 0)!)!,
    376 		"invalid truncate() result 06");
    377 
    378 	assert(chrono::simultaneous(
    379 			&truncate(d, zflag::CONTIG, unit::MINUTE)!,
    380 			&new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 0, 0)!)!,
    381 		"invalid truncate() result 07");
    382 
    383 	assert(chrono::simultaneous(
    384 			&truncate(d, zflag::CONTIG, unit::SECOND)!,
    385 			&new(chrono::UTC, 0, 1994, 8, 27, 11, 20, 1, 0)!)!,
    386 		"invalid truncate() result 08");
    387 
    388 	assert(chrono::simultaneous(
    389 			&truncate(d, zflag::CONTIG, unit::NANOSECOND)!,
    390 			&d)!,
    391 		"invalid truncate() result 09");
    392 };