commit 603b0ee10143a9bd5abfe189f05264ba26d638ee
parent 101287c697c47f91b3da461dfd6658d3337997bc
Author: Byron Torres <b@torresjrjr.com>
Date: Tue, 1 Feb 2022 22:34:59 +0000
new types {chrono,datetime}::invalid
Signed-off-by: Byron Torres <b@torresjrjr.com>
Diffstat:
7 files changed, 65 insertions(+), 56 deletions(-)
diff --git a/datetime/date.ha b/datetime/date.ha
@@ -194,9 +194,9 @@ fn calc_zeroweekday(wd: int) int = {
};
// Calculates the [[chrono::epochal]], given a (year, month, day) date
-fn calc_epochal_from_ymd(y: int, m: int, d: int) (chrono::epochal | errors::invalid) = {
+fn calc_epochal_from_ymd(y: int, m: int, d: int) (chrono::epochal | invalid) = {
if (!is_valid_ymd(y, m, d)) {
- return errors::invalid;
+ return invalid;
};
// Algorithm adapted from:
// https://en.wikipedia.org/wiki/Julian_day
@@ -214,7 +214,7 @@ fn calc_epochal_from_ymd(y: int, m: int, d: int) (chrono::epochal | errors::inva
};
// Calculates the [[chrono::epochal]], given a (year, week, weekday) date
-fn calc_epochal_from_ywd(y: int, w: int, wd: int) (chrono::epochal | errors::invalid) = {
+fn calc_epochal_from_ywd(y: int, w: int, wd: int) (chrono::epochal | invalid) = {
const jan1 = calc_epochal_from_ymd(y, 1, 1)?;
const jan1_wd = calc_weekday(jan1);
const yd = if (w == 0) {
@@ -227,16 +227,16 @@ fn calc_epochal_from_ywd(y: int, w: int, wd: int) (chrono::epochal | errors::inv
};
// Calculates a (year, month, day) date given a (year, yearday) date
-fn calc_ymd_from_yd(y: int, yd: int) ((int, int, int) | errors::invalid) = {
+fn calc_ymd_from_yd(y: int, yd: int) ((int, int, int) | invalid) = {
if (!is_valid_yd(y, yd)) {
- return errors::invalid;
+ return invalid;
};
let m: int = 1;
let monthdays = calc_n_days_in_month(y, m);
let d = yd;
for (true) {
if (m > 12) {
- return errors::invalid;
+ return invalid;
};
if (d <= monthdays) {
return (y, m, d);
@@ -245,11 +245,11 @@ fn calc_ymd_from_yd(y: int, yd: int) ((int, int, int) | errors::invalid) = {
m += 1;
monthdays = calc_n_days_in_month(y, m);
};
- return errors::invalid;
+ return invalid;
};
// Calculates the [[chrono::epochal]], given a (year, yearday) date
-fn calc_epochal_from_yd(y: int, yd: int) (chrono::epochal | errors::invalid) = {
+fn calc_epochal_from_yd(y: int, yd: int) (chrono::epochal | invalid) = {
const ymd = calc_ymd_from_yd(y, yd)?;
return calc_epochal_from_ymd(ymd.0, ymd.1, ymd.2)?;
};
@@ -295,7 +295,7 @@ fn calc_epochal_from_yd(y: int, yd: int) (chrono::epochal | errors::invalid) = {
);
if (should_error) {
- assert(actual is errors::invalid, "invalid date accepted");
+ assert(actual is invalid, "invalid date accepted");
} else {
assert(actual is chrono::epochal, "valid date not accepted");
assert(actual as chrono::epochal == expect, "epochal miscalculation");
@@ -426,9 +426,9 @@ fn calc_epochal_from_yd(y: int, yd: int) (chrono::epochal | errors::invalid) = {
assert(expected == actual,
"error in epochal calculation from yd");
};
- assert(calc_epochal_from_yd(2020, 0) is errors::invalid,
+ assert(calc_epochal_from_yd(2020, 0) is invalid,
"calc_epochal_from_yd() did not reject invalid yearday");
- assert(calc_epochal_from_yd(2020, 400) is errors::invalid,
+ assert(calc_epochal_from_yd(2020, 400) is invalid,
"calc_epochal_from_yd() did not reject invalid yearday");
};
diff --git a/datetime/datetime.ha b/datetime/datetime.ha
@@ -2,6 +2,9 @@ use errors;
use time;
use time::chrono;
+// The given combination of date, time, and locality is invalid.
+export type invalid = !chrono::invalid;
+
export type datetime = struct {
chrono::moment,
@@ -103,7 +106,7 @@ export fn new(
// TODO: improve variety of errors.
// `invaliddatetime = !void` ?
// `invaliddatetime = !datetime::mock` ?
-) (datetime | errors::invalid) = {
+) (datetime | invalid) = {
let defaults: [_]int = [
0, 1, 1, // year month day
0, 0, 0, 0, // hour min sec nsec
@@ -111,7 +114,7 @@ export fn new(
if (len(fields) > len(defaults)) {
// cannot specify more than 7 fields
- return errors::invalid;
+ return invalid;
};
for (let i = 0z; i < len(fields); i += 1) {
@@ -126,14 +129,16 @@ export fn new(
const sec = defaults[5];
const nsec = defaults[6];
+ let m = chrono::moment {
+ date = calc_epochal_from_ymd(year, month, day)?,
+ time = calc_time_from_hmsn(hour, min, sec, nsec)?,
+ loc = if (loc is void) chrono::LOCAL else loc: *chrono::timezone,
+ zone = chrono::zone { ... },
+ };
+
// TODO: Set the correct values according to the given zo and
// locality/timezone.
- let m = chrono::new(
- calc_epochal_from_ymd(year, month, day)?,
- calc_time_from_hmsn(hour, min, sec, nsec)?,
- if (loc is void) chrono::LOCAL else loc: *chrono::timezone,
- );
-
+ //
// figuring out what zone this moment observes
if (zo is time::duration) {
// Transform inversely to the moment that would transform back
@@ -161,7 +166,7 @@ export fn new(
) {
void;
} else {
- return errors::invalid;
+ return invalid;
};
return dt;
};
@@ -194,7 +199,7 @@ export fn from_moment(m: chrono::moment) datetime = {
// using [[strategy::ALL]], or fails otherwise.
//
// TODO: allow the user to specify [[strategy]] for security?
-export fn from_str(layout: str, s: str) (datetime | insufficient | errors::invalid) = {
+export fn from_str(layout: str, s: str) (datetime | insufficient | invalid) = {
const b = newmock();
parse(&b, layout, s)?;
return finish(&b)?;
@@ -231,7 +236,7 @@ export fn newmock() mock = init(): mock;
// Returns a datetime from a mock. The provided [[strategy]]s will be tried in
// order until a valid datetime is produced, or fail otherwise. The default
// strategy is [[strategy::ALL]].
-export fn finish(f: *mock, m: strategy...) (datetime | insufficient | errors::invalid) = {
+export fn finish(f: *mock, m: strategy...) (datetime | insufficient | invalid) = {
if (len(m) == 0) {
m = [strategy::ALL];
};
diff --git a/datetime/format.ha b/datetime/format.ha
@@ -57,7 +57,7 @@ def MONTHS_SHORT: [_]str = [
// Formats a [[datetime]] and writes it into a caller supplied buffer.
// The returned string is borrowed from this buffer.
-export fn bformat(buf: []u8, layout: str, dt: *datetime) (str | errors::invalid | io::error) = {
+export fn bformat(buf: []u8, layout: str, dt: *datetime) (str | invalid | io::error) = {
let sink = strio::fixed(buf);
fmtstream(&sink, layout, dt)?;
return strio::string(&sink);
@@ -65,13 +65,13 @@ export fn bformat(buf: []u8, layout: str, dt: *datetime) (str | errors::invalid
// Formats a [[datetime]] and writes it into a heap-allocated string.
// The caller must free the return value.
-export fn format(layout: str, dt: *datetime) (str | errors::invalid | io::error) = {
+export fn format(layout: str, dt: *datetime) (str | invalid | io::error) = {
let sink = strio::dynamic();
fmtstream(&sink, layout, dt)?;
return strio::string(&sink);
};
-fn fmt_specifier(r: rune, dt: *datetime) (str | errors::invalid | io::error) = {
+fn fmt_specifier(r: rune, dt: *datetime) (str | invalid | io::error) = {
return switch (r) {
case 'a' =>
yield WEEKDAYS_SHORT[weekday(dt) - 1];
@@ -147,7 +147,7 @@ export fn fmtstream(
h: io::handle,
layout: str,
dt: *datetime
-) (size | errors::invalid | io::error) = {
+) (size | invalid | io::error) = {
const iter = strings::iter(layout);
let escaped = false;
let n = 0z;
@@ -173,10 +173,10 @@ export fn fmtstream(
return n;
};
-fn get_default_locale_string_index(iter: *strings::iterator, list: []str) (int | errors::invalid) = {
+fn get_default_locale_string_index(iter: *strings::iterator, list: []str) (int | invalid) = {
const name = strings::iter_str(iter);
if (len(name) == 0) {
- return errors::invalid;
+ return invalid;
};
for(let i = 0z; i < len(list); i += 1) {
if (strings::hasprefix(name, list[i])) {
@@ -187,10 +187,10 @@ fn get_default_locale_string_index(iter: *strings::iterator, list: []str) (int |
return (i: int) + 1;
};
};
- return errors::invalid;
+ return invalid;
};
-fn get_max_n_digits(iter: *strings::iterator, n: uint) (int | errors::invalid) = {
+fn get_max_n_digits(iter: *strings::iterator, n: uint) (int | invalid) = {
let buf: [64]u8 = [0...];
let bufstr = strio::fixed(buf);
for (let i = 0z; i < n; i += 1) {
@@ -206,7 +206,7 @@ fn get_max_n_digits(iter: *strings::iterator, n: uint) (int | errors::invalid) =
};
match (strio::appendrune(&bufstr, r)) {
case io::error =>
- return errors::invalid;
+ return invalid;
case =>
void;
};
@@ -215,14 +215,14 @@ fn get_max_n_digits(iter: *strings::iterator, n: uint) (int | errors::invalid) =
case let res: int =>
yield res;
case =>
- yield errors::invalid;
+ yield invalid;
};
};
-fn eat_one_rune(iter: *strings::iterator, needle: rune) (uint | errors::invalid) = {
+fn eat_one_rune(iter: *strings::iterator, needle: rune) (uint | invalid) = {
let s_r = match (strings::next(iter)) {
case void =>
- return errors::invalid;
+ return invalid;
case let r: rune =>
yield r;
};
@@ -319,13 +319,13 @@ fn hour12(dt: *datetime) int = {
// dt.nsec as int == 123, "invalid parsing results");
// // General errors
-// assert(parse("%Y-%m-%d", "1a94-08-27", &dt) is errors::invalid,
+// assert(parse("%Y-%m-%d", "1a94-08-27", &dt) is invalid,
// "invalid datetime string did not throw error");
-// assert(parse("%Y-%m-%d", "1994-123-27", &dt) is errors::invalid,
+// assert(parse("%Y-%m-%d", "1994-123-27", &dt) is invalid,
// "invalid datetime string did not throw error");
-// assert(parse("%Y-%m-%d", "a994-08-27", &dt) is errors::invalid,
+// assert(parse("%Y-%m-%d", "a994-08-27", &dt) is invalid,
// "invalid datetime string did not throw error");
// // Basic specifiers
@@ -357,10 +357,10 @@ fn hour12(dt: *datetime) int = {
// parse("%H %p", "6 PM", &dt)!;
// assert(dt.hour as int == 18, "invalid parsing results");
-// assert(parse("%H %p", "13 PM", &dt) is errors::invalid,
+// assert(parse("%H %p", "13 PM", &dt) is invalid,
// "invalid parsing results");
-// assert(parse("%H %p", "PM 6", &dt) is errors::invalid,
+// assert(parse("%H %p", "PM 6", &dt) is invalid,
// "invalid parsing results");
// parse("%u", "7", &dt)!;
diff --git a/datetime/parse.ha b/datetime/parse.ha
@@ -8,7 +8,7 @@ use strings;
// datetime::parse(&mok, "%Y-%m-%d", "2038-01-19");
// datetime::parse(&mok, "%H:%M:%S", "03:14:07");
//
-export fn parse(mok: *mock, layout: str, s: str) (void | errors::invalid) = {
+export fn parse(mok: *mock, layout: str, s: str) (void | invalid) = {
const format_iter = strings::iter(layout);
const s_iter = strings::iter(s);
let escaped = false;
@@ -28,12 +28,12 @@ export fn parse(mok: *mock, layout: str, s: str) (void | errors::invalid) = {
if (!escaped) {
let s_r = match (strings::next(&s_iter)) {
case void =>
- return errors::invalid;
+ return invalid;
case let r: rune =>
yield r;
};
if (s_r != format_r) {
- return errors::invalid;
+ return invalid;
};
continue;
};
@@ -72,7 +72,7 @@ export fn parse(mok: *mock, layout: str, s: str) (void | errors::invalid) = {
yield clamp_int(hour, 1, 12);
};
case =>
- return errors::invalid;
+ return invalid;
};
case 'j' =>
mok.yearday = clamp_int(
@@ -90,26 +90,26 @@ export fn parse(mok: *mock, layout: str, s: str) (void | errors::invalid) = {
if (mok.hour is void) {
// We can't change the hour's am/pm because we
// have no hour.
- return errors::invalid;
+ return invalid;
};
const rest = strings::iter_str(&s_iter);
if (strings::hasprefix(rest, "AM")) {
if (mok.hour as int > 12) {
// 13 AM?
- return errors::invalid;
+ return invalid;
} else if (mok.hour as int == 12) {
mok.hour = 0;
};
} else if (strings::hasprefix(rest, "PM")) {
if (mok.hour as int > 12) {
// 13 PM?
- return errors::invalid;
+ return invalid;
} else if (mok.hour as int < 12) {
mok.hour =
(mok.hour as int) + 12;
};
} else {
- return errors::invalid;
+ return invalid;
};
strings::next(&s_iter);
strings::next(&s_iter);
@@ -129,7 +129,7 @@ export fn parse(mok: *mock, layout: str, s: str) (void | errors::invalid) = {
yield clamp_int(i, 1, 7);
};
case =>
- return errors::invalid;
+ return invalid;
};
case 'U', 'W' =>
mok.week = clamp_int(
diff --git a/datetime/time.ha b/datetime/time.ha
@@ -20,7 +20,7 @@ fn calc_time_from_hmsn(
min: int,
sec: int,
nsec: int,
-) (time::duration | errors::invalid) = {
+) (time::duration | invalid) = {
const t = (
(hour * time::HOUR) +
(min * time::MINUTE) +
diff --git a/time/chrono/chronology.ha b/time/chrono/chronology.ha
@@ -1,5 +1,8 @@
use time;
+// This date, time, and locality combination is invalid.
+export type invalid = !void;
+
// A date & time, within a locality, intepreted via a chronology
export type moment = struct {
// The ordinal day (on Earth or otherwise)
@@ -26,11 +29,10 @@ export type moment = struct {
export type epochal = i64;
// Creates a new [[moment]]
-//
-// TODO: Check for `time` value bounds overflow. Should we:
-// a. Return an error?
-// b. "Correct" by overflowing? See datetime::new();
-export fn new(date: epochal, time: time::duration, loc: locality) moment = {
+export fn new(date: epochal, time: time::duration, loc: locality) (moment | invalid) = {
+ if (time > loc.daylength) {
+ return invalid;
+ };
const m = moment {
date = date,
time = time,
@@ -46,7 +48,8 @@ export fn from_instant(i: time::instant, loc: locality) moment = {
const daysec = (loc.daylength / time::SECOND);
const d = i.sec / daysec;
const t = (i.sec % daysec) * time::SECOND + i.nsec * time::NANOSECOND;
- return new(d, t, loc);
+ assert(t < loc.daylength, "Internal error: time excedes daylength");
+ return new(d, t, loc)!;
};
// Creates a new [[time::instant]] from a [[moment]]
diff --git a/time/chrono/timezone.ha b/time/chrono/timezone.ha
@@ -64,7 +64,8 @@ type tzname = struct {
// Converts a [[moment]] to one in a different [[locality]]
export fn in(loc: locality, m: moment) moment = {
- return new(m.date, m.time, loc); // resets .zone
+ assert(m.time < loc.daylength, "Internal error: time excedes daylength");
+ return new(m.date, m.time, loc)!; // resets .zone
};
export fn transform(m: moment, zo: time::duration) moment = {