shared/format-table: optionally print timestamps without "left"
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 22 Feb 2023 22:40:04 +0000 (23:40 +0100)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 22 Feb 2023 22:43:33 +0000 (23:43 +0100)
This just adds the base functionality and some unit tests.
With TABLE_TIMESTAMP_RELATIVE we print "5s ago" and "5s left",
with the new TABLE_TIMESTAMP_LEFT, we print "5s ago" but "5s".
This is more useful for cases where we generally only want to
print timestamps in the future.

src/basic/time-util.c
src/basic/time-util.h
src/shared/format-table.c
src/shared/format-table.h
src/test/test-time-util.c

index b700f364eff9be9aac033008209d88bb18b7e399..bae62ce411f9b06ef95a6b4551fb9fc109f2faf0 100644 (file)
@@ -418,7 +418,7 @@ char *format_timestamp_style(
         return buf;
 }
 
-char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
+char* format_timestamp_relative_full(char *buf, size_t l, usec_t t, bool implicit_left) {
         const char *s;
         usec_t n, d;
 
@@ -428,17 +428,17 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
         n = now(CLOCK_REALTIME);
         if (n > t) {
                 d = n - t;
-                s = "ago";
+                s = " ago";
         } else {
                 d = t - n;
-                s = "left";
+                s = implicit_left ? "" : " left";
         }
 
         if (d >= USEC_PER_YEAR) {
                 usec_t years = d / USEC_PER_YEAR;
                 usec_t months = (d % USEC_PER_YEAR) / USEC_PER_MONTH;
 
-                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s",
+                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s",
                                 years,
                                 years == 1 ? "year" : "years",
                                 months,
@@ -448,7 +448,7 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
                 usec_t months = d / USEC_PER_MONTH;
                 usec_t days = (d % USEC_PER_MONTH) / USEC_PER_DAY;
 
-                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s",
+                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s",
                                 months,
                                 months == 1 ? "month" : "months",
                                 days,
@@ -458,39 +458,39 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
                 usec_t weeks = d / USEC_PER_WEEK;
                 usec_t days = (d % USEC_PER_WEEK) / USEC_PER_DAY;
 
-                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s",
+                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s",
                                 weeks,
                                 weeks == 1 ? "week" : "weeks",
                                 days,
                                 days == 1 ? "day" : "days",
                                 s);
         } else if (d >= 2*USEC_PER_DAY)
-                (void) snprintf(buf, l, USEC_FMT " days %s", d / USEC_PER_DAY, s);
+                (void) snprintf(buf, l, USEC_FMT " days%s", d / USEC_PER_DAY,s);
         else if (d >= 25*USEC_PER_HOUR)
