From: Thomas Walker Lynch Date: Mon, 10 Nov 2025 12:00:53 +0000 (+0000) Subject: Z -> Python, adds a number of command options X-Git-Url: https://git.reasoningtechnology.com/style/static/gitweb.css?a=commitdiff_plain;h=47c81aa9dd35f7a56767b434181666132b8d85fe;p=subu-incommon%2F.git Z -> Python, adds a number of command options --- diff --git a/executable/Z b/executable/Z index 7b77248..a70194e 100755 --- a/executable/Z +++ b/executable/Z @@ -1,3 +1,211 @@ -#!/bin/sh +#!/usr/bin/env -S python3 -B +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- -/usr/bin/date -u +"%Y-%m-%d %H:%M:%SZ" +import sys, time, datetime + +USAGE = """ +Z – UTC timestamp helper + +Usage: + Z Show this help. + Z help Show this help. + Z unix Print Unix time (seconds since epoch). + + Z [OPTIONS] Print a formatted UTC timestamp, where OPTIONS are: + T Use 'T' between date and time (ISO 8601 style). + T- Use between date and time. + T- with empty uses a single space (default). + + leftmost[-FIELD] Leftmost field to include (start at FIELD). + FIELD: year, month, day, hour, minute, second, scintilla + Default FIELD: year. + + rightmost[-FIELD] Rightmost field to include (end at FIELD). + Same FIELD set as leftmost. + Default FIELD: second. + + suffix Use 'Z' as the suffix (default). + suffix- Use 'UTC' as the suffix. + suffix- Use as the suffix. + suffix-'' No suffix at all (literal two single-quotes). + +Examples: + Z T-_ + Z T leftmost-year rightmost-second + Z T- leftmost-day rightmost-minute suffix + Z leftmost-hour rightmost-second suffix-UTC +""" + +FIELDS = ["year", "month", "day", "hour", "minute", "second", "scintilla"] +FIELD_INDEX = {name: i for i, name in enumerate(FIELDS)} + +DEFAULT_LEFTMOST = "year" +DEFAULT_RIGHTMOST = "second" +DEFAULT_SEP = " " +DEFAULT_SUFFIX = " Z" + +def print_usage(): + print(USAGE.strip()) + + +def parse_field(label, value, default): + if not value: + return default + if value not in FIELD_INDEX: + print(f"Z: invalid {label} '{value}' (expected one of: {', '.join(FIELDS)})", file=sys.stderr) + raise SystemExit(1) + return value + + +def build_timestamp(dt, leftmost, rightmost, sep, suffix): + li = FIELD_INDEX[leftmost] + ri = FIELD_INDEX[rightmost] + if li > ri: + print(f"Z: leftmost '{leftmost}' is finer than rightmost '{rightmost}'", file=sys.stderr) + raise SystemExit(1) + + def in_range(name): + idx = FIELD_INDEX[name] + return li <= idx <= ri + + # date part + date_parts = [] + if in_range("year"): + date_parts.append(f"{dt.year:04d}") + if in_range("month"): + date_parts.append(f"{dt.month:02d}") + if in_range("day"): + date_parts.append(f"{dt.day:02d}") + date_str = "-".join(date_parts) if date_parts else "" + + # time part + time_parts = [] + want_hour = in_range("hour") + want_minute = in_range("minute") + want_second = in_range("second") + want_scint = in_range("scintilla") + + if want_hour or want_minute or want_second or want_scint: + if want_hour: + time_parts.append(f"{dt.hour:02d}") + if want_minute: + time_parts.append(f"{dt.minute:02d}") + elif want_hour and (want_second or want_scint): + time_parts.append(f"{dt.minute:02d}") + if want_second or want_scint: + time_parts.append(f"{dt.second:02d}") + + time_str = ":".join(time_parts) if time_parts else "" + + if want_scint: + frac = f"{dt.microsecond:06d}" + if time_str: + time_str = f"{time_str}.{frac}" + else: + time_str = f"{dt.second:02d}.{frac}" + + # combine date + time + if date_str and time_str: + out = f"{date_str}{sep}{time_str}" + else: + out = date_str or time_str or "" + + # suffix is literal; if you want a separator, put it in the suffix + if suffix: + out = f"{out}{suffix}" if out else suffix + + return out + +def format_utc( + *, + dt=None, + leftmost="year", + rightmost="second", + sep=" ", + suffix="Z", +): + if dt is None: + dt = datetime.datetime.now(datetime.timezone.utc) + elif dt.tzinfo is None: + dt = dt.replace(tzinfo=datetime.timezone.utc) + else: + dt = dt.astimezone(datetime.timezone.utc) + return build_timestamp(dt, leftmost, rightmost, sep, suffix) + +def CLI(argv=None): + """ + Command-line interface for Z. + + argv – list of arguments (excluding program name). If None, uses sys.argv[1:]. + Returns integer exit code. + """ + if argv is None: + argv = sys.argv[1:] + + # No-arg default: most readable compact form + if not argv: + out = format_utc( + leftmost=DEFAULT_LEFTMOST, + rightmost=DEFAULT_RIGHTMOST, + sep=DEFAULT_SEP, + suffix=DEFAULT_SUFFIX, + ) + print(out) + return 0 + + if argv[0] in ("help", "-h", "--help"): + print_usage() + return 0 + + # Start from the same defaults that no-arg uses + leftmost = DEFAULT_LEFTMOST + rightmost = DEFAULT_RIGHTMOST + sep = DEFAULT_SEP + suffix = DEFAULT_SUFFIX + + args = list(argv) + + # Now parse options + for arg in args: + if arg == "T": + sep = "T" + elif arg.startswith("T-"): + tail = arg[2:] + sep = " " if tail == "" else tail + + elif arg == "leftmost": + leftmost = "year" + elif arg.startswith("leftmost-"): + leftmost = parse_field("leftmost", arg.split("-", 1)[1], "year") + + elif arg == "rightmost": + rightmost = "second" + elif arg.startswith("rightmost-"): + rightmost = parse_field("rightmost", arg.split("-", 1)[1], "second") + + elif arg == "suffix": + suffix = "Z" + elif arg.startswith("suffix-"): + tail = arg.split("-", 1)[1] + if tail == "": + suffix = "UTC" + elif tail == "''": + suffix = "" + else: + suffix = tail + + else: + print(f"Z: unknown option '{arg}'", file=sys.stderr) + return 1 + + out = format_utc( + leftmost=leftmost, + rightmost=rightmost, + sep=sep, + suffix=suffix, + ) + print(out) + return 0 + +if __name__ == "__main__": + raise SystemExit(CLI())