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