-                (void) snprintf(buf, l, "1 day " USEC_FMT "h %s",
+                (void) snprintf(buf, l, "1 day " USEC_FMT "h%s",
                                 (d - USEC_PER_DAY) / USEC_PER_HOUR, s);
         else if (d >= 6*USEC_PER_HOUR)
-                (void) snprintf(buf, l, USEC_FMT "h %s",
+                (void) snprintf(buf, l, USEC_FMT "h%s",
                                 d / USEC_PER_HOUR, s);
         else if (d >= USEC_PER_HOUR)
-                (void) snprintf(buf, l, USEC_FMT "h " USEC_FMT "min %s",
+                (void) snprintf(buf, l, USEC_FMT "h " USEC_FMT "min%s",
                                 d / USEC_PER_HOUR,
                                 (d % USEC_PER_HOUR) / USEC_PER_MINUTE, s);
         else if (d >= 5*USEC_PER_MINUTE)
-                (void) snprintf(buf, l, USEC_FMT "min %s",
+                (void) snprintf(buf, l, USEC_FMT "min%s",
                                 d / USEC_PER_MINUTE, s);
         else if (d >= USEC_PER_MINUTE)
-                (void) snprintf(buf, l, USEC_FMT "min " USEC_FMT "s %s",
+                (void) snprintf(buf, l, USEC_FMT "min " USEC_FMT "s%s",
                                 d / USEC_PER_MINUTE,
                                 (d % USEC_PER_MINUTE) / USEC_PER_SEC, s);
         else if (d >= USEC_PER_SEC)
-                (void) snprintf(buf, l, USEC_FMT "s %s",
+                (void) snprintf(buf, l, USEC_FMT "s%s",
                                 d / USEC_PER_SEC, s);
         else if (d >= USEC_PER_MSEC)
-                (void) snprintf(buf, l, USEC_FMT "ms %s",
+                (void) snprintf(buf, l, USEC_FMT "ms%s",
                                 d / USEC_PER_MSEC, s);
         else if (d > 0)
-                (void) snprintf(buf, l, USEC_FMT"us %s",
+                (void) snprintf(buf, l, USEC_FMT"us%s",
                                 d, s);
         else
                 (void) snprintf(buf, l, "now");
@@ -499,7 +499,7 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
         return buf;
 }
 
-char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
+charformat_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
         static const struct {
                 const char *suffix;
                 usec_t usec;
index c5ae0c98d493ece73adc5689765c418b7a23e184..0ed19d04adb8bbe58f15361ba19b5bee3a4e4b25 100644 (file)
@@ -124,9 +124,14 @@ struct timeval* timeval_store(struct timeval *tv, usec_t u);
 #define TIMEVAL_STORE(u) timeval_store(&(struct timeval) {}, (u))
 
 char* format_timestamp_style(char *buf, size_t l, usec_t t, TimestampStyle style) _warn_unused_result_;
-char* format_timestamp_relative(char *buf, size_t l, usec_t t) _warn_unused_result_;
+char* format_timestamp_relative_full(char *buf, size_t l, usec_t t, bool implicit_left) _warn_unused_result_;
 char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) _warn_unused_result_;
 
+_warn_unused_result_
+static inline char* format_timestamp_relative(char *buf, size_t l, usec_t t)  {
+        return format_timestamp_relative_full(buf, l, t, false);
+}
+
 _warn_unused_result_
 static inline char* format_timestamp(char *buf, size_t l, usec_t t) {
         return format_timestamp_style(buf, l, t, TIMESTAMP_PRETTY);
index ee45d4fd9e237596e8e53c2bca9beafa8ce25313..351a5ede118445f9cd1eb40726f73b08f954afad 100644 (file)
@@ -297,6 +297,7 @@ static size_t table_data_size(TableDataType type, const void *data) {
         case TABLE_TIMESTAMP:
         case TABLE_TIMESTAMP_UTC:
         case TABLE_TIMESTAMP_RELATIVE:
+        case TABLE_TIMESTAMP_LEFT:
         case TABLE_TIMESTAMP_DATE:
         case TABLE_TIMESPAN:
         case TABLE_TIMESPAN_MSEC:
@@ -896,6 +897,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
                 case TABLE_TIMESTAMP:
                 case TABLE_TIMESTAMP_UTC:
                 case TABLE_TIMESTAMP_RELATIVE:
+                case TABLE_TIMESTAMP_LEFT:
                 case TABLE_TIMESTAMP_DATE:
                 case TABLE_TIMESPAN:
                 case TABLE_TIMESPAN_MSEC:
@@ -1304,6 +1306,7 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
                 case TABLE_TIMESTAMP:
                 case TABLE_TIMESTAMP_UTC:
                 case TABLE_TIMESTAMP_RELATIVE:
+                case TABLE_TIMESTAMP_LEFT:
                 case TABLE_TIMESTAMP_DATE:
                         return CMP(a->timestamp, b->timestamp);
 
@@ -1537,11 +1540,14 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
         case TABLE_TIMESTAMP:
         case TABLE_TIMESTAMP_UTC:
         case TABLE_TIMESTAMP_RELATIVE:
+        case TABLE_TIMESTAMP_LEFT:
         case TABLE_TIMESTAMP_DATE: {
                 _cleanup_free_ char *p = NULL;
                 char *ret;
 
-                p = new(char, d->type == TABLE_TIMESTAMP_RELATIVE ? FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
+                p = new(char,
+                        IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_LEFT) ?
+                                FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
                 if (!p)
                         return NULL;
 
@@ -1552,7 +1558,9 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
                 else if (d->type == TABLE_TIMESTAMP_DATE)
                         ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_DATE);
                 else
-                        ret = format_timestamp_relative(p, FORMAT_TIMESTAMP_RELATIVE_MAX, d->timestamp);
+                        ret = format_timestamp_relative_full(
+                                        p, FORMAT_TIMESTAMP_RELATIVE_MAX, d->timestamp,
+                                        /* implicit_left= */  d->type == TABLE_TIMESTAMP_LEFT);
                 if (!ret)
                         return "-";
 
@@ -2589,6 +2597,7 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) {
         case TABLE_TIMESTAMP:
         case TABLE_TIMESTAMP_UTC:
         case TABLE_TIMESTAMP_RELATIVE:
+        case TABLE_TIMESTAMP_LEFT:
         case TABLE_TIMESTAMP_DATE:
                 if (d->timestamp == USEC_INFINITY)
                         return json_variant_new_null(ret);
index 97255f5aef24eb42b54d6af20d8cad091e2d57af..5a2b366b59c9b9e9f8c3ae59e1df660c3bb80680 100644 (file)
@@ -23,6 +23,7 @@ typedef enum TableDataType {
         TABLE_TIMESTAMP,
         TABLE_TIMESTAMP_UTC,
         TABLE_TIMESTAMP_RELATIVE,
+        TABLE_TIMESTAMP_LEFT,
         TABLE_TIMESTAMP_DATE,
         TABLE_TIMESPAN,
         TABLE_TIMESPAN_MSEC,
index 0fb76391fd1f4dab870a0db2ca4b102d1489384c..dee012fa2effb80608357ad4a824a15ef3515d1c 100644 (file)
@@ -401,6 +401,74 @@ TEST(FORMAT_TIMESTAMP) {
         }
 }
 
+TEST(format_timestamp_relative_full) {
+        char buf[CONST_MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESPAN_MAX)];
+        usec_t x;
+
+        /* Years and months */
+        x = now(CLOCK_REALTIME) - (1*USEC_PER_YEAR + 1*USEC_PER_MONTH);
+        assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, true));
+        log_debug("%s", buf);
+        assert_se(streq(buf, "1 year 1 month ago"));
+
+        x = now(CLOCK_REALTIME) - (1*USEC_PER_YEAR + 2*USEC_PER_MONTH);
+        assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, true));
+        log_debug("%s", buf);
+        assert_se(streq(buf, "1 year 2 months ago"));
+
+        x = now(CLOCK_REALTIME) - (2*USEC_PER_YEAR + 1*USEC_PER_MONTH);
+        assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, true));
+        log_debug("%s", buf);
+        assert_se(streq(buf, "2 years 1 month ago"));
+
+        x = now(CLOCK_REALTIME) - (2*USEC_PER_YEAR + 2*USEC_PER_MONTH);
+        assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, true));
+        log_debug("%s", buf);
+        assert_se(streq(buf, "2 years 2 months ago"));
+
+        /* Months and days */
+        x = now(CLOCK_REALTIME) - (1*USEC_PER_MONTH + 1*USEC_PER_DAY);
+        assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, true));
+        log_debug("%s", buf);
+        assert_se(streq(buf, "1 month 1 day ago"));
+
+        x = now(CLOCK_REALTIME) - (1*USEC_PER_MONTH + 2*USEC_PER_DAY);
+        assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, true));
+        log_debug("%s", buf);
+        assert_se(streq(buf, "1 month 2 days ago"));
+
+        x = now(CLOCK_REALTIME) - (2*USEC_PER_MONTH + 1*USEC_PER_DAY);
+        assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, true));
+        log_debug("%s", buf);
+        assert_se(streq(buf, "2 months 1 day ago"));
+
+        x = now(CLOCK_REALTIME) - (2*USEC_PER_MONTH + 2*USEC_PER_DAY);
+        assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, true));
+        log_debug("%s", buf);
+        assert_se(streq(buf, "2 months 2 days ago"));
+
+        /* Weeks and days */
+        x = now(CLOCK_REALTIME) - (1*USEC_PER_WEEK + 1*USEC_PER_DAY);
+        assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, true));
+        log_debug("%s", buf);
+        assert_se(streq(buf, "1 week 1 day ago"));
+
+        x = now(CLOCK_REALTIME) - (1*USEC_PER_WEEK + 2*USEC_PER_DAY);
+        assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, true));
+        log_debug("%s", buf);
+        assert_se(streq(buf, "1 week 2 days ago"));
+
+        x = now(CLOCK_REALTIME) - (2*USEC_PER_WEEK + 1*USEC_PER_DAY);
+        assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, true));
+        log_debug("%s", buf);
+        assert_se(streq(buf, "2 weeks 1 day ago"));
+
+        x = now(CLOCK_REALTIME) - (2*USEC_PER_WEEK + 2*USEC_PER_DAY);
+        assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, true));
+        log_debug("%s", buf);
+        assert_se(streq(buf, "2 weeks 2 days ago"));
+}
+
 TEST(format_timestamp_relative) {
         char buf[CONST_MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESPAN_MAX)];
         usec_t x;