commit 09087b54346b22ee1524366180ca9bd2c2c7607a
parent 05eb7293847b1be053a932e5d7c47384ac903703
Author: Byron Torres <b@torresjrjr.com>
Date: Mon, 15 Nov 2021 01:30:45 +0000
add calendrical calc fns with some tests
Some calendrical calculation functions were adapted from a patch sent by
Vlad-Stefan Harbuz <vlad@vladh.net>.
Signed-off-by: Byron Torres <b@torresjrjr.com>
Signed-off-by: Vlad-Stefan Harbuz <vlad@vladh.net>
Diffstat:
6 files changed, 325 insertions(+), 101 deletions(-)
diff --git a/datetime/calendar.ha b/datetime/calendar.ha
@@ -61,44 +61,42 @@ export fn era(m: chrono::moment) int = {
// Calculates a moment's year
export fn year(m: chrono::moment) int = {
- const ld = localdate { ... };
- calc_ymd(m.date, &ld);
- return ld.year as int;
+ return 0; // TODO
};
// Calculates a moment's month
export fn month(m: chrono::moment) int = {
- const ld = localdate { ... };
- calc_ymd(m.date, &ld);
- return ld.month as int;
+ return 0; // TODO
};
// Calculates a moment's day of the month
export fn day(m: chrono::moment) int = {
- const ld = localdate { ... };
- calc_ymd(m.date, &ld);
- return ld.day as int;
+ return 0; // TODO
};
// Calculates a moment's ISO week calendar year
-export fn weekyear(m: chrono::moment) int = {
- const ld = localdate { ... };
- calc_ywd(m.date, &ld);
- return ld.weekyear as int;
+export fn isoweekyear(m: chrono::moment) int = {
+ return 0; // TODO
};
// Calculates a moment's ISO week
+export fn isoweek(m: chrono::moment) int = {
+ return 0; // TODO
+};
+
+// Calculates a moment's Gregorian week
export fn week(m: chrono::moment) int = {
- const ld = localdate { ... };
- calc_ywd(m.date, &ld);
- return ld.week as int;
+ return 0; // TODO
+};
+
+// Calculates a moment's day of the week
+export fn weekday(m: chrono::moment) int = {
+ return 0; // TODO
};
// Calculates a moment's ordinal day of the year
export fn yearday(m: chrono::moment) int = {
- const ld = localdate { ... };
- calc_yd(m.date, &ld);
- return ld.yearday as int;
+ return 0; // TODO
};
//
diff --git a/datetime/date+test.ha b/datetime/date+test.ha
@@ -0,0 +1,98 @@
+@test fn calc_ymd() void = {
+ const cases = [
+ (-999999, (-0768, 02, 05)),
+ (-719529, (-0001, 12, 31)),
+ (-719528, ( 0000, 01, 01)),
+ (-719527, ( 0000, 01, 02)),
+ (-719163, ( 0000, 12, 31)),
+ (-719162, ( 0001, 01, 01)),
+ (-719161, ( 0001, 01, 02)),
+ ( -1745, ( 1965, 03, 23)),
+ ( -1, ( 1969, 12, 31)),
+ ( 0, ( 1970, 01, 01)),
+ ( 1, ( 1970, 01, 02)),
+ ( 10956, ( 1999, 12, 31)),
+ ( 10957, ( 2000, 01, 01)),
+ ( 10958, ( 2000, 01, 02)),
+ ( 24854, ( 2038, 01, 18)),
+ ( 24855, ( 2038, 01, 19)),
+ ( 24856, ( 2038, 01, 20)),
+ ( 100000, ( 2243, 10, 17)),
+ ( 999999, ( 4707, 11, 28)),
+ (1000000, ( 4707, 11, 29)),
+ (9999999, (29349, 01, 25)),
+ ];
+ for (let i = 0z; i < len(cases); i += 1) {
+ const paramt = cases[i].0;
+ const expect = cases[i].1;
+ const actual = calc_ymd(paramt);
+ assert(expect.0 == actual.0, "year mismatch");
+ assert(expect.1 == actual.1, "month mismatch");
+ assert(expect.2 == actual.2, "day mismatch");
+ };
+};
+
+@test fn calc_yearday() void = {
+ const cases = [
+ ((-0768, 02, 05), 037),
+ ((-0001, 12, 31), 365),
+ (( 0000, 01, 01), 001),
+ (( 0000, 01, 02), 002),
+ (( 0000, 12, 31), 366),
+ (( 0001, 01, 01), 001),
+ (( 0001, 01, 02), 002),
+ (( 1965, 03, 23), 082),
+ (( 1969, 12, 31), 365),
+ (( 1970, 01, 01), 001),
+ (( 1970, 01, 02), 002),
+ (( 1999, 12, 31), 365),
+ (( 2000, 01, 01), 001),
+ (( 2000, 01, 02), 002),
+ (( 2038, 01, 18), 018),
+ (( 2038, 01, 19), 019),
+ (( 2038, 01, 20), 020),
+ (( 2243, 10, 17), 290),
+ (( 4707, 11, 28), 332),
+ (( 4707, 11, 29), 333),
+ ((29349, 01, 25), 025),
+ ];
+ for (let i = 0z; i < len(cases); i += 1) {
+ const params = cases[i].0;
+ const expect = cases[i].1;
+ const actual = calc_yearday(params.0, params.1, params.2);
+ assert(expect == actual, "yearday miscalculation");
+ };
+};
+
+@test fn calc_weekday() void = {
+ const cases = [
+ (-999999, 4), // -0768-02-05
+ (-719529, 5), // -0001-12-31
+ (-719528, 6), // 0000-01-01
+ (-719527, 7), // 0000-01-02
+ (-719163, 7), // 0000-12-31
+ (-719162, 1), // 0001-01-01
+ (-719161, 2), // 0001-01-02
+ ( -1745, 2), // 1965-03-23
+ ( -1, 3), // 1969-12-31
+ ( 0, 4), // 1970-01-01
+ ( 1, 5), // 1970-01-02
+ ( 10956, 5), // 1999-12-31
+ ( 10957, 6), // 2000-01-01
+ ( 10958, 7), // 2000-01-02
+ ( 24854, 1), // 2038-01-18
+ ( 24855, 2), // 2038-01-19
+ ( 24856, 3), // 2038-01-20
+ ( 100000, 2), // 2243-10-17
+ ( 999999, 4), // 4707-11-28
+ (1000000, 5), // 4707-11-29
+ (9999999, 6), // 29349-01-25
+ ];
+ for (let i = 0z; i < len(cases); i += 1) {
+ const paramt = cases[i].0;
+ const expect = cases[i].1;
+ const actual = calc_weekday(paramt);
+ assert(expect == actual, "weekday miscalculation");
+ };
+};
+
diff --git a/datetime/date.ha b/datetime/date.ha
@@ -4,78 +4,188 @@ use errors;
// Represents an ISO calendar date.
// Instances created from datetime:: functions are guaranteed to be valid.
export type localdate = struct {
- era: (void | int),
- year: (void | int),
- month: (void | int),
- day: (void | int),
- weekyear: (void | int),
- week: (void | int),
- weekday: (void | int),
- yearday: (void | int),
+ era: (void | int),
+ year: (void | int),
+ month: (void | int),
+ day: (void | int),
+ yearday: (void | int),
+ isoweekyear: (void | int),
+ isoweek: (void | int),
+ week: (void | int),
+ weekday: (void | int),
};
export fn init_date() localdate = localdate {
- era = void,
- year = void,
- month = void,
- day = void,
- weekyear = void,
- week = void,
- weekday = void,
- yearday = void,
-};
-
-// TODO: The following function can be split up for efficiency.
-// calc_y, calc_m, calc_d, etc.
-
-// Populates the year, month, and day fields of a given [[localdate]],
-// calculated from a [[chrono::epochal]]
-export fn calc_ymd(epochal: chrono::epochal, ld: *localdate) void = {
+ era = void,
+ year = void,
+ month = void,
+ day = void,
+ yearday = void,
+ isoweekyear = void,
+ isoweek = void,
+ week = void,
+ weekday = void,
+};
+
+export type WEEKDAYS = enum int {
+ MON = 1,
+ TUE = 2,
+ WED = 3,
+ THU = 4,
+ FRI = 5,
+ SAT = 6,
+ SUN = 7,
+};
+
+
+// Calculates an era, given a year
+fn calc_era(y: int) int = {
+ return if (y >= 0) {
+ yield 1; // CE "Common Era"
+ } else {
+ yield 0; // BCE "Before Common Era"
+ };
+};
+
+
+// Calculates the (year, month, day) of a [[chrono::epochal]]
+//
+// TODO: Split up this function? Probably not. Most useful as is.
+fn calc_ymd(e: chrono::epochal) (int, int, int) = {
// Algorithm adapted from:
// https://en.wikipedia.org/wiki/Julian_day#Julian_or_Gregorian_calendar_from_Julian_day_number
//
// Alternate methods of date calculation should be explored.
- const J = epochal - EPOCH_JULIAN;
+ const J = e - EPOCH_JULIAN;
+
+ // TODO: substitute numbers where possible
+ const b = 274277;
+ const c = -38;
const j = 1401;
- const y = 4716;
- const B = 274277;
- const C = -38;
- const r = 4;
- const v = 3;
+ const m = 2;
+ const n = 12;
const p = 1461;
+ const r = 4;
+ const s = 153;
const u = 5;
+ const v = 3;
const w = 2;
- const s = 153;
- const n = 12;
- const m = 2;
+ const y = 4716;
- const f = J + j + (((4 * J + B) / 146097) * 3) / 4 + C;
- const e = r * f + v;
- const g = (e % p) / r;
+ const f = J + j + (((4 * J + b) / 146097) * 3) / 4 + c;
+ const a = r * f + v;
+ const g = (a % p) / r;
const h = u * g + w;
const D = (h % s) / u + 1;
const M = ((h / s + m) % n) + 1;
- const Y = (e / p) - y + (n + m - M) / n;
+ const Y = (a / p) - y + (n + m - M) / n;
- ld.year = Y: int;
- ld.month = M: int;
- ld.day = D: int;
+ return (Y: int, M: int, D: int);
};
-export fn calc_ywd(e: chrono::epochal, ld: *localdate) void = {
- // TODO
- ld.year = 0;
- ld.week = 0;
- ld.weekday = 0;
- return;
+// Calculates the day of a year, given a year, month, and day
+fn calc_yearday(y: int, m: int, d: int) int = {
+ const months_firsts: [_]int = [
+ 0, 31, 59,
+ 90, 120, 151,
+ 181, 212, 243,
+ 273, 304, 334,
+ ];
+
+ if (m >= 2 && is_leap_year(y)) {
+ return months_firsts[m - 1] + d + 1;
+ } else {
+ return months_firsts[m - 1] + d;
+ };
};
-export fn calc_yd(e: chrono::epochal, ld: *localdate) void = {
- // TODO
- ld.year = 0;
- ld.yearday = 0;
- return;
+// Calculates the ISO week-numbering year,
+// given a year, month, day, and weekday
+fn calc_isoweekyear(y: int, m: int, d: int, wd: int) int = {
+ if (
+ // if the date is within a week whose Thurday
+ // belongs to the previous gregorian year
+ m == 1 && (
+ (d == 1 && (wd == 5 || wd == 6 || wd == 7))
+ || (d == 2 && (wd == 6 || wd == 7))
+ || (d == 3 && wd == 7)
+ )
+ ) {
+ return y - 1;
+ } else if (
+ // if the date is within a week whose Thurday
+ // belongs to the next gregorian year
+ m == 12 && (
+ (d == 29 && wd == 1)
+ || (d == 30 && (wd == 1 || wd == 2))
+ || (d == 31 && (wd == 1 || wd == 2 || wd == 3))
+ )
+ ) {
+ return y + 1;
+ } else {
+ return y;
+ };
+};
+
+// Calculates the ISO week of a [[chrono::epochal]]
+fn calc_isoweek(y: int, w: int, wd: int, yd: int) int = {
+ const jan1wd = (yd - wd + 7) % 7 + 1;
+
+ const iw = if (jan1wd == 1) {
+ yield w;
+ } else if (jan1wd == 2 || jan1wd == 3 || jan1wd == 4) {
+ yield w + 1;
+ } else {
+ yield if (w == 0) {
+ yield if (jan1wd == 5) {
+ yield 53;
+ } else if (jan1wd == 6) {
+ yield if (is_leap_year(y - 1)) {
+ yield 53;
+ } else {
+ yield 52;
+ };
+ } else if (jan1wd == 7) {
+ yield 52;
+ } else {
+ // all jan1wd values exhausted
+ abort("Unreachable");
+ };
+ } else {
+ yield w;
+ };
+ };
+
+ return iw;
+};
+
+// Calculates the week within a Gregorian year [0..53],
+// given a yearday and weekday.
+// All days in a new year before the year's first Monday belong to week 0.
+fn calc_week(yd: int, wd: int) int = {
+ return (5 + yd - wd) / 7;
+};
+
+// Calculates the weekday of a [[chrono::epochal]],
+// from Monday=1 to Sunday=7
+fn calc_weekday(e: chrono::epochal) int = {
+ const wd = ((e + 3) % 7 + 1): int;
+ return if (wd > 0) wd else wd + 7;
+};
+
+// Calculates the weekday of a [[chrono::epochal]],
+// from Monday=0 to Sunday=6
+fn calc_zeroweekday(wd: int) int = {
+ return wd - 1;
+};
+
+// Calculates whether a given year is a leap year.
+fn is_leap_year(y: int) bool = {
+ return if (y % 4 != 0) false
+ else if (y % 100 != 0) true
+ else if (y % 400 != 0) false
+ else true;
};
// Converts a [[chrono::epocal]] to a [[localdate]]. The fields in "date" are
@@ -91,32 +201,36 @@ export fn conv_epochal_localdate(
date: *localdate,
want: *localdate,
) void = {
- if (
- // TODO: split up calc_ymd for finer grained
- // calculating and populating
- want.year is int
- || want.month is int
- || want.day is int
- ) {
- calc_ymd(e, date);
+ if (want.year is int || want.month is int || want.day is int) {
+ const ymd = calc_ymd(e);
+ date.year = ymd.0;
+ date.month = ymd.1;
+ date.day = ymd.2;
};
- if (
- want.year is int
- || want.week is int
- || want.weekday is int
- ) {
- calc_ywd(e, date);
+
+ if (want.yearday is int) {
+ if (date.year is void || date.month is void || date.day is void) {
+ const ymd = calc_ymd(e);
+ date.year = ymd.0;
+ date.month = ymd.1;
+ date.day = ymd.2;
+ };
+ calc_yearday(date.year: int, date.month: int, date.day: int);
};
- if (
- want.year is int
- || want.yearday is int
- ) {
- calc_yd(e, date);
+
+ if (want.week is int) {
+ // TODO
+ //calc_isoweek(e);
+ void;
+ };
+
+ if (want.weekday is int) {
+ date.weekday = calc_weekday(e);
};
};
// Converts a year-month-day date into an [[chrono::epocal]]
-export fn epocal_from_ymd(y: int, m: int, d: int) (chrono::epochal | errors::invalid) = {
+fn epocal_from_ymd(y: int, m: int, d: int) (chrono::epochal | errors::invalid) = {
// Algorithm adapted from:
// https://en.wikipedia.org/wiki/Julian_day
//
@@ -134,13 +248,13 @@ export fn epocal_from_ymd(y: int, m: int, d: int) (chrono::epochal | errors::inv
};
// Converts a year-week-weekday date into an [[chrono::epocal]]
-export fn epocal_from_ywd() (chrono::epochal | errors::invalid) = {
+fn epocal_from_ywd() (chrono::epochal | errors::invalid) = {
// TODO
return 0;
};
// Converts a year-yearday date into an [[chrono::epocal]]
-export fn epocal_from_yd() (chrono::epochal | errors::invalid) = {
+fn epocal_from_yd() (chrono::epochal | errors::invalid) = {
// TODO
return 0;
};
diff --git a/datetime/datetime.ha b/datetime/datetime.ha
@@ -71,7 +71,8 @@ export fn new_datetime(
year = year,
month = month,
day = day,
- weekyear = void,
+ isoweekyear = void,
+ isoweek = void,
week = void,
weekday = void,
yearday = void,
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -182,14 +182,26 @@ bytes() {
}
datetime() {
- gen_srcs datetime \
- calendar.ha \
- datetime.ha \
- timezone.ha \
- date.ha \
- time.ha \
- format.ha
- gen_ssa datetime errors fmt strings strio time time::chrono
+ if [ $testing -eq 0 ]
+ then
+ gen_srcs datetime \
+ calendar.ha \
+ datetime.ha \
+ timezone.ha \
+ date.ha \
+ time.ha \
+ format.ha
+ else
+ gen_srcs datetime \
+ calendar.ha \
+ datetime.ha \
+ timezone.ha \
+ date.ha \
+ date+test.ha \
+ time.ha \
+ format.ha
+ fi
+ gen_ssa datetime errors fmt strings strio time time::chrono
}
compress_flate() {
diff --git a/stdlib.mk b/stdlib.mk
@@ -2896,6 +2896,7 @@ testlib_datetime_any_srcs= \
$(STDLIB)/datetime/datetime.ha \
$(STDLIB)/datetime/timezone.ha \
$(STDLIB)/datetime/date.ha \
+ $(STDLIB)/datetime/date+test.ha \
$(STDLIB)/datetime/time.ha \
$(STDLIB)/datetime/format.ha