[PATCH hare v2] time::date: parse %y, %C
Export this patch
Signed-off-by: Curtis Arthaud <uku82@gmx.fr>
Hey. So the patch has some issues, but I edited and merged it to my
branch with your authorship. Take a look:
https://git.sr.ht/~torresjrjr/hare/commit/387aabeb426fd49ad135638ac7344e787563d1b7
I'll still write a review.
---
Sending my patch here rebased to your time branch.
(Is it better to send patches to the relevant external tree when it exists
like this one?)
I think so.
If you need a program that uses zflags + %y (+ os::chtimes from my unmerged
patch in the main mailing list) in practice, this is touch.ha (it's meant
for hautils but Drew has closed his mailing list :eyerolls:).
You can send hautils patches to hare-dev.
Your design works well, my only feedback is that documentation is at times very
difficult to understand (though much of that is probably that the subject *is*
difficult when treated correctly)
Yep. I'll write a tutorial on harelang.org probably after I merge
zflags.
```
use errors;
use fmt;
use fs;
use getopt;
use io;
use os;
use strings;
use time;
use time::chrono;
use time::date;
type flags = struct {
chatime: bool,
chmtime: bool,
nocreate: bool,
provided: bool,
atime: (time::instant | void),
mtime: (time::instant | void)
};
export fn main() void = {
const help: []getopt::help = [
"change file access and modification times",
('a', "change the access time of file. Do not change the"
"modification time unless -m is also specified."),
('c', "do not create a specified file if it does not exist."
"Do not write any diagnostic messages concerning"
"this condition."),
('m', "change the modification time of file. Do not change"
"the access time unless -a is also specified."),
('d', "datetime", "use a time of format %Y-%m-%d(T| )%H:%M:%S[(.|,)%N][Z] instead of the current time."),
('r', "reffile", "use the time of a reference file instead of the current time."),
('t', "time", "use a time of format [(%y|%Y)]%m%d%H%M[.%S] instead of the current time."),
"[file...]"
];
const cmd = getopt::parse(os::args, help...);
defer getopt::finish(&cmd);
let flg = flags{ atime = void, mtime = void, ... };
for (const (opt, optarg) .. cmd.opts) {
switch (opt) {
case 'a' =>
flg.chatime = true;
case 'c' =>
flg.nocreate = true;
case 'm' =>
flg.chmtime = true;
case 'd' =>
flg.provided = true;
flg.atime = match (parse_timed(optarg)) {
case let t: time::instant => yield t;
case let err: date::error =>
fmt::fatalf("couldn't parse datetime: {}",
date::strerror(err));
};
flg.mtime = flg.atime;
case 'r' =>
flg.provided = true;
match (os::stat(optarg)) {
case let st: fs::filestat =>
flg.atime = st.atime;
flg.mtime = st.mtime;
case let err: fs::error =>
fmt::fatalf("couldn't retrieve time from {}: {}",
optarg, fs::strerror(err));
};
case 't' =>
flg.provided = true;
flg.atime = match (parse_timet(optarg)) {
case let t: time::instant => yield t;
case let err: date::error =>
fmt::fatalf("couldn't parse datetime: {}",
date::strerror(err));
};
flg.mtime = flg.atime;
case =>
usage(help);
};
};
if (!flg.chatime && !flg.chmtime) {
flg.chatime = true;
flg.chmtime = true;
};
if (!flg.provided) {
const now = time::now(time::clock::REALTIME);
if (flg.chatime) flg.atime = now;
if (flg.chmtime) flg.mtime = now;
};
if (len(cmd.args) == 0) usage(help);
if (!flg.chatime) {
flg.atime = void;
};
if (!flg.chmtime) {
flg.mtime = void;
};
let exit_nonzero = false;
for (const path .. cmd.args) {
match (touch(&flg, path)) {
case void => void;
case let err: fs::error =>
fmt::errorfln("cannot touch {}: {}", path, fs::strerror(err))!;
exit_nonzero = true;
};
};
if (exit_nonzero) return os::exit(255);
};
fn touch(flg: *flags, path: str) (void | fs::error) = {
if (os::exists(path)) {
os::chtimes(path, flg.atime, flg.mtime)?;
} else if (!flg.nocreate) {
let file = os::create(path, 0o666)?;
defer io::close(file)!;
os::fchtimes(file, flg.atime, flg.mtime)?;
};
};
// %Y-%m-%d(T| )%H:%M:%S[(.|,)%N][Z]
fn parse_timed(d: str) (time::instant | date::error) = {
const n = strings::replace(d, "T", " ");
const norm = strings::replace(n, ",", ".");
free(n);
defer free(norm);
const loc = if (strings::hassuffix(norm, "Z")) {
yield chrono::UTC;
} else {
yield chrono::LOCAL;
};
const v = date::newvirtual();
v.vloc = loc;
v.zoff = date::zflag::LAP_EARLY | date::zflag::GAP_END;
v.nanosecond = 0;
const (head, tail) = strings::cut(norm, ".");
date::parse(&v, "%Y-%m-%d %H:%M:%S", head)?;
if (tail != "") {
date::parse(&v, "%N", tail)?;
};
const dt = date::realize(v)?;
return *(&dt: *time::instant);
};
// [(%y|%Y)]%m%d%H%M[.%S]
fn parse_timet(t: str) (time::instant | date::error) = {
const v = date::newvirtual();
v.vloc = chrono::LOCAL;
v.zoff = date::zflag::LAP_EARLY | date::zflag::GAP_END;
v.second = 0;
v.nanosecond = 0;
const (head, secs) = strings::cut(t, ".");
switch (len(head)) {
case 8 =>
const now = date::localnow();
v.year = date::year(&now);
date::parse(&v, "%m%d%H%M", head)?;
case 10 =>
date::parse(&v, "%y%m%d%H%M", head)?;
if (v.year2digit >= 69) {
v.century = 19;
} else {
v.century = 20;
};
case 12 =>
date::parse(&v, "%Y%m%d%H%M", head)?;
case =>
return date::invalid;
Not really applicable here but I guess this saved you from making:
type syntaxerror = !void;
};
if (secs != "") {
date::parse(&v, "%S", secs)?;
};
const dt = date::realize(v)?;
return *(&dt: *time::instant);
};
export fn usage(help: []getopt::help, flags: rune...) never = {
const progname = os::args[0];
if (len(flags) == 1) {
fmt::errorfln("{}: invalid argument for option -{}",
progname, flags[0])!;
} else if (len(flags) > 1) {
fmt::errorf("{}: invalid combination of options",
progname)!;
for (let i = 0z; i < len(flags); i += 1) {
fmt::errorf(" -{}", flags[i])!;
};
fmt::errorln()!;
};
getopt::printusage(os::stderr, progname, help)!;
os::exit(1);
};
```
Cool. The usage of date:: is idiomatic and looks correct to me.
---
Review.
time/date/date.ha | 4 ++++
time/date/format.ha | 4 ++++
time/date/parse.ha | 4 ++++
time/date/virtual.ha | 20 ++++++++++++++++++++
4 files changed, 32 insertions(+)
diff --git a/time/date/date.ha b/time/date/date.ha
index 57c63c08..4c5a40cf 100644
--- a/time/date/date.ha
+++ b/time/date/date.ha
@@ -26,6 +26,8 @@ export type date = struct {
chrono::moment,
era: (void | int),
+ century: (void | int),
+ year2digit: (void | int),
Should be in [[virtual]], not [[date]].
year: (void | int),
month: (void | int),
day: (void | int),
@@ -51,6 +53,8 @@ fn init() date = date {
daytime = void,
era = void,
+ century = void,
+ year2digit = void,
year = void,
month = void,
day = void,
diff --git a/time/date/format.ha b/time/date/format.ha
-%<-
index cb80879e..84b4788e 100644
--- a/time/date/format.ha
+++ b/time/date/format.ha
@@ -128,6 +128,8 @@ fn fmtout(out: io::handle, r: rune, d: *date) (size | io::error) = {
return fmt::fprint(out, MONTHS_SHORT[_month(d) - 1]);
case 'B' =>
return fmt::fprint(out, MONTHS[_month(d) - 1]);
+ case 'C' =>
+ return fmt::fprintf(out, "{:.2}", _year(d) / 100);
case 'd' =>
return fmt::fprintf(out, "{:.2}", _day(d));
case 'e' =>
@@ -197,6 +199,7 @@ fn fmtout(out: io::handle, r: rune, d: *date) (size | io::error) = {
// %A -- The full name of the day of the week.
// %b -- The abbreviated name of the month.
// %B -- The full name of the month.
+ // %C -- The century digits (first two digits of the year).
// %d -- The day of the month (decimal, range 01 to 31).
// %e -- The day of the month (decimal, range 1 to 31), left-padded space.
// %F -- The full date, equivalent to %Y-%m-%d
You didn't fix the whitespace here. Check your text editor settings.
@@ -272,6 +275,7 @@ export fn format(
// year
("%Y", "1994"),
("%y", "94"),
+ ("%C", "19"),
// month name
("%b", "Jan"),
("%B", "January"),
diff --git a/time/date/parse.ha b/time/date/parse.ha
index 11f958af..254d00ac 100644
--- a/time/date/parse.ha
+++ b/time/date/parse.ha
@@ -74,6 +74,8 @@ fn parse_specifier(
v.month = scan_for(iter, MONTHS_SHORT...)? + 1;
case 'B' =>
v.month = scan_for(iter, MONTHS...)? + 1;
+ case 'C' =>
+ v.century = scan_int(iter, 2)?;
case 'd', 'e' =>
v.day = scan_int(iter, 2)?;
case 'F' =>
@@ -118,6 +120,8 @@ fn parse_specifier(
v.weekday = scan_int(iter, 1)? - 1;
case 'W' =>
v.week = scan_int(iter, 2)?;
+ case 'y' =>
+ v.year2digit = scan_int(iter, 2)?;
case 'Y' =>
v.year = scan_int(iter, 4)?;
case 'z' =>
diff --git a/time/date/virtual.ha b/time/date/virtual.ha
-%<-
index 6ec90236..fc9e2103 100644
--- a/time/date/virtual.ha
+++ b/time/date/virtual.ha
@@ -170,6 +170,8 @@ export fn newvirtual() virtual = virtual {
daytime = void,
era = void,
+ century = void,
+ year2digit = void,
year = void,
month = void,
day = void,
@@ -253,6 +255,24 @@ export fn realize(
v: virtual,
locs: chrono::locality...
) (date | insufficient | invalid | zferror) = {
+ // normalize year2digit to year
+ if (v.year2digit is int) {
+ if (v.century is int) {
+ const year = v.century * 100 + v.year2digit as int;
+ if (v.year is int && v.year as int != year) {
+ fmt::println(v.year, year)!;
+ return invalid;
+ };
+ v.year = year;
+ } else {
+ if (v.year is int) {
+ v.century = v.year / 100;
+ } else {
+ return lack::DAYDATE;
+ };
+ };
+ };
+
match (v.zoff) {
case void =>
return lack::ZOFF;
--
2.44.0
See my commit for how this part should have been done.
hare/patches: FAILED in 37s
[time::date: parse %y, %C][0] v2 from [Curtis Arthaud][1]
[0]: https://lists.sr.ht/~torresjrjr/public-inbox/patches/50874
[1]: mailto:uku82@gmx.fr
✗ #1191919 FAILED hare/patches/alpine.yml https://builds.sr.ht/~torresjrjr/job/1191919
✗ #1191920 FAILED hare/patches/freebsd.yml https://builds.sr.ht/~torresjrjr/job/1191920
✗ #1191921 FAILED hare/patches/openbsd.yml https://builds.sr.ht/~torresjrjr/job/1191921