~sircmpwn/hare-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
11 3

[PATCH hare v2 01/10] time::chrono: rename _lookupzone to lookupzone

Details
Message ID
<20240222234557.97775-1-b@torresjrjr.com>
DKIM signature
pass
Download raw message
Patch: +2 -2
Signed-off-by: Byron Torres <b@torresjrjr.com>
---
 time/chrono/chronology.ha | 2 +-
 time/chrono/timezone.ha   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/time/chrono/chronology.ha b/time/chrono/chronology.ha
index 885eb7c5..8361f599 100644
--- a/time/chrono/chronology.ha
+++ b/time/chrono/chronology.ha
@@ -56,7 +56,7 @@ export fn mzone(m: *moment) zone = {
	case let z: *zone =>
		return *z;
	case null =>
		const z = _lookupzone(m.loc, *(m: *time::instant));
		const z = lookupzone(m.loc, *(m: *time::instant));
		m.zone = z;
		return *z;
	};
diff --git a/time/chrono/timezone.ha b/time/chrono/timezone.ha
index 8371f795..3a1ac096 100644
--- a/time/chrono/timezone.ha
+++ b/time/chrono/timezone.ha
@@ -110,7 +110,7 @@ export fn in(loc: locality, m: moment) (moment | discontinuity) = {
};

