summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lamparter <equinox@opensourcerouting.org>2022-01-13 07:51:54 +0100
committerDavid Lamparter <equinox@opensourcerouting.org>2022-01-14 13:33:57 +0100
commit2c76ba433f2b0d0b180bca20ddc2f28751dd9b70 (patch)
tree87aa3f67c878c8809f590e4414dc6f9131caf8e7
parent2c5b4d80efc507f7244303d959f99af8061dacf3 (diff)
lib: add time formatting printfrr exts
Refer to docs in doc/developer for details. Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
-rw-r--r--doc/developer/logging.rst116
-rw-r--r--lib/monotime.h75
-rw-r--r--lib/printfrr.h4
-rw-r--r--lib/strformat.c325
-rw-r--r--tests/lib/test_printfrr.c110
5 files changed, 630 insertions, 0 deletions
diff --git a/doc/developer/logging.rst b/doc/developer/logging.rst
index a51f51553f..5aca27d7f7 100644
--- a/doc/developer/logging.rst
+++ b/doc/developer/logging.rst
@@ -219,6 +219,122 @@ Networking data types
:frrfmtout:`SOCK_STREAM`
+Time/interval formats
+^^^^^^^^^^^^^^^^^^^^^
+
+.. frrfmt:: %pTS (struct timespec *)
+
+.. frrfmt:: %pTV (struct timeval *)
+
+.. frrfmt:: %pTT (time_t *)
+
+ Above 3 options internally result in the same code being called, support
+ the same flags and produce equal output with one exception: ``%pTT``
+ has no sub-second precision and the formatter will never print a
+ (nonsensical) ``.000``.
+
+ Exactly one of ``I``, ``M`` or ``R`` must immediately follow after
+ ``TS``/``TV``/``TT`` to specify whether the input is an interval, monotonic
+ timestamp or realtime timestamp:
+
+ ``%pTVI``: input is an interval, not a timestamp. Print interval.
+
+ ``%pTVIs``: input is an interval, convert to wallclock by subtracting it
+ from current time (i.e. interval has passed **s**\ ince.)
+
+ ``%pTVIu``: input is an interval, convert to wallclock by adding it to
+ current time (i.e. **u**\ ntil interval has passed.)
+
+ ``%pTVM`` - input is a timestamp on CLOCK_MONOTONIC, convert to wallclock
+ time (by grabbing current CLOCK_MONOTONIC and CLOCK_REALTIME and doing the
+ math) and print calendaric date.
+
+ ``%pTVMs`` - input is a timestamp on CLOCK_MONOTONIC, print interval
+ **s**\ ince that timestamp (elapsed.)
+
+ ``%pTVMu`` - input is a timestamp on CLOCK_MONOTONIC, print interval
+ **u**\ ntil that timestamp (deadline.)
+
+ ``%pTVR`` - input is a timestamp on CLOCK_REALTIME, print calendaric date.
+
+ ``%pTVRs`` - input is a timestamp on CLOCK_REALTIME, print interval
+ **s**\ ince that timestamp.
+
+ ``%pTVRu`` - input is a timestamp on CLOCK_REALTIME, print interval
+ **u**\ ntil that timestamp.
+
+ ``%pTVA`` - reserved for CLOCK_TAI in case a PTP implementation is
+ interfaced to FRR. Not currently implemented.
+
+ .. note::
+
+ If ``%pTVRs`` or ``%pTVRu`` are used, this is generally an indication
+ that a CLOCK_MONOTONIC timestamp should be used instead (or added in
+ parallel.) CLOCK_REALTIME might be adjusted by NTP, PTP or similar
+ procedures, causing bogus intervals to be printed.
+
+ ``%pTVM`` on first look might be assumed to have the same problem, but
+ on closer thought the assumption is always that current system time is
+ correct. And since a CLOCK_MONOTONIC interval is also quite safe to
+ assume to be correct, the (past) absolute timestamp to be printed from
+ this can likely be correct even if it doesn't match what CLOCK_REALTIME
+ would have indicated at that point in the past. This logic does,
+ however, not quite work for *future* times.
+
+ Generally speaking, almost all use cases in FRR should (and do) use
+ CLOCK_MONOTONIC (through :c:func:`monotime()`.)
+
+ Flags common to printing calendar times and intervals:
+
+ ``p``: include spaces in appropriate places (depends on selected format.)
+
+ ``%p.3TV...``: specify sub-second resolution (use with ``FMT_NSTD`` to
+ suppress gcc warning.) As noted above, ``%pTT`` will never print sub-second
+ digits since there are none. Only some formats support printing sub-second
+ digits and the default may vary.
+
+ The following flags are available for printing calendar times/dates:
+
+ (no flag): :frrfmtout:`Sat Jan 1 00:00:00 2022` - print output from
+ ``ctime()``, in local time zone. Since FRR does not currently use/enable
+ locale support, this is always the C locale. (Locale support getting added
+ is unlikely for the time being and would likely break other things worse
+ than this.)
+
+ ``i``: :frrfmtout:`2022-01-01T00:00:00.123` - ISO8601 timestamp in local
+ time zone (note there is no ``Z`` or ``+00:00`` suffix.) Defaults to
+ millisecond precision.
+
+ ``ip``: :frrfmtout:`2022-01-01 00:00:00.123` - use readable form of ISO8601
+ with space instead of ``T`` separator.
+
+ The following flags are available for printing intervals:
+
+ (no flag): :frrfmtout:`9w9d09:09:09.123` - does not match any
+ preexisting format; added because it does not lose precision (like ``t``)
+ for longer intervals without printing huge numbers (like ``h``/``m``).
+ Defaults to millisecond precision. The week/day fields are left off if
+ they're zero, ``p`` adds a space after the respective letter.
+
+ ``t``: :frrfmtout:`9w9d09h`, :frrfmtout:`9d09h09m`, :frrfmtout:`09:09:09` -
+ this replaces :c:func:`frrtime_to_interval()`. ``p`` adds spaces after
+ week/day/hour letters.
+
+ ``d``: print decimal number of seconds. Defaults to millisecond precision.
+
+ ``x`` / ``tx`` / ``dx``: Like no flag / ``t`` / ``d``, but print
+ :frrfmtout:`-` for zero or negative intervals (for use with unset timers.)
+
+ ``h``: :frrfmtout:`09:09:09`
+
+ ``hx``: :frrfmtout:`09:09:09`, :frrfmtout:`--:--:--` - this replaces
+ :c:func:`pim_time_timer_to_hhmmss()`.
+
+ ``m``: :frrfmtout:`09:09`
+
+ ``mx``: :frrfmtout:`09:09`, :frrfmtout:`--:--` - this replaces
+ :c:func:`pim_time_timer_to_mmss()`.
+
General utility formats
^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/lib/monotime.h b/lib/monotime.h
index dda763784f..15b6933955 100644
--- a/lib/monotime.h
+++ b/lib/monotime.h
@@ -25,6 +25,9 @@
extern "C" {
#endif
+struct fbuf;
+struct printfrr_eargs;
+
#ifndef TIMESPEC_TO_TIMEVAL
/* should be in sys/time.h on BSD & Linux libcs */
#define TIMESPEC_TO_TIMEVAL(tv, ts) \
@@ -42,6 +45,31 @@ extern "C" {
} while (0)
#endif
+/* Linux/glibc is sadly missing these timespec helpers */
+#ifndef timespecadd
+#define timespecadd(tsp, usp, vsp) \
+ do { \
+ (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \
+ (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \
+ if ((vsp)->tv_nsec >= 1000000000L) { \
+ (vsp)->tv_sec++; \
+ (vsp)->tv_nsec -= 1000000000L; \
+ } \
+ } while (0)
+#endif
+
+#ifndef timespecsub
+#define timespecsub(tsp, usp, vsp) \
+ do { \
+ (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \
+ (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \
+ if ((vsp)->tv_nsec < 0) { \
+ (vsp)->tv_sec--; \
+ (vsp)->tv_nsec += 1000000000L; \
+ } \
+ } while (0)
+#endif
+
static inline time_t monotime(struct timeval *tvo)
{
struct timespec ts;
@@ -132,6 +160,53 @@ static inline const char *frrtime_to_interval(time_t t, char *buf,
return buf;
}
+enum {
+ /* n/a - input was seconds precision, don't print any fractional */
+ TIMEFMT_SECONDS = (1 << 0),
+ /* caller is directly invoking printfrr_time and has pre-specified
+ * I/Iu/Is/M/Mu/Ms/R/Ru/Rs (for printing timers)
+ */
+ TIMEFMT_PRESELECT = (1 << 1),
+ /* don't print any output - this is needed for invoking printfrr_time
+ * from another printfrr extensions to skip over flag characters
+ */
+ TIMEFMT_SKIP = (1 << 2),
+ /* use spaces in appropriate places */
+ TIMEFMT_SPACE = (1 << 3),
+
+ /* input interpretations: */
+ TIMEFMT_REALTIME = (1 << 8),
+ TIMEFMT_MONOTONIC = (1 << 9),
+ TIMEFMT_SINCE = (1 << 10),
+ TIMEFMT_UNTIL = (1 << 11),
+
+ TIMEFMT_ABSOLUTE = TIMEFMT_REALTIME | TIMEFMT_MONOTONIC,
+ TIMEFMT_ANCHORS = TIMEFMT_SINCE | TIMEFMT_UNTIL,
+
+ /* calendaric formats: */
+ TIMEFMT_ISO8601 = (1 << 16),
+
+ /* interval formats: */
+ /* 't' - use [t]raditional 3-block format */
+ TIMEFMT_BASIC = (1 << 24),
+ /* 'm' - select mm:ss */
+ TIMEFMT_MMSS = (1 << 25),
+ /* 'h' - select hh:mm:ss */
+ TIMEFMT_HHMMSS = (1 << 26),
+ /* 'd' - print as decimal number of seconds */
+ TIMEFMT_DECIMAL = (1 << 27),
+ /* 'mx'/'hx' - replace zero value with "--:--" or "--:--:--" */
+ TIMEFMT_DASHES = (1 << 31),
+
+ /* helpers for reference */
+ TIMEFMT_TIMER_DEADLINE =
+ TIMEFMT_PRESELECT | TIMEFMT_MONOTONIC | TIMEFMT_UNTIL,
+ TIMEFMT_TIMER_INTERVAL = TIMEFMT_PRESELECT,
+};
+
+extern ssize_t printfrr_time(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags);
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/printfrr.h b/lib/printfrr.h
index 37f1f9c8cd..1b5ddb8ca4 100644
--- a/lib/printfrr.h
+++ b/lib/printfrr.h
@@ -286,6 +286,10 @@ struct va_format {
#pragma FRR printfrr_ext "%pSE" (char *)
#pragma FRR printfrr_ext "%pSQ" (char *)
+
+#pragma FRR printfrr_ext "%pTS" (struct timespec *)
+#pragma FRR printfrr_ext "%pTV" (struct timeval *)
+#pragma FRR printfrr_ext "%pTT" (time_t *)
#endif
/* when using non-ISO-C compatible extension specifiers... */
diff --git a/lib/strformat.c b/lib/strformat.c
index 431e573a0c..7903e87ead 100644
--- a/lib/strformat.c
+++ b/lib/strformat.c
@@ -22,8 +22,10 @@
#include <string.h>
#include <ctype.h>
+#include <time.h>
#include "printfrr.h"
+#include "monotime.h"
printfrr_ext_autoreg_p("HX", printfrr_hexdump)
static ssize_t printfrr_hexdump(struct fbuf *buf, struct printfrr_eargs *ea,
@@ -270,3 +272,326 @@ static ssize_t printfrr_quote(struct fbuf *buf, struct printfrr_eargs *ea,
ret += bputch(buf, '"');
return ret;
}
+
+static ssize_t printfrr_abstime(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags);
+static ssize_t printfrr_reltime(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags);
+
+ssize_t printfrr_time(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags)
+{
+ bool have_abs, have_anchor;
+
+ if (!(flags & TIMEFMT_PRESELECT)) {
+ switch (ea->fmt[0]) {
+ case 'I':
+ /* no bit set */
+ break;
+ case 'M':
+ flags |= TIMEFMT_MONOTONIC;
+ break;
+ case 'R':
+ flags |= TIMEFMT_REALTIME;
+ break;
+ default:
+ return bputs(buf,
+ "{invalid time format input specifier}");
+ }
+ ea->fmt++;
+
+ if (ea->fmt[0] == 's') {
+ flags |= TIMEFMT_SINCE;
+ ea->fmt++;
+ } else if (ea->fmt[0] == 'u') {
+ flags |= TIMEFMT_UNTIL;
+ ea->fmt++;
+ }
+ }
+
+ have_abs = !!(flags & TIMEFMT_ABSOLUTE);
+ have_anchor = !!(flags & TIMEFMT_ANCHORS);
+
+ if (have_abs ^ have_anchor)
+ return printfrr_abstime(buf, ea, ts, flags);
+ else
+ return printfrr_reltime(buf, ea, ts, flags);
+}
+
+static ssize_t do_subsec(struct fbuf *buf, const struct timespec *ts,
+ int precision, unsigned int flags)
+{
+ unsigned long long frac;
+
+ if (precision <= 0 || (flags & TIMEFMT_SECONDS))
+ return 0;
+
+ frac = ts->tv_nsec;
+ if (precision > 9)
+ precision = 9;
+ for (int i = precision; i < 9; i++)
+ frac /= 10;
+ return bprintfrr(buf, ".%0*llu", precision, frac);
+}
+
+static ssize_t printfrr_abstime(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags)
+{
+ struct timespec real_ts[1];
+ struct tm tm;
+ char cbuf[32] = ""; /* manpage says 26 for ctime_r */
+ ssize_t ret = 0;
+ int precision = ea->precision;
+
+ while (ea->fmt[0]) {
+ char ch = *ea->fmt++;
+
+ switch (ch) {
+ case 'p':
+ flags |= TIMEFMT_SPACE;
+ continue;
+ case 'i':
+ flags |= TIMEFMT_ISO8601;
+ continue;
+ }
+
+ ea->fmt--;
+ break;
+ }
+
+ if (flags & TIMEFMT_SKIP)
+ return 0;
+
+ if (flags & TIMEFMT_REALTIME)
+ *real_ts = *ts;
+ else if (flags & TIMEFMT_MONOTONIC) {
+ struct timespec mono_now[1];
+
+ clock_gettime(CLOCK_REALTIME, real_ts);
+ clock_gettime(CLOCK_MONOTONIC, mono_now);
+
+ timespecsub(real_ts, mono_now, real_ts);
+ timespecadd(real_ts, ts, real_ts);
+ } else {
+ clock_gettime(CLOCK_REALTIME, real_ts);
+
+ if (flags & TIMEFMT_SINCE)
+ timespecsub(real_ts, ts, real_ts);
+ else /* flags & TIMEFMT_UNTIL */
+ timespecadd(real_ts, ts, real_ts);
+ }
+
+ localtime_r(&real_ts->tv_sec, &tm);
+
+ if (flags & TIMEFMT_ISO8601) {
+ if (flags & TIMEFMT_SPACE)
+ strftime(cbuf, sizeof(cbuf), "%Y-%m-%d %H:%M:%S", &tm);
+ else
+ strftime(cbuf, sizeof(cbuf), "%Y-%m-%dT%H:%M:%S", &tm);
+ ret += bputs(buf, cbuf);
+
+ if (precision == -1)
+ precision = 3;
+ ret += do_subsec(buf, real_ts, precision, flags);
+ } else {
+ size_t len;
+
+ asctime_r(&tm, cbuf);
+
+ len = strlen(cbuf);
+ if (!len)
+ /* WTF. */
+ return 0;
+ if (cbuf[len - 1] == '\n')
+ cbuf[len - 1] = '\0';
+
+ ret += bputs(buf, cbuf);
+ }
+ return ret;
+}
+
+static ssize_t printfrr_reltime(struct fbuf *buf, struct printfrr_eargs *ea,
+ const struct timespec *ts, unsigned int flags)
+{
+ struct timespec real_ts[1];
+ ssize_t ret = 0;
+ const char *space = "";
+ const char *dashes = "-";
+ int precision = ea->precision;
+
+ while (ea->fmt[0]) {
+ char ch = *ea->fmt++;
+
+ switch (ch) {
+ case 'p':
+ flags |= TIMEFMT_SPACE;
+ space = " ";
+ continue;
+ case 't':
+ flags |= TIMEFMT_BASIC;
+ continue;
+ case 'd':
+ flags |= TIMEFMT_DECIMAL;
+ continue;
+ case 'm':
+ flags |= TIMEFMT_MMSS;
+ dashes = "--:--";
+ continue;
+ case 'h':
+ flags |= TIMEFMT_HHMMSS;
+ dashes = "--:--:--";
+ continue;
+ case 'x':
+ flags |= TIMEFMT_DASHES;
+ continue;
+ }
+
+ ea->fmt--;
+ break;
+ }
+
+ if (flags & TIMEFMT_SKIP)
+ return 0;
+
+ if (flags & TIMEFMT_ABSOLUTE) {
+ struct timespec anchor[1];
+
+ if (flags & TIMEFMT_REALTIME)
+ clock_gettime(CLOCK_REALTIME, anchor);
+ else
+ clock_gettime(CLOCK_MONOTONIC, anchor);
+ if (flags & TIMEFMT_UNTIL)
+ timespecsub(ts, anchor, real_ts);
+ else /* flags & TIMEFMT_SINCE */
+ timespecsub(anchor, ts, real_ts);
+ } else
+ *real_ts = *ts;
+
+ if (real_ts->tv_sec == 0 && real_ts->tv_nsec == 0 &&
+ (flags & TIMEFMT_DASHES))
+ return bputs(buf, dashes);
+
+ if (real_ts->tv_sec < 0) {
+ if (flags & TIMEFMT_DASHES)
+ return bputs(buf, dashes);
+
+ /* -0.3s is { -1s + 700ms } */
+ real_ts->tv_sec = -real_ts->tv_sec - 1;
+ real_ts->tv_nsec = 1000000000L - real_ts->tv_nsec;
+ if (real_ts->tv_nsec >= 1000000000L) {
+ real_ts->tv_sec++;
+ real_ts->tv_nsec -= 1000000000L;
+ }
+
+ /* all formats have a - make sense in front */
+ ret += bputch(buf, '-');
+ }
+
+ if (flags & TIMEFMT_DECIMAL) {
+ ret += bprintfrr(buf, "%lld", (long long)real_ts->tv_sec);
+ if (precision == -1)
+ precision = 3;
+ ret += do_subsec(buf, real_ts, precision, flags);
+ return ret;
+ }
+
+ /* these divisions may be slow on embedded boxes, hence only do the
+ * ones we need, plus the ?: zero check to hopefully skip zeros fast
+ */
+ lldiv_t min_sec = lldiv(real_ts->tv_sec, 60);
+
+ if (flags & TIMEFMT_MMSS) {
+ ret += bprintfrr(buf, "%02lld:%02lld", min_sec.quot,
+ min_sec.rem);
+ ret += do_subsec(buf, real_ts, precision, flags);
+ return ret;
+ }
+
+ lldiv_t hour_min = min_sec.quot ? lldiv(min_sec.quot, 60) : (lldiv_t){};
+
+ if (flags & TIMEFMT_HHMMSS) {
+ ret += bprintfrr(buf, "%02lld:%02lld:%02lld", hour_min.quot,
+ hour_min.rem, min_sec.rem);
+ ret += do_subsec(buf, real_ts, precision, flags);
+ return ret;
+ }
+
+ lldiv_t day_hour =
+ hour_min.quot ? lldiv(hour_min.quot, 24) : (lldiv_t){};
+ lldiv_t week_day =
+ day_hour.quot ? lldiv(day_hour.quot, 7) : (lldiv_t){};
+
+ /* if sub-second precision is not supported, return */
+ if (flags & TIMEFMT_BASIC) {
+ /* match frrtime_to_interval (without space flag) */
+ if (week_day.quot)
+ ret += bprintfrr(buf, "%lldw%s%lldd%s%02lldh",
+ week_day.quot, space, week_day.rem,
+ space, day_hour.rem);
+ else if (day_hour.quot)
+ ret += bprintfrr(buf, "%lldd%s%02lldh%s%02lldm",
+ day_hour.quot, space, day_hour.rem,
+ space, hour_min.rem);
+ else
+ ret += bprintfrr(buf, "%02lld:%02lld:%02lld",
+ hour_min.quot, hour_min.rem,
+ min_sec.rem);
+ /* no sub-seconds here */
+ return ret;
+ }
+
+ /* default format */
+ if (week_day.quot)
+ ret += bprintfrr(buf, "%lldw%s", week_day.quot, space);
+ if (week_day.rem || week_day.quot)
+ ret += bprintfrr(buf, "%lldd%s", week_day.rem, space);
+
+ ret += bprintfrr(buf, "%02lld:%02lld:%02lld", day_hour.rem,
+ hour_min.rem, min_sec.rem);
+
+ if (precision == -1)
+ precision = 3;
+ ret += do_subsec(buf, real_ts, precision, flags);
+ return ret;
+}
+
+printfrr_ext_autoreg_p("TS", printfrr_ts)
+static ssize_t printfrr_ts(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *vptr)
+{
+ const struct timespec *ts = vptr;
+
+ if (!ts)
+ return bputs(buf, "(null)");
+ return printfrr_time(buf, ea, ts, 0);
+}
+
+printfrr_ext_autoreg_p("TV", printfrr_tv)
+static ssize_t printfrr_tv(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *vptr)
+{
+ const struct timeval *tv = vptr;
+ struct timespec ts;
+
+ if (!tv)
+ return bputs(buf, "(null)");
+
+ ts.tv_sec = tv->tv_sec;
+ ts.tv_nsec = tv->tv_usec * 1000;
+ return printfrr_time(buf, ea, &ts, 0);
+}
+
+printfrr_ext_autoreg_p("TT", printfrr_tt)
+static ssize_t printfrr_tt(struct fbuf *buf, struct printfrr_eargs *ea,
+ const void *vptr)
+{
+ const time_t *tt = vptr;
+ struct timespec ts;
+
+ if (!tt)
+ return bputs(buf, "(null)");
+
+ ts.tv_sec = *tt;
+ ts.tv_nsec = 0;
+ return printfrr_time(buf, ea, &ts, TIMEFMT_SECONDS);
+}
diff --git a/tests/lib/test_printfrr.c b/tests/lib/test_printfrr.c
index 55aa9bdefc..8f9d637afd 100644
--- a/tests/lib/test_printfrr.c
+++ b/tests/lib/test_printfrr.c
@@ -278,5 +278,115 @@ int main(int argc, char **argv)
inet_pton(AF_INET6, "fe2c::34", &nh.gate.ipv6);
printchk("fe2c::34", "%pNHcg", &nh);
+ /* time printing */
+
+ /* need a non-UTC timezone for testing */
+ setenv("TZ", "TEST-01:00", 1);
+ tzset();
+
+ struct timespec ts;
+ struct timeval tv;
+ time_t tt;
+
+ ts.tv_sec = tv.tv_sec = tt = 1642015880;
+ ts.tv_nsec = 123456789;
+ tv.tv_usec = 234567;
+
+ printchk("Wed Jan 12 20:31:20 2022", "%pTSR", &ts);
+ printchk("Wed Jan 12 20:31:20 2022", "%pTVR", &tv);
+ printchk("Wed Jan 12 20:31:20 2022", "%pTTR", &tt);
+
+ FMT_NSTD(printchk("Wed Jan 12 20:31:20 2022", "%.3pTSR", &ts));
+
+ printchk("2022-01-12T20:31:20.123", "%pTSRi", &ts);
+ printchk("2022-01-12 20:31:20.123", "%pTSRip", &ts);
+ printchk("2022-01-12 20:31:20.123", "%pTSRpi", &ts);
+ FMT_NSTD(printchk("2022-01-12T20:31:20", "%.0pTSRi", &ts));
+ FMT_NSTD(printchk("2022-01-12T20:31:20.123456789", "%.9pTSRi", &ts));
+ FMT_NSTD(printchk("2022-01-12T20:31:20", "%.3pTTRi", &tt));
+
+ ts.tv_sec = tv.tv_sec = tt = 9 * 86400 + 12345;
+
+ printchk("1w 2d 03:25:45.123", "%pTSIp", &ts);
+ printchk("1w2d03:25:45.123", "%pTSI", &ts);
+ printchk("1w2d03:25:45.234", "%pTVI", &tv);
+ printchk("1w2d03:25:45", "%pTTI", &tt);
+
+ printchk("1w 2d 03h", "%pTVItp", &tv);
+ printchk("1w2d03h", "%pTSIt", &ts);
+
+ printchk("219:25:45", "%pTVIh", &tv);
+ printchk("13165:45", "%pTVIm", &tv);
+
+ ts.tv_sec = tv.tv_sec = tt = 1 * 86400 + 12345;
+
+ printchk("1d 03:25:45.123", "%pTSIp", &ts);
+ printchk("1d03:25:45.234", "%pTVI", &tv);
+
+ printchk("1d 03h 25m", "%pTVItp", &tv);
+ printchk("1d03h25m", "%pTSIt", &ts);
+
+ printchk("98745.234", "%pTVId", &tv);
+
+ printchk("27:25:45", "%pTVIh", &tv);
+ printchk("1645:45", "%pTVIm", &tv);
+
+ ts.tv_sec = tv.tv_sec = tt = 12345;
+
+ printchk("03:25:45.123", "%pTSIp", &ts);
+ printchk("03:25:45.123", "%pTSI", &ts);
+ printchk("03:25:45.234", "%pTVI", &tv);
+ printchk("03:25:45", "%pTTI", &tt);
+
+ printchk("03:25:45", "%pTSItp", &ts);
+ printchk("03:25:45", "%pTVIt", &tv);
+
+ printchk("12345.234", "%pTVId", &tv);
+
+ printchk("03:25:45", "%pTVIh", &tv);
+ printchk("205:45", "%pTVIm", &tv);
+
+ ts.tv_sec = tv.tv_sec = tt = 0;
+
+ printchk("00:00:00.123", "%pTSIp", &ts);
+ printchk("00:00:00.123", "%pTSI", &ts);
+ printchk("00:00:00.234", "%pTVI", &tv);
+ printchk("00:00:00", "%pTTI", &tt);
+
+ printchk("00:00:00", "%pTVItp", &tv);
+ printchk("00:00:00", "%pTSIt", &ts);
+
+ printchk("0.234", "%pTVId", &tv);
+ printchk("0.234", "%pTVIdx", &tv);
+ printchk("-", "%pTTIdx", &tt);
+
+ printchk("00:00:00", "%pTVIhx", &tv);
+ printchk("--:--:--", "%pTTIhx", &tt);
+ printchk("00:00", "%pTVImx", &tv);
+ printchk("--:--", "%pTTImx", &tt);
+
+ ts.tv_sec = tv.tv_sec = tt = -10;
+
+ printchk("-00:00:09.876", "%pTSIp", &ts);
+ printchk("-00:00:09.876", "%pTSI", &ts);
+ printchk("-00:00:09.765", "%pTVI", &tv);
+ printchk("-00:00:10", "%pTTI", &tt);
+
+ printchk("-00:00:09", "%pTSItp", &ts);
+ printchk("-00:00:09", "%pTSIt", &ts);
+ printchk("-00:00:09", "%pTVIt", &tv);
+ printchk("-00:00:10", "%pTTIt", &tt);
+
+ printchk("-9.765", "%pTVId", &tv);
+ printchk("-", "%pTVIdx", &tv);
+
+ printchk("-00:00:09", "%pTSIh", &ts);
+ printchk("--:--:--", "%pTVIhx", &tv);
+ printchk("--:--:--", "%pTTIhx", &tt);
+
+ printchk("-00:09", "%pTSIm", &ts);
+ printchk("--:--", "%pTVImx", &tv);
+ printchk("--:--", "%pTTImx", &tt);
+
return !!errors;
}