~torresjrjr/public-inbox

hare: time::date: parse %y, %C v2 APPLIED

Curtis Arthaud: 1
 time::date: parse %y, %C

 4 files changed, 32 insertions(+), 0 deletions(-)
#1191919 alpine.yml failed
#1191920 freebsd.yml failed
#1191921 openbsd.yml failed
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~torresjrjr/public-inbox/patches/50874/mbox | git am -3
Learn more about email & git

[PATCH hare v2] time::date: parse %y, %C Export this patch

Signed-off-by: Curtis Arthaud <uku82@gmx.fr>
---
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?)
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:).
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)
```
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;
	};
	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);
};
```
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),
	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
@@ -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




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