// Finds and returns a [[moment]]'s currently observed [[zone]].
fn _lookupzone(loc: locality, inst: time::instant) *zone = {
fn lookupzone(loc: locality, inst: time::instant) *zone = {
	// TODO: https://todo.sr.ht/~sircmpwn/hare/643
	if (len(loc.zones) == 0) {
		abort("time::chrono: Timezone has no zones");
-- 
2.43.2

[PATCH hare v2 05/10] time::chrono: rename path constant exports

Details
Message ID
<20240222234557.97775-5-b@torresjrjr.com>
In-Reply-To
<20240222234557.97775-1-b@torresjrjr.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +13 -13
Rename [[ZONEINFO_PREFIX]] to [[TZDB_PATH]]
as "zoneinfo" is merely a convention.

Rename [[UTC_LEAPSECS_FILE]] to [[UTC_LEAPSECS_PATH]]
for consistency.

Breaking-Change: 0.24.0
Signed-off-by: Byron Torres <b@torresjrjr.com>
---
 time/chrono/+freebsd.ha  | 4 ++--
 time/chrono/+linux.ha    | 4 ++--
 time/chrono/+openbsd.ha  | 4 ++--
 time/chrono/leapsec.ha   | 6 +++---
 time/chrono/timescale.ha | 2 +-
 time/chrono/tzdb.ha      | 6 +++---
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/time/chrono/+freebsd.ha b/time/chrono/+freebsd.ha
index 26d78ab1..af49080e 100644
--- a/time/chrono/+freebsd.ha
+++ b/time/chrono/+freebsd.ha
@@ -2,8 +2,8 @@
// (c) Hare authors <https://harelang.org>

def LOCALTIME_PATH: str = "/etc/localtime";
def ZONEINFO_PREFIX: str = "/usr/share/zoneinfo/";
def TZDB_PATH: str = "/usr/share/zoneinfo/";

// The filepath of the system's "leap-seconds.list" file, which contains UTC/TAI
// leap second data.
export def UTC_LEAPSECS_FILE: str = "/var/db/ntpd.leap-seconds.list";
export def UTC_LEAPSECS_PATH: str = "/var/db/ntpd.leap-seconds.list";
diff --git a/time/chrono/+linux.ha b/time/chrono/+linux.ha
index 600f606c..2756fd6f 100644
--- a/time/chrono/+linux.ha
+++ b/time/chrono/+linux.ha
@@ -2,8 +2,8 @@
// (c) Hare authors <https://harelang.org>

def LOCALTIME_PATH: str = "/etc/localtime";
def ZONEINFO_PREFIX: str = "/usr/share/zoneinfo/";
def TZDB_PATH: str = "/usr/share/zoneinfo/";

// The filepath of the system's "leap-seconds.list" file, which contains UTC/TAI
// leap second data.
export def UTC_LEAPSECS_FILE: str = "/usr/share/zoneinfo/leap-seconds.list";
export def UTC_LEAPSECS_PATH: str = "/usr/share/zoneinfo/leap-seconds.list";
diff --git a/time/chrono/+openbsd.ha b/time/chrono/+openbsd.ha
index 600f606c..2756fd6f 100644
--- a/time/chrono/+openbsd.ha
+++ b/time/chrono/+openbsd.ha
@@ -2,8 +2,8 @@
// (c) Hare authors <https://harelang.org>

def LOCALTIME_PATH: str = "/etc/localtime";
def ZONEINFO_PREFIX: str = "/usr/share/zoneinfo/";
def TZDB_PATH: str = "/usr/share/zoneinfo/";

// The filepath of the system's "leap-seconds.list" file, which contains UTC/TAI
// leap second data.
export def UTC_LEAPSECS_FILE: str = "/usr/share/zoneinfo/leap-seconds.list";
export def UTC_LEAPSECS_PATH: str = "/usr/share/zoneinfo/leap-seconds.list";
diff --git a/time/chrono/leapsec.ha b/time/chrono/leapsec.ha
index dc6fbb82..91a1c7f1 100644
--- a/time/chrono/leapsec.ha
+++ b/time/chrono/leapsec.ha
@@ -38,7 +38,7 @@ type utciniterror = !(fs::error | io::error | utf8::invalid);
export def SECS_1900_1970: i64 = 2208988800;

// UTC/TAI leap second data; UTC timestamps and their offsets from TAI.
// Sourced from [[UTC_LEAPSECS_FILE]].
// Sourced from [[UTC_LEAPSECS_PATH]].
let utc_leapsecs: [](i64, i64) = [];

let utc_isinitialized: bool = false;
@@ -48,12 +48,12 @@ let utc_isinitialized: bool = false;
};

fn init_utc_leapsecs() (void | utciniterror) = {
	const file = os::open(UTC_LEAPSECS_FILE)?;
	const file = os::open(UTC_LEAPSECS_PATH)?;
	defer io::close(file)!;
	parse_utc_leapsecs(file)?;
};

// Parse UTC/TAI leap second data from [[UTC_LEAPSECS_FILE]].
// Parse UTC/TAI leap second data from [[UTC_LEAPSECS_PATH]].
// See file for format details.
fn parse_utc_leapsecs(h: io::handle) (void | utf8::invalid | io::error) = {
	for (true) {
diff --git a/time/chrono/timescale.ha b/time/chrono/timescale.ha
index 71c0007f..ae4aabce 100644
--- a/time/chrono/timescale.ha
+++ b/time/chrono/timescale.ha
@@ -104,7 +104,7 @@ fn tai_conv(ts: *timescale, i: time::instant) ([]time::instant | void) = {
// Discontinuous (has leap seconds).
//
// During a program's initialization, this timescale initializes by loading its
// UTC/TAI leap second data from [[UTC_LEAPSECS_FILE]]; otherwise, fails
// UTC/TAI leap second data from [[UTC_LEAPSECS_PATH]]; otherwise, fails
// silently. If failed, any attempt to consult UTC leapsec data (e.g. calling
// [[convert]] on UTC) causes an abort. This includes [[in]].
export const utc: timescale = timescale {
diff --git a/time/chrono/tzdb.ha b/time/chrono/tzdb.ha
index 437cd2d8..fa50a541 100644
--- a/time/chrono/tzdb.ha
+++ b/time/chrono/tzdb.ha
@@ -22,7 +22,7 @@ export type invalidtzif = !void;
// database (TZDB), and returns it as a [[locality]]. Each call returns a new
// instance. The caller must free the return value; see [[timezone_free]].
//
// The system TZDB is normally located at [[ZONEINFO_PREFIX]]. The timezone
// The system TZDB is normally located at [[TZDB_PATH]]. The timezone
// filepath is resolved by appending the name argument to this prefix path.
// If [name] is a full filepath (begins with '/'), it is used directly instead.
//
@@ -30,8 +30,8 @@ export type invalidtzif = !void;
// [[EARTH_DAY]] day-length.
export fn tz(name: str) (locality | tzdberror) = {
	const filepath =
		if (!strings::hasprefix(name, ZONEINFO_PREFIX))
			path::init(ZONEINFO_PREFIX, name)!
		if (!strings::hasprefix(name, TZDB_PATH))
			path::init(TZDB_PATH, name)!
		else
			path::init(name)!;
	const file = os::open(path::string(&filepath))?;
-- 
2.43.2

[PATCH hare v2 06/10] time::date: rename some calc functions

Details
Message ID
<20240222234557.97775-6-b@torresjrjr.com>
In-Reply-To
<20240222234557.97775-1-b@torresjrjr.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +15 -15
Signed-off-by: Byron Torres <b@torresjrjr.com>
---
 time/date/daydate.ha | 10 +++++-----
 time/date/parithm.ha |  8 ++++----
 time/date/reckon.ha  | 12 ++++++------
 3 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/time/date/daydate.ha b/time/date/daydate.ha
index 982848f5..026cdb11 100644
--- a/time/date/daydate.ha
+++ b/time/date/daydate.ha
@@ -25,16 +25,16 @@ export fn isleapyear(y: int) bool = {
// Calculates whether a given year, month, and day-of-month, is a valid date.
fn is_valid_ymd(y: int, m: int, d: int) bool = {
	return m >= 1 && m <= 12 && d >= 1 &&
		d <= calc_month_daycnt(y, m);
		d <= calc_days_in_month(y, m);
};

// Calculates whether a given year, and day-of-year, is a valid date.
fn is_valid_yd(y: int, yd: int) bool = {
	return yd >= 1 && yd <= calc_year_daycnt(y);
	return yd >= 1 && yd <= calc_days_in_year(y);
};

// Calculates the number of days in the given month of the given year.
fn calc_month_daycnt(y: int, m: int) int = {
fn calc_days_in_month(y: int, m: int) int = {
	const days_per_month: [_]int = [
		31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
	];
@@ -46,7 +46,7 @@ fn calc_month_daycnt(y: int, m: int) int = {
};

// Calculates the number of days in a given year.
fn calc_year_daycnt(y: int) int = {
fn calc_days_in_year(y: int) int = {
	return if (isleapyear(y)) 366 else 365;
};

@@ -244,7 +244,7 @@ fn calc_daydate__ywd(y: int, w: int, wd: int) (i64 | invalid) = {
// Calculates the daydate,
// given a year and day-of-year.
fn calc_daydate__yd(y: int, yd: int) (i64 | invalid) = {
	if (yd < 1 || yd > calc_year_daycnt(y)) {
	if (yd < 1 || yd > calc_days_in_year(y)) {
		return invalid;
	};
	return calc_daydate__ymd(y, 1, 1)? + yd - 1;
diff --git a/time/date/parithm.ha b/time/date/parithm.ha
index 1b7788cc..c91b7890 100644
--- a/time/date/parithm.ha
+++ b/time/date/parithm.ha
@@ -49,21 +49,21 @@ export fn pdiff(a: date, b: date) period = {
	p.days = _day(&b) - _day(&a);
	let year = _year(&b);
	let month = _month(&b);
	let daycnt = calc_month_daycnt(year, month);
	for (_day(&a) > daycnt || p.days < 0) {
	let monthdays = calc_days_in_month(year, month);
	for (_day(&a) > monthdays || p.days < 0) {
		month -= 1;
		if (month == 0) {
			year -= 1;
			month = 12;
		};
		daycnt = calc_month_daycnt(year, month);
		monthdays = calc_days_in_month(year, month);

		p.months -= 1;
		if (p.months < 0) {
			p.years -= 1;
			p.months += 12;
		};
		p.days += daycnt;
		p.days += monthdays;
	};

	p.hours = _hour(&b) - _hour(&a);
diff --git a/time/date/reckon.ha b/time/date/reckon.ha
index e1ddacf0..229d7643 100644
--- a/time/date/reckon.ha
+++ b/time/date/reckon.ha
@@ -151,12 +151,12 @@ fn reckon_days(r: *virtual, days: i64, calc: calculus) void = {
	day += days: int;

	// day overflow
	let month_daycnt = calc_month_daycnt(year, month);
	for (day > month_daycnt) {
	let monthdays = calc_days_in_month(year, month);
	for (day > monthdays) {
		if (calc & calculus::FLOOR != 0) {
			day = 1;
		} else if (calc & calculus::CEIL != 0) {
			day = month_daycnt;
			day = monthdays;
		} else if (calc & calculus::HOP != 0) {
			r.year  = year;
			r.month = month;
@@ -174,9 +174,9 @@ fn reckon_days(r: *virtual, days: i64, calc: calculus) void = {

			year   = r.year  as int;
			month  = r.month as int;
			day   -= month_daycnt;
			day   -= monthdays;
		};
		month_daycnt = calc_month_daycnt(year, month);
		monthdays = calc_days_in_month(year, month);
	};
	for (day < 1) {
		r.year  = year;
@@ -186,7 +186,7 @@ fn reckon_days(r: *virtual, days: i64, calc: calculus) void = {

		year   = r.year  as int;
		month  = r.month as int;
		day   += calc_month_daycnt(year, month);
		day   += calc_days_in_month(year, month);
	};

	r.year  = year;
-- 
2.43.2

[PATCH hare v2 03/10] time::chrono: moment.daytime: i64, not duration

Details
Message ID
<20240222234557.97775-3-b@torresjrjr.com>
In-Reply-To
<20240222234557.97775-1-b@torresjrjr.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +13 -12
Semantic correction.

Signed-off-by: Byron Torres <b@torresjrjr.com>
---
 time/chrono/chronology.ha | 15 ++++++++-------
 time/date/daytime.ha      |  4 ++--
 time/date/virtual.ha      |  6 +++---
 3 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/time/chrono/chronology.ha b/time/chrono/chronology.ha
index 8361f599..ea8f9cfc 100644
--- a/time/chrono/chronology.ha
+++ b/time/chrono/chronology.ha
@@ -33,8 +33,9 @@ export type moment = struct {
	// since an abitrary epoch (e.g. the Unix epoch 1970-01-01).
	daydate: (void | i64),

	// The observed time-of-day (amount of daytime progressed in a day).
	daytime: (void | time::duration),
	// The observed time-of-day (amount of daytime progressed in a day)
	// as nanoseconds.
	daytime: (void | i64),
};

// Creates a new [[moment]]. Uses a given [[time::instant]] with a [[timescale]]
@@ -83,10 +84,10 @@ export fn daydate(m: *moment) i64 = {
};

// Observes a [[moment]]'s observed time-of-day (amount of daytime progressed in
// a day) as a [[time::duration]].
export fn daytime(m: *moment) time::duration = {
// a day) as nanoseconds.
export fn daytime(m: *moment) i64 = {
	match (m.daytime) {
	case let dt: time::duration =>
	case let dt: i64 =>
		return dt;
	case void =>
		const (dd, dt) = calc_datetime(
@@ -119,7 +120,7 @@ export fn from_datetime(
	loc: locality,
	zo: time::duration,
	dd: i64,
	dt: time::duration,
	dt: i64,
) moment = {
	const inst = calc_instant(loc.daylength, zo, dd, dt);
	return moment {
@@ -136,7 +137,7 @@ fn calc_instant(
	day: time::duration, // length of a day
	zo: time::duration,  // zone offset
	dd: i64,             // date since epoch
	dt: time::duration,  // time since start of day
	dt: i64,             // time since start of day (ns)
) time::instant = {
	const daysec = (day / time::SECOND): i64;
	const dayrem = day % time::SECOND;
diff --git a/time/date/daytime.ha b/time/date/daytime.ha
index 493f42de..ee4b2483 100644
--- a/time/date/daytime.ha
+++ b/time/date/daytime.ha
@@ -5,7 +5,7 @@ use time;

// Calculates the wall clock (hour, minute, second, nanosecond),
// given a time-of-day (amount of daytime progressed in a day).
fn calc_hmsn(t: time::duration) (int, int, int, int) = {
fn calc_hmsn(t: i64) (int, int, int, int) = {
	// TODO: Special case for leap seconds, 61st second?
	const hour = (t / time::HOUR): int;
	const min = ((t / time::MINUTE) % 60): int;
@@ -21,7 +21,7 @@ fn calc_daytime__hmsn(
	min: int,
	sec: int,
	nsec: int,
) (time::duration | invalid) = {
) (i64 | invalid) = {
	const t = (
		(hour * time::HOUR) +
		(min * time::MINUTE) +
diff --git a/time/date/virtual.ha b/time/date/virtual.ha
index 651e602c..f330ed26 100644
--- a/time/date/virtual.ha
+++ b/time/date/virtual.ha
@@ -158,7 +158,7 @@ export fn realize(
	};

	// determine .daytime
	if (v.daytime is time::duration) {
	if (v.daytime is i64) {
		void;
	} else {
		const hour = if (v.hour is int) {
@@ -188,7 +188,7 @@ export fn realize(
	};

	// determine zone offset
	if (v.zoff is time::duration) {
	if (v.zoff is i64) {
		void;
	} else {
		return insufficient;
@@ -213,7 +213,7 @@ export fn realize(
		v.loc,
		v.zoff as time::duration,
		v.daydate as i64,
		v.daytime as time::duration,
		v.daytime as i64,
	));

	// verify zone offset
-- 
2.43.2

[PATCH hare v2 04/10] time::chrono: rename mzone to ozone

Details
Message ID
<20240222234557.97775-4-b@torresjrjr.com>
In-Reply-To
<20240222234557.97775-1-b@torresjrjr.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +19 -19
Rename export [[time::chrono::mzone]] to [[time::chrono::ozone]].

Braking-Change: 0.24.0
Signed-off-by: Byron Torres <b@torresjrjr.com>
---

It's "observe zone".

 time/chrono/chronology.ha |  8 ++++----
 time/date/date.ha         |  2 +-
 time/date/format.ha       |  8 ++++----
 time/date/parithm.ha      | 16 ++++++++--------
 time/date/reckon.ha       |  2 +-
 time/date/virtual.ha      |  2 +-
 6 files changed, 19 insertions(+), 19 deletions(-)

diff --git a/time/chrono/chronology.ha b/time/chrono/chronology.ha
index ea8f9cfc..78741b29 100644
--- a/time/chrono/chronology.ha
+++ b/time/chrono/chronology.ha
@@ -17,7 +17,7 @@ export type invalid = !void;
//
// Moments observe a daydate, time-of-day, and [[zone]], which are evaluated,
// cached and obtained with the observer functions [[daydate]], [[daytime]], and
// [[mzone]]. These values are derived from the embedded instant and locality
// [[ozone]]. These values are derived from the embedded instant and locality
// information, and thus are guaranteed to be valid.
export type moment = struct {
	// The embedded [[time::instant]].
@@ -52,7 +52,7 @@ export fn new(loc: locality, i: time::instant) moment = {
};

// Observes a [[moment]]'s observed [[zone]].
export fn mzone(m: *moment) zone = {
export fn ozone(m: *moment) zone = {
	match (m.zone) {
	case let z: *zone =>
		return *z;
@@ -75,7 +75,7 @@ export fn daydate(m: *moment) i64 = {
		return dd;
	case void =>
		const (dd, dt) = calc_datetime(
			m.loc, *(m: *time::instant), mzone(m).zoff,
			m.loc, *(m: *time::instant), ozone(m).zoff,
		);
		m.daytime = dt;
		m.daydate = dd;
@@ -91,7 +91,7 @@ export fn daytime(m: *moment) i64 = {
		return dt;
	case void =>
		const (dd, dt) = calc_datetime(
			m.loc, *(m: *time::instant), mzone(m).zoff,
			m.loc, *(m: *time::instant), ozone(m).zoff,
		);
		m.daytime = dt;
		m.daydate = dd;
diff --git a/time/date/date.ha b/time/date/date.ha
index 87266200..4f46fe1f 100644
--- a/time/date/date.ha
+++ b/time/date/date.ha
@@ -164,7 +164,7 @@ export fn new(

	// check if input values are actually observed
	if (
		zoff != chrono::mzone(&d).zoff
		zoff != chrono::ozone(&d).zoff
		|| _fields[0] != _year(&d)
		|| _fields[1] != _month(&d)
		|| _fields[2] != _day(&d)
diff --git a/time/date/format.ha b/time/date/format.ha
index 25db9c2d..592461b4 100644
--- a/time/date/format.ha
+++ b/time/date/format.ha
@@ -148,15 +148,15 @@ fn fmtout(out: io::handle, r: rune, d: *date) (size | io::error) = {
	case 'Y' =>
		return fmt::fprintf(out, "{:.4}", _year(d));
	case 'z' =>
		const (sign, zo) = if (chrono::mzone(d).zoff >= 0) {
			yield ('+', calc_hmsn(chrono::mzone(d).zoff));
		const (sign, zo) = if (chrono::ozone(d).zoff >= 0) {
			yield ('+', calc_hmsn(chrono::ozone(d).zoff));
		} else {
			yield ('-', calc_hmsn(-chrono::mzone(d).zoff));
			yield ('-', calc_hmsn(-chrono::ozone(d).zoff));
		};
		const (hr, mi) = (zo.0, zo.1);
		return fmt::fprintf(out, "{}{:.2}{:.2}", sign, hr, mi);
	case 'Z' =>
		return fmt::fprint(out, chrono::mzone(d).abbr);
		return fmt::fprint(out, chrono::ozone(d).abbr);
	case '%' =>
		return fmt::fprint(out, "%");
	case =>
diff --git a/time/date/parithm.ha b/time/date/parithm.ha
index 2fb5e6af..1b7788cc 100644
--- a/time/date/parithm.ha
+++ b/time/date/parithm.ha
@@ -131,44 +131,44 @@ export fn truncate(d: date, u: unit) date = {
	// realize(), and then use realize() here.
	return switch (u) {
	case unit::ERA =>
		yield new(d.loc, chrono::mzone(&d).zoff,
		yield new(d.loc, chrono::ozone(&d).zoff,
			1, 1, 1,
			0, 0, 0, 0,
		)!;
	case unit::YEAR =>
		yield new(d.loc, chrono::mzone(&d).zoff,
		yield new(d.loc, chrono::ozone(&d).zoff,
			_year(&d), 1, 1,
			0, 0, 0, 0,
		)!;
	case unit::MONTH =>
		yield new(d.loc, chrono::mzone(&d).zoff,
		yield new(d.loc, chrono::ozone(&d).zoff,
			_year(&d), _month(&d), 1,
			0, 0, 0, 0,
		)!;
	case unit::WEEK =>
		const dd = chrono::daydate(&d) - _weekday(&d);
		const ymd = calc_ymd(dd);
		yield new(d.loc, chrono::mzone(&d).zoff,
		yield new(d.loc, chrono::ozone(&d).zoff,
			ymd.0, ymd.1, ymd.2,
			0, 0, 0, 0,
		)!;
	case unit::DAY =>
		yield new(d.loc, chrono::mzone(&d).zoff,
		yield new(d.loc, chrono::ozone(&d).zoff,
			_year(&d), _month(&d), _day(&d),
			0, 0, 0, 0,
		)!;
	case unit::HOUR =>
		yield new(d.loc, chrono::mzone(&d).zoff,
		yield new(d.loc, chrono::ozone(&d).zoff,
			_year(&d), _month(&d), _day(&d),
			_hour(&d), 0, 0, 0,
		)!;
	case unit::MINUTE =>
		yield new(d.loc, chrono::mzone(&d).zoff,
		yield new(d.loc, chrono::ozone(&d).zoff,
			_year(&d), _month(&d), _day(&d),
			_hour(&d), _minute(&d), 0, 0,
		)!;
	case unit::SECOND =>
		yield new(d.loc, chrono::mzone(&d).zoff,
		yield new(d.loc, chrono::ozone(&d).zoff,
			_year(&d), _month(&d), _day(&d),
			_hour(&d), _minute(&d), _second(&d), 0,
		)!;
diff --git a/time/date/reckon.ha b/time/date/reckon.ha
index 77ac0720..e1ddacf0 100644
--- a/time/date/reckon.ha
+++ b/time/date/reckon.ha
@@ -70,7 +70,7 @@ export type calculus = enum uint {
export fn reckon(d: date, calc: calculus, ps: period...) date = {
	let r = newvirtual(); // our reckoner
	r.vloc       = d.loc;
	r.zoff       = chrono::mzone(&d).zoff;
	r.zoff       = chrono::ozone(&d).zoff;
	r.year       = _year(&d);
	r.month      = _month(&d);
	r.day        = _day(&d);
diff --git a/time/date/virtual.ha b/time/date/virtual.ha
index f330ed26..d87eabca 100644
--- a/time/date/virtual.ha
+++ b/time/date/virtual.ha
@@ -217,7 +217,7 @@ export fn realize(
	));

	// verify zone offset
	const z = chrono::mzone(&d);
	const z = chrono::ozone(&d);
	if (z.zoff != v.zoff as time::duration) {
		return invalid;
	};
-- 
2.43.2

[PATCH hare v2 02/10] time::chrono: improve LOCAL, tz()

Details
Message ID
<20240222234557.97775-2-b@torresjrjr.com>
In-Reply-To
<20240222234557.97775-1-b@torresjrjr.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +35 -65
Make [[LOCAL]] default to [[UTC]] proper.

Accept the POSIX $TZ environment variable form with a ':' prefix.

Allow [[tz]] to accept full filepaths as in POSIX.

Signed-off-by: Byron Torres <b@torresjrjr.com>
---
 hare/module/format.ha   |  2 +-
 time/chrono/timezone.ha | 78 +++++++++++------------------------------
 time/chrono/tzdb.ha     | 20 +++++++----
 3 files changed, 35 insertions(+), 65 deletions(-)

diff --git a/hare/module/format.ha b/hare/module/format.ha
index 8c54f3c7..a8788f75 100644
--- a/hare/module/format.ha
+++ b/hare/module/format.ha
@@ -35,7 +35,7 @@ export fn format_srcset(out: io::handle, srcs: *srcset) (size | io::error) = {
	n += fmt::fprint(out, "relevant tags: ")?;
	n += format_tags(out, srcs.seentags)?;
	n += fmt::fprintln(out)?;
	const dt = date::from_instant(time::chrono::LOCAL, srcs.mtime);
	const dt = date::from_instant(chrono::LOCAL, srcs.mtime);
	n += date::format(out, "last change to source list: %F %T\n", &dt)?;
	n += fmt::fprintln(out, "hare sources:")?;
	for (let i = 0z; i < len(srcs.ha); i += 1) {
diff --git a/time/chrono/timezone.ha b/time/chrono/timezone.ha
index 3a1ac096..97bd5e4d 100644
--- a/time/chrono/timezone.ha
+++ b/time/chrono/timezone.ha
@@ -181,77 +181,41 @@ export fn fixedzone(ts: *timescale, daylen: time::duration, z: zone) timezone =
	};
};

// The system's [[locality]]; the system's local [[timezone]].
// The local [[locality]]; the system or environment configured [[timezone]].
//
// This is set during a program's initialisation, where the TZ environment
// variable is tried, otherwise the /etc/localtime file is tried, otherwise a
// default is used.
//
// The default timezone is equivalent to that of [[UTC]], with "Local" being the
// name of both the timezone and its single zero-offset zone.
export const LOCAL: locality = &TZ_LOCAL;

def TZ_LOCAL_NAME: str = "Local";

let TZ_LOCAL: timezone = timezone {
	name = TZ_LOCAL_NAME,
	timescale = &utc,
	daylength = EARTH_DAY,
	zones = [
		zone {
			zoff = 0 * time::SECOND,
			name = TZ_LOCAL_NAME,
			abbr = "",
			dst = false,
		},
	],
	transitions = [],
	posix_extend = "",
};
// variable is tried, otherwise the /etc/localtime file is tried, otherwise it
// defaults  to [[UTC]].
export const LOCAL: locality = &TZ_UTC;

@init fn init_tz_local() void = {
	match (os::getenv("TZ")) {
	case let timezone: str =>
		match (tz(timezone)) {
		case let loc: locality =>
			TZ_LOCAL = *loc;
		case =>
			return;
	let path = match (os::getenv("TZ")) {
	case let path: str =>
		// remove POSIX prefix ':'
		yield if (strings::hasprefix(path, ':')) {
			yield strings::sub(path, 1, strings::end);
		} else {
			yield path;
		};
	case void =>
		const filepath = match (os::readlink(LOCALTIME_PATH)) {
		case let fp: str =>
			yield fp;
		case =>
			yield LOCALTIME_PATH;
		};

		const file = match (os::open(filepath)) {
		case let f: io::file =>
			yield f;
		yield match (os::realpath(LOCALTIME_PATH)) {
		case let path: str =>
			yield path;
		case =>
			return;
		};
		defer io::close(file)!;

		if (strings::hasprefix(filepath, ZONEINFO_PREFIX)) {
			TZ_LOCAL.name = strings::trimprefix(
				filepath, ZONEINFO_PREFIX,
			);
		};
	};

		static let buf: [os::BUFSZ]u8 = [0...];
		const file = bufio::init(file, buf, []);
		load_tzif(&file, &TZ_LOCAL): void;
	match (tz(path)) {
	case => void;
	case let loc: locality =>
		LOCAL = loc;
	};
};

@fini fn free_tz_local() void = {
	free(TZ_LOCAL.transitions);
	switch(TZ_LOCAL.name) {
	case TZ_LOCAL_NAME => void;
	case =>
		free(TZ_LOCAL.zones);
	if (LOCAL != UTC) {
		timezone_free(LOCAL);
	};
};

diff --git a/time/chrono/tzdb.ha b/time/chrono/tzdb.ha
index b27edf43..437cd2d8 100644
--- a/time/chrono/tzdb.ha
+++ b/time/chrono/tzdb.ha
@@ -19,16 +19,22 @@ export type tzdberror = !(invalidtzif | fs::error | io::error);
export type invalidtzif = !void;

// Finds, loads, and allocates a [[timezone]] from the system's Timezone
// database, normally located at /usr/share/zoneinfo, and returns it as a
// [[locality]]. Each call returns a new instance. The caller must free the
// return value.
// database (TZDB), and returns it as a [[locality]]. Each call returns a new
// instance. The caller must free the return value; see [[timezone_free]].
//
// All localities provided default to the [[utc]] [[timescale]] and
// The system TZDB is normally located at [[ZONEINFO_PREFIX]]. The timezone
// filepath is resolved by appending the name argument to this prefix path.
// If [name] is a full filepath (begins with '/'), it is used directly instead.
//
// All localities returned default to the [[utc]] [[timescale]] and
// [[EARTH_DAY]] day-length.
export fn tz(name: str) (locality | tzdberror) = {
	const filepath = path::init(ZONEINFO_PREFIX, name)!;
	const fpath = path::string(&filepath);
	const file = os::open(fpath)?;
	const filepath =
		if (!strings::hasprefix(name, ZONEINFO_PREFIX))
			path::init(ZONEINFO_PREFIX, name)!
		else
			path::init(name)!;
	const file = os::open(path::string(&filepath))?;

	static let buf: [os::BUFSZ]u8 = [0...];
	const bufstrm = bufio::init(file, buf, []);
-- 
2.43.2

[PATCH hare v2 10/10] time::date: virtual: add .vsec, .vnsec

Details
Message ID
<20240222234557.97775-10-b@torresjrjr.com>
In-Reply-To
<20240222234557.97775-1-b@torresjrjr.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +126 -61
[[virtual]] has two new fields:

* .vsec "virtual's second since epoch"
* .vnsec "virtual's nanosecond"

[[parse]] now handles %s, and %N additionally assigns to .vnsec.

[[realize]] now accounts for .vsec and .vnsec, and has improved
documentation.

This change makes parsing dates using the [[QUARTZ]], [[QUARTZZOFF]],
and [[QUARTZLOC]] layouts useful.

    date::parse(&v, date::QUARTZLOC, "2147483647.0:Europe/Amsterdam");

Signed-off-by: Byron Torres <b@torresjrjr.com>
---
 time/date/parse.ha   |  37 +++++++++--
 time/date/virtual.ha | 150 +++++++++++++++++++++++++++----------------
 2 files changed, 126 insertions(+), 61 deletions(-)

diff --git a/time/date/parse.ha b/time/date/parse.ha
index cda8adfd..8c198073 100644
--- a/time/date/parse.ha
+++ b/time/date/parse.ha
@@ -102,9 +102,13 @@ fn parse_specifier(
	case 'M' =>
		v.minute = scan_int(iter, 2)?;
	case 'N' =>
		v.nanosecond = scan_decimal(iter, 9)?;
		let nsec = scan_decimal(iter, 9)?;
		v.nanosecond = nsec: int;
		v.vnsec = nsec;
	case 'p' => // AM=false PM=true
		v.ampm = scan_for(iter, "AM", "PM", "am", "pm")? % 2 == 1;
	case 's' =>
		v.vsec = scan_num(iter, 20)?;
	case 'S' =>
		v.second = scan_int(iter, 2)?;
	case 'T' =>
@@ -200,10 +204,35 @@ fn scan_int(iter: *strings::iterator, maxrunes: size) (int | failure) = {
	};
};

// Scans the iterator for consecutive numeric digits.
// Left-padded whitespace and zeros are permitted.
// Returns the resulting i64.
fn scan_num(iter: *strings::iterator, maxrunes: size) (i64 | failure) = {
	let start = *iter;
	for (let i = 0z; i < maxrunes; i += 1) {
		match (strings::next(iter)) {
		case void =>
			return failure;
		case let rn: rune =>
			if (!ascii::isdigit(rn)) {
				strings::prev(iter);
				break;
			};
		};
	};

	match (strconv::stoi64(strings::slice(&start, iter))) {
	case let num: i64 =>
		return num;
	case =>
		return failure;
	};
};

// Scans the iterator for consecutive numeric digits.
// Left-padded whitespace and zeros are NOT permitted.
// The resulting decimal is right-padded with zeros.
fn scan_decimal(iter: *strings::iterator, maxrunes: size) (int | failure) = {
fn scan_decimal(iter: *strings::iterator, maxrunes: size) (i64 | failure) = {
	let start = *iter;
	for (let i = 0z; i < maxrunes; i += 1) {
		let rn: rune = match (strings::next(iter)) {
@@ -218,8 +247,8 @@ fn scan_decimal(iter: *strings::iterator, maxrunes: size) (int | failure) = {
		};
	};
	const s = strings::slice(&start, iter);
	match (strconv::stoi(s)) {
	case let num: int =>
	match (strconv::stoi64(s)) {
	case let num: i64 =>
		for (let i = 0z; i < maxrunes - len(s); i += 1) {
			num *= 10;
		};
diff --git a/time/date/virtual.ha b/time/date/virtual.ha
index 2ab1a4df..87df4ecf 100644
--- a/time/date/virtual.ha
+++ b/time/date/virtual.ha
@@ -39,6 +39,10 @@ export type lack = enum u8 {
//
export type virtual = struct {
	date,
	// virtual's timescalar second
	vsec:     (void | i64),
	// virtual's nanosecond of timescalar second
	vnsec:    (void | i64),
	// virtual's locality
	vloc:     (void | chrono::locality),
	// locality name
@@ -78,6 +82,8 @@ export fn newvirtual() virtual = virtual {
	second      = void,
	nanosecond  = void,

	vsec        = void,
	vnsec       = void,
	vloc        = void,
	locname     = void,
	zoff        = void,
@@ -87,45 +93,96 @@ export fn newvirtual() virtual = virtual {
};

// Realizes a valid [[date]] from a [[virtual]], or fails appropriately.
// Four values require determination. Each has various determination strategies,
// each of which use a certain set of non-void fields from the given virtual.
// The following determination strategies will be attempted in order.
//
// Field sets for determining the daydate:
// The virtual must hold enough valid date information to be able to calculate
// values for the resulting date. A valid combination of its fields must be
// "filled-in" (hold numerical, non-void values). For example:
//
// 1. daydate
// 2. year, month, day
// 3. year, yearday
// 4. year, week, weekday
// 5. isoweekyear, isoweek, weekday
// 	let v = date::newvirtual();
// 	v.locname = "Europe/Amsterdam";
// 	v.zoff = 1 * time::HOUR;
// 	date::parse(&v, // fills-in .year .month .day
// 		"Date: %Y-%m-%d", "Date: 2038-01-19")!;
// 	v.hour = 4;
// 	v.minute = 14;
// 	v.second = 7;
// 	v.nanosecond = 0;
// 	let d = date::realize(v, time::chrono::tz("Europe/Amsterdam")!)!;
//
// This function consults the fields of the given virtual using a predictable
// procedure. In calculating a date, it attempts to calculate values for empty
// fields using sets of other filled-in fields (dependencies), in a order
// described below.
//
// The resultant date depends on a locality and instant.
//
// The locality ([[time::chrono::locality]]) depends on:
//
// - .vloc
// - .locname : This is compared to the .name field of each locality
//   provided via the locs parameter, or "UTC" if none are provided.
//   The first matching locality is used.
// - (nothing) : Defaults to [[time::chrono::UTC]].
//
// Field sets for determining the time-of-day:
// The instant ([[time::instant]]) depends on:
//
// 1. daytime
// 2. hour, minute, second, nanosecond
// - .vsec, .vnsec
// - .daydate, .daytime, .zoff
//
// Field sets for determining the zone offset:
// An empty .daydate depends on:
//
// 1. zoff
// - .year, .month, .day
// - .year, .yearday
// - .year, .week, .weekday
// - .isoweekyear, .isoweek, .weekday
//
// Field sets for determining the [[time::chrono::locality]]:
// An empty .daytime depends on:
//
// 1. vloc
// 2. locname
//         This is compared to each provided locality's 'name' field,
//         or "UTC" if none are provided. The first match is used.
// 3. (none)
//         Defaults to [[time::chrono::UTC]].
// - .hour, .minute, .second, .nanosecond
//
// If for any of these values no determination strategy could be attempted,
// [[insufficient]] is returned. If the resultant date is invalid,
// [[invalid]] is returned.
// If none of the possible combinations of fields were filled-in,
// [[insufficient]] is returned. If after calculation the resultant date is
// invalid, [[invalid]] is returned.
export fn realize(
	v: virtual,
	locs: chrono::locality...
) (date | insufficient | invalid) = {
	let lacking = 0u8;

	// determine .loc (defaults to time::chrono::UTC)
	if (v.vloc is chrono::locality) {
		v.loc = v.vloc as chrono::locality;
	} else if (v.locname is str) {
		v.loc = chrono::UTC;
		for (let i = 0z; i < len(locs); i += 1) {
			const loc = locs[i];
			if (loc.name == v.locname as str) {
				v.loc = loc;
				break;
			};
		};
	};

	// try using .vsec .vnsec
	if (v.vsec is i64 && v.vnsec is i64) {
		return from_instant(
			v.loc,
			time::instant{
				sec = v.vsec as i64,
				nsec = v.vnsec as i64,
			},
		);
	};

	// try using .daydate, .daytime, .zoff

	// determine zone offset
	if (v.zoff is i64) {
		void;
	} else {
		lacking |= insufficient::ZOFF;
	};

	// determine .daydate
	if (v.daydate is i64) {
		void;
@@ -168,17 +225,18 @@ export fn realize(
	// determine .daytime
	if (v.daytime is i64) {
		void;
	} else :nodaytime {
		const hour = if (v.hour is int) {
			yield v.hour as int;
		} else if (v.halfhour is int && v.ampm is bool) {
			const hr = v.halfhour as int;
			const pm = v.ampm as bool;
			yield if (pm) hr * 2 else hr;
		} else {
			lacking |= insufficient::DAYTIME;
			yield :nodaytime;
		};
	} else :daytime {
		const hour =
			if (v.hour is int) {
				yield v.hour as int;
			} else if (v.halfhour is int && v.ampm is bool) {
				const hr = v.halfhour as int;
				const pm = v.ampm as bool;
				yield if (pm) hr * 2 else hr;
			} else {
				lacking |= insufficient::DAYTIME;
				yield :daytime;
			};

		if (
			v.minute is int &&
@@ -191,33 +249,11 @@ export fn realize(
				v.second as int,
				v.nanosecond as int,
			)?;
			lacking |= 0u8;
		} else {
			lacking |= insufficient::DAYTIME;
		};
	};

	// determine zone offset
	if (v.zoff is i64) {
		void;
	} else {
		lacking |= insufficient::ZOFF;
	};

	// determine .loc (defaults to time::chrono::UTC)
	if (v.vloc is chrono::locality) {
		v.loc = v.vloc as chrono::locality;
	} else if (v.locname is str) {
		v.loc = chrono::UTC;
		for (let i = 0z; i < len(locs); i += 1) {
			const loc = locs[i];
			if (loc.name == v.locname as str) {
				v.loc = loc;
				break;
			};
		};
	};

	if (lacking != 0u8) {
		return lacking: insufficient;
	};
-- 
2.43.2

[PATCH hare v2 09/10] time::date: change, add layout constants

Details
Message ID
<20240222234557.97775-9-b@torresjrjr.com>
In-Reply-To
<20240222234557.97775-1-b@torresjrjr.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +33 -12
Rename layout [[EMAILZ]] to [[EMAILZONE]].
Rename layout [[STAMP_NANO]] to [[STAMPNANO]].
Rename layout [[STAMP_ZOFF]] to [[STAMPZOFF]].
Rename layout [[STAMP_ZONE]] to [[STAMPZONE]].
Rename layout [[STAMP_NOZL]] to [[STAMPLOC]].

New layouts with examples:

[[JOURNAL]]
        "2038 Jan 19, Tue 04:14:07 +0100 CET Europe/Amsterdam"
[[WRIST]]
        "Jan-19 Tue 04:14 CET"
[[QUARTZ]]
        "2147480047.012700000"
[[QUARTZZOFF]]
        "2147480047.012700000+0100"
[[QUARTZLOC]]
        "2147480047.012700000:Europe/Amsterdam"

Breaking-Change: 0.24.0
Signed-off-by: Byron Torres <b@torresjrjr.com>
---
 encoding/asn1/+test/decoder_test.ha |  2 +-
 time/date/date.ha                   |  4 +--
 time/date/format.ha                 | 39 ++++++++++++++++++++++-------
 3 files changed, 33 insertions(+), 12 deletions(-)

diff --git a/encoding/asn1/+test/decoder_test.ha b/encoding/asn1/+test/decoder_test.ha
index cb32d7c3..ccb3a279 100644
--- a/encoding/asn1/+test/decoder_test.ha
+++ b/encoding/asn1/+test/decoder_test.ha
@@ -289,7 +289,7 @@ fn newdatetime(s: str, tag: utag) []u8 = {

	let derdatetime = newdatetime("20231030133710.1Z", utag::GENERALIZED_TIME);
	let dt = read_gtime(&d(derdatetime))!;
	assert(date::bsformat(fbuf, date::STAMP_NANO, &dt)!
	assert(date::bsformat(fbuf, date::STAMPNANO, &dt)!
		== "2023-10-30 13:37:10.100000000");

	// must end with Z
diff --git a/time/date/date.ha b/time/date/date.ha
index ded5c818..2bacca22 100644
--- a/time/date/date.ha
+++ b/time/date/date.ha
@@ -214,7 +214,7 @@ export fn from_instant(loc: chrono::locality, i: time::instant) date = {
// provided, they default to 0.
//
// 	let new = date::from_str(
// 		date::STAMP_NOZL,
// 		date::STAMPLOC,
// 		"2019-12-27 22:07:08.000000000 +0100 CET Europe/Amsterdam",
// 		locs...
// 	)!;
@@ -240,7 +240,7 @@ export fn from_str(

@test fn from_str() void = {
	let testcases: [_](str, str, []chrono::locality, (date | error)) = [
		(STAMP_NOZL, "2001-02-03 15:16:17.123456789 +0000 UTC UTC", [],
		(STAMPLOC, "2001-02-03 15:16:17.123456789 +0000 UTC UTC", [],
			new(chrono::UTC, 0, 2001, 2, 3, 15, 16, 17, 123456789)!),
		(STAMP, "2001-02-03 15:16:17", [],
			new(chrono::UTC, 0, 2001, 2, 3, 15, 16, 17)!),
diff --git a/time/date/format.ha b/time/date/format.ha
index 592461b4..b86da76e 100644
--- a/time/date/format.ha
+++ b/time/date/format.ha
@@ -14,7 +14,7 @@ export def EMAIL: str = "%a, %d %b %Y %H:%M:%S %z";

// [[format]] layout for the email date format, with zone offset and
// zone abbreviation.
export def EMAILZ: str = "%a, %d %b %Y %H:%M:%S %z %Z";
export def EMAILZONE: str = "%a, %d %b %Y %H:%M:%S %z %Z";

// [[format]] layout for the POSIX locale's default date & time representation.
export def POSIX: str = "%a %b %e %H:%M:%S %Y";
@@ -22,19 +22,40 @@ export def POSIX: str = "%a %b %e %H:%M:%S %Y";
// [[format]] layout compatible with RFC 3339.
export def RFC3339: str = "%Y-%m-%dT%H:%M:%S%z";

// [[format]] layout for a simple timestamp.
// [[format]] layout for a standard, collatable timestamp.
export def STAMP: str = "%Y-%m-%d %H:%M:%S";

// [[format]] layout for a simple timestamp with nanoseconds.
export def STAMP_NANO: str = "%Y-%m-%d %H:%M:%S.%N";
// [[format]] layout for a standard, collatable timestamp with nanoseconds.
export def STAMPNANO: str = "%Y-%m-%d %H:%M:%S.%N";

// [[format]] layout for a simple timestamp with nanoseconds and zone
// offset.
export def STAMP_ZOFF: str = "%Y-%m-%d %H:%M:%S.%N %z";
// [[format]] layout for a standard, collatable timestamp with nanoseconds
// and zone offset.
export def STAMPZOFF: str = "%Y-%m-%d %H:%M:%S.%N %z";

// [[format]] layout for a simple timestamp with nanoseconds,
// [[format]] layout for a standard, collatable timestamp with nanoseconds,
// zone offset, and zone abbreviation.
export def STAMPZONE: str = "%Y-%m-%d %H:%M:%S.%N %z %Z";

// [[format]] layout for a standard, collatable timestamp with nanoseconds,
// zone offset, zone abbreviation, and locality.
export def STAMP_NOZL: str = "%Y-%m-%d %H:%M:%S.%N %z %Z %L";
export def STAMPLOC: str = "%Y-%m-%d %H:%M:%S.%N %z %Z %L";

// [[format]] layout for a friendly, comprehensive, serializable datetime.
export def JOURNAL: str = "%Y %b %d, %a %H:%M:%S %z %Z %L";

// [[format]] layout for a friendly, terse, glanceable datetime.
export def WRIST: str = "%b-%d %a %H:%M %Z";

// [[format]] layout for a precise timescalar second and nanosecond.
export def QUARTZ: str = "%s.%N";

// [[format]] layout for a precise timescalar second, nanosecond,
// and zone offset.
export def QUARTZZOFF: str = "%s.%N%z";

// [[format]] layout for a precise timescalar second, nanosecond,
// and locality.
export def QUARTZLOC: str = "%s.%N:%L";

def WEEKDAYS: [_]str = [
	"Monday",
-- 
2.43.2

[PATCH hare v2 07/10] time::date: make type insufficient an enum

Details
Message ID
<20240222234557.97775-7-b@torresjrjr.com>
In-Reply-To
<20240222234557.97775-1-b@torresjrjr.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +32 -8
Signed-off-by: Byron Torres <b@torresjrjr.com>
---
 time/date/error.ha   | 14 ++++++++++++--
 time/date/virtual.ha | 26 ++++++++++++++++++++------
 2 files changed, 32 insertions(+), 8 deletions(-)

diff --git a/time/date/error.ha b/time/date/error.ha
index 0da7cfff..9cc8339c 100644
--- a/time/date/error.ha
+++ b/time/date/error.ha
@@ -2,6 +2,7 @@
// (c) Hare authors <https://harelang.org>

use fmt;
use strings;

// All possible errors returned from [[date]].
export type error = !(insufficient | invalid | parsefail);
@@ -10,8 +11,17 @@ export type error = !(insufficient | invalid | parsefail);
// statically allocated.
export fn strerror(err: error) const str = {
	match (err) {
	case insufficient =>
		return "Insufficient date information";
	case let lack: insufficient =>
		static let buf: [92]u8 = [0...];
		return strings::rtrim(fmt::bsprint(buf,
			"Insufficient date information, could not calculate:",
			if (lack & insufficient::DAYDATE: u8 == 0) "" else
				"daydate",
			if (lack & insufficient::DAYTIME: u8 == 0) "" else
				"time-of-day",
			if (lack & insufficient::ZOFF: u8 == 0) "" else
				"zone-offset",
		));
	case invalid =>
		return "Invalid date information";
	case let rn: parsefail =>
diff --git a/time/date/virtual.ha b/time/date/virtual.ha
index d87eabca..2ab1a4df 100644
--- a/time/date/virtual.ha
+++ b/time/date/virtual.ha
@@ -5,7 +5,13 @@ use time;
use time::chrono;

// A [[virtual]] does not have enough information to create a valid [[date]].
export type insufficient = !void;
export type insufficient = !lack; // TODO: drop alias workaround

export type lack = enum u8 {
	DAYDATE = 1 << 0, // could not infer daydate
	DAYTIME = 1 << 1, // could not infer time-of-day
	ZOFF = 1 << 2,    // could not infer zone offset
};

// A virtual date; a [[date]] wrapper interface, which represents a date of
// uncertain validity. Its fields need not be valid observed chronological
@@ -118,6 +124,8 @@ export fn realize(
	v: virtual,
	locs: chrono::locality...
) (date | insufficient | invalid) = {
	let lacking = 0u8;

	// determine .daydate
	if (v.daydate is i64) {
		void;
@@ -154,13 +162,13 @@ export fn realize(
		void;
	} else {
		// cannot deduce daydate
		return insufficient;
		lacking |= insufficient::DAYDATE;
	};

	// determine .daytime
	if (v.daytime is i64) {
		void;
	} else {
	} else :nodaytime {
		const hour = if (v.hour is int) {
			yield v.hour as int;
		} else if (v.halfhour is int && v.ampm is bool) {
@@ -168,7 +176,8 @@ export fn realize(
			const pm = v.ampm as bool;
			yield if (pm) hr * 2 else hr;
		} else {
			return insufficient;
			lacking |= insufficient::DAYTIME;
			yield :nodaytime;
		};

		if (
@@ -182,8 +191,9 @@ export fn realize(
				v.second as int,
				v.nanosecond as int,
			)?;
			lacking |= 0u8;
		} else {
			return insufficient;
			lacking |= insufficient::DAYTIME;
		};
	};

@@ -191,7 +201,7 @@ export fn realize(
	if (v.zoff is i64) {
		void;
	} else {
		return insufficient;
		lacking |= insufficient::ZOFF;
	};

	// determine .loc (defaults to time::chrono::UTC)
@@ -208,6 +218,10 @@ export fn realize(
		};
	};

	if (lacking != 0u8) {
		return lacking: insufficient;
	};

	// determine .sec, .nsec
	const d = from_moment(chrono::from_datetime(
		v.loc,
-- 
2.43.2

[PATCH hare v2 08/10] time::date: change type parsefail, add byteindex

Details
Message ID
<20240222234557.97775-8-b@torresjrjr.com>
In-Reply-To
<20240222234557.97775-1-b@torresjrjr.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +13 -15
Breaking-Change: 0.24.0
Signed-off-by: Byron Torres <b@torresjrjr.com>
---
 time/date/date.ha  |  4 ++--
 time/date/error.ha | 12 +++++-------
 time/date/parse.ha | 12 ++++++------
 3 files changed, 13 insertions(+), 15 deletions(-)

diff --git a/time/date/date.ha b/time/date/date.ha
index 4f46fe1f..ded5c818 100644
--- a/time/date/date.ha
+++ b/time/date/date.ha
@@ -261,8 +261,8 @@ export fn from_str(
		//("%FT%T%z %L", "2009-06-30T18:30:00+0200 Europe/Amsterdam", [amst],
		//	new(amst, 2 * time::HOUR, 2009, 6, 30, 18, 30)!),

		("%Y", "a", [], 'a': parsefail),
		("%X", "2008", [], '2': parsefail),
		("%Y", "a", [], (0z, 'a'): parsefail),
		("%X", "2008", [], (0z, '2'): parsefail),
	];

	let buf: [64]u8 = [0...];
diff --git a/time/date/error.ha b/time/date/error.ha
index 9cc8339c..8e7641b5 100644
--- a/time/date/error.ha
+++ b/time/date/error.ha
@@ -24,12 +24,10 @@ export fn strerror(err: error) const str = {
		));
	case invalid =>
		return "Invalid date information";
	case let rn: parsefail =>
		if (rn == '\0') {
			return "Date parsing error";
		};
		def FMTMSG = "Invalid date format for specifier '{}'";
		static let buf: [len(FMTMSG) + 2]u8 = [0...];
		return fmt::bsprintf(buf, FMTMSG, rn);
	case let pf: parsefail =>
		const (bi, rn) = pf;
		def FMTMSG = "Date parsing failure for layout rune '{}' at byteindex {}";
		static let buf: [len(FMTMSG) + 3]u8 = [0...];
		return fmt::bsprintf(buf, FMTMSG, rn, bi);
	};
};
diff --git a/time/date/parse.ha b/time/date/parse.ha
index 4edbae45..cda8adfd 100644
--- a/time/date/parse.ha
+++ b/time/date/parse.ha
@@ -10,9 +10,9 @@ use time::chrono;

type failure = !void;

// A parsing error occurred. If appropriate, the offending format specifier is
// stored. A null rune represents all other error cases.
export type parsefail = !rune;
// A parsing error occurred. This shall contain a byteindex of and rune from the
// layout at the position where the parsing failure occured.
export type parsefail = !(size, rune);

// Parses a date/time string into a [[virtual]], according to a layout format
// string with specifiers as documented under [[format]]. Partial, sequential,
@@ -45,12 +45,12 @@ export fn parse(v: *virtual, layout: str, s: str) (void | parsefail) = {
		if (!escaped) {
			const sr = match (strings::next(&siter)) {
			case void =>
				return '\x00';
				return (liter.dec.offs, lr);
			case let sr: rune =>
				yield sr;
			};
			if (sr != lr) {
				return '\x00';
				return (liter.dec.offs, lr);
			};
			continue;
		};
@@ -60,7 +60,7 @@ export fn parse(v: *virtual, layout: str, s: str) (void | parsefail) = {
		match (parse_specifier(v, &siter, lr)) {
		case void => void;
		case failure =>
			return lr;
			return (liter.dec.offs, lr);
		};
	};

-- 
2.43.2

[hare/patches] build success

builds.sr.ht <builds@sr.ht>
Details
Message ID
<CZC0C58KMPU0.IEUYGKE8V5PZ@fra02>
In-Reply-To
<20240222234557.97775-8-b@torresjrjr.com> (view parent)
DKIM signature
missing
Download raw message
hare/patches: SUCCESS in 1m9s

[time::chrono: rename _lookupzone to lookupzone][0] v2 from [Byron Torres][1]

[0]: https://lists.sr.ht/~sircmpwn/hare-dev/patches/49755
[1]: b@torresjrjr.com

✓ #1155871 SUCCESS hare/patches/freebsd.yml https://builds.sr.ht/~sircmpwn/job/1155871
✓ #1155870 SUCCESS hare/patches/alpine.yml  https://builds.sr.ht/~sircmpwn/job/1155870
✓ #1155872 SUCCESS hare/patches/openbsd.yml https://builds.sr.ht/~sircmpwn/job/1155872

Re: [PATCH hare v2 10/10] time::date: virtual: add .vsec, .vnsec

Details
Message ID
<CZE4798XV7RE.3P93FCT8Q48BZ@cmpwn.com>
In-Reply-To
<20240222234557.97775-10-b@torresjrjr.com> (view parent)
DKIM signature
pass
Download raw message
Thanks!

To git@git.sr.ht:~sircmpwn/hare
   8f42a75a..0dbc18b1  master -> master
Reply to thread Export thread (mbox)