]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
timeutils: add strtimespec_relative
authorThomas Weißschuh <thomas@t-8ch.de>
Tue, 4 Jul 2023 07:45:55 +0000 (09:45 +0200)
committerThomas Weißschuh <thomas@t-8ch.de>
Tue, 4 Jul 2023 12:15:20 +0000 (14:15 +0200)
Signed-off-by: Thomas Weißschuh <thomas@t-8ch.de>
include/timeutils.h
lib/timeutils.c
tests/ts/lib/timeutils

index 4257bd1edad46da9c37c9451a7f93e05c9712318..27261abd87dbcdb177119cc833818cb7b4877b60 100644 (file)
@@ -78,6 +78,7 @@ int strtimeval_iso(const struct timeval *tv, int flags, char *buf, size_t bufsz)
 int strtm_iso(const struct tm *tm, int flags, char *buf, size_t bufsz);
 int strtime_iso(const time_t *t, int flags, char *buf, size_t bufsz);
 int strtimespec_iso(const struct timespec *t, int flags, char *buf, size_t bufsz);
+int strtimespec_relative(const struct timespec *ts, char *buf, size_t bufsz);
 
 #define UL_SHORTTIME_THISYEAR_HHMM (1 << 1)
 
index c1ffc547c033d175b3dd4ca6f35e7d16e672ed0d..8f9c8e9be82fd5a7e24be6320d69e276c44a0074 100644 (file)
@@ -617,6 +617,63 @@ int strtime_short(const time_t *t, struct timeval *now, int flags, char *buf, si
        return rc <= 0 ? -1 : 0;
 }
 
+int strtimespec_relative(const struct timespec *ts, char *buf, size_t bufsz)
+{
+       time_t secs = ts->tv_sec;
+       size_t i, parts = 0;
+       int rc;
+
+       if (bufsz)
+               buf[0] = '\0';
+
+       static const struct {
+               const char * const suffix;
+               int width;
+               int64_t secs;
+       } table[] = {
+               { "y", 4, NSEC_PER_YEAR   / NSEC_PER_SEC },
+               { "d", 3, NSEC_PER_DAY    / NSEC_PER_SEC },
+               { "h", 2, NSEC_PER_HOUR   / NSEC_PER_SEC },
+               { "m", 2, NSEC_PER_MINUTE / NSEC_PER_SEC },
+               { "s", 2, NSEC_PER_SEC    / NSEC_PER_SEC },
+       };
+
+       for (i = 0; i < ARRAY_SIZE(table); i++) {
+               if (secs >= table[i].secs) {
+                       rc = snprintf(buf, bufsz,
+                                     "%*"PRId64"%s%s",
+                                     parts ? table[i].width : 0,
+                                     secs / table[i].secs, table[i].suffix,
+                                     secs % table[i].secs ? " " : "");
+                       if (rc < 0 || (size_t) rc > bufsz)
+                               goto err;
+                       parts++;
+                       buf += rc;
+                       bufsz -= rc;
+                       secs %= table[i].secs;
+               }
+       }
+
+       if (ts->tv_nsec) {
+               if (ts->tv_nsec % NSEC_PER_MSEC) {
+                       rc = snprintf(buf, bufsz, "%*luns",
+                                     parts ? 10 : 0, ts->tv_nsec);
+                       if (rc < 0 || (size_t) rc > bufsz)
+                               goto err;
+               } else {
+                       rc = snprintf(buf, bufsz, "%*llums",
+                                     parts ? 4 : 0, ts->tv_nsec / NSEC_PER_MSEC);
+                       if (rc < 0 || (size_t) rc > bufsz)
+                               goto err;
+               }
+       }
+
+       return 0;
+ err:
+       warnx(_("format_reltime: buffer overflow."));
+       return -1;
+}
+
 #ifndef HAVE_TIMEGM
 time_t timegm(struct tm *tm)
 {
@@ -727,6 +784,53 @@ static int run_unittest_format(void)
        return rc;
 }
 
+static int run_unittest_format_relative(void)
+{
+       int rc = EXIT_SUCCESS;
+       char buf[FORMAT_TIMESTAMP_MAX];
+       static const struct testcase {
+               struct timespec ts;
+               const char * const expected;
+       } testcases[] = {
+               {{}, "" },
+               {{         1 },                  "1s" },
+               {{        10 },                 "10s" },
+               {{       100 },              "1m 40s" },
+               {{      1000 },             "16m 40s" },
+               {{     10000 },          "2h 46m 40s" },
+               {{    100000 },      "1d  3h 46m 40s" },
+               {{   1000000 },     "11d 13h 46m 40s" },
+               {{  10000000 },    "115d 17h 46m 40s" },
+               {{ 100000000 }, "3y  61d 15h 46m 40s" },
+               {{        60 },                  "1m" },
+               {{      3600 },                  "1h" },
+
+               {{ 1,       1 }, "1s         1ns" },
+               {{ 0,       1 },            "1ns" },
+               {{ 0, 1000000 },            "1ms" },
+               {{ 0, 1000001 },      "1000001ns" },
+       };
+
+       setenv("TZ", "GMT", 1);
+       tzset();
+
+       for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
+               struct testcase t = testcases[i];
+               int r = strtimespec_relative(&t.ts, buf, sizeof(buf));
+               if (r) {
+                       fprintf(stderr, "Could not format '%s'\n", t.expected);
+                       rc = EXIT_FAILURE;
+               }
+
+               if (strcmp(buf, t.expected)) {
+                       fprintf(stderr, "#%02zu '%-20s' != '%-20s'\n", i, buf, t.expected);
+                       rc = EXIT_FAILURE;
+               }
+       }
+
+       return rc;
+}
+
 int main(int argc, char *argv[])
 {
        struct timespec ts = { 0 };
@@ -741,6 +845,8 @@ int main(int argc, char *argv[])
                return run_unittest_timestamp();
        else if (strcmp(argv[1], "--unittest-format") == 0)
                return run_unittest_format();
+       else if (strcmp(argv[1], "--unittest-format-relative") == 0)
+               return run_unittest_format_relative();
 
        if (strcmp(argv[1], "--timestamp") == 0) {
                usec_t usec = 0;
@@ -768,6 +874,9 @@ int main(int argc, char *argv[])
        strtimespec_iso(&ts, ISO_TIMESTAMP_DOT, buf, sizeof(buf));
        printf("Zone: '%s'\n", buf);
 
+       strtimespec_relative(&ts, buf, sizeof(buf));
+       printf("Rel:  '%s'\n", buf);
+
        return EXIT_SUCCESS;
 }
 
index 74bb17b8e12cdebbd471b62eafeba20f26aa284f..043b0c0e130fec264350ec09e2b721d1970726c5 100755 (executable)
@@ -23,4 +23,8 @@ ts_init_subtest "format"
 "$TS_HELPER_TIMEUTILS" --unittest-format 2> "$TS_ERRLOG" || ts_die "test failed"
 ts_finalize_subtest
 
+ts_init_subtest "format-relative"
+"$TS_HELPER_TIMEUTILS" --unittest-format-relative 2> "$TS_ERRLOG" || ts_die "test failed"
+ts_finalize_subtest
+
 ts_finalize