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:
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;
};