hare

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

commit 0dbc18b1073aada9250a896baf921572ed326979
parent 0013d3531b87f0a556185ae08e7e52fbc6e7c072
Author: Byron Torres <b@torresjrjr.com>
Date:   Thu, 22 Feb 2024 23:44:06 +0000

time::date: virtual: add .vsec, .vnsec

[[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>

Diffstat:
Mtime/date/parse.ha | 37+++++++++++++++++++++++++++++++++----
Mtime/date/virtual.ha | 150+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
2 files changed, 126 insertions(+), 61 deletions(-)

diff --git 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' => @@ -201,9 +205,34 @@ 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 @@ -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; };