diff --git a/src/basic/time-util.c b/src/basic/time-util.c index d5c10571c9..b700f364ef 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -308,54 +308,48 @@ char *format_timestamp_style( }; struct tm tm; + bool utc, us; time_t sec; size_t n; - bool utc = false, us = false; - int r; assert(buf); + assert(style >= 0); + assert(style < _TIMESTAMP_STYLE_MAX); - switch (style) { - case TIMESTAMP_PRETTY: - case TIMESTAMP_UNIX: - break; - case TIMESTAMP_US: - us = true; - break; - case TIMESTAMP_UTC: - utc = true; - break; - case TIMESTAMP_US_UTC: - us = true; - utc = true; - break; - default: - return NULL; - } - - if (l < (size_t) (3 + /* week day */ - 1 + 10 + /* space and date */ - 1 + 8 + /* space and time */ - (us ? 1 + 6 : 0) + /* "." and microsecond part */ - 1 + 1 + /* space and shortest possible zone */ - 1)) - return NULL; /* Not enough space even for the shortest form. */ if (!timestamp_is_set(t)) return NULL; /* Timestamp is unset */ if (style == TIMESTAMP_UNIX) { - r = snprintf(buf, l, "@" USEC_FMT, t / USEC_PER_SEC); /* round down µs → s */ - if (r < 0 || (size_t) r >= l) - return NULL; /* Doesn't fit */ + if (l < (size_t) (1 + 1 + 1)) + return NULL; /* not enough space for even the shortest of forms */ - return buf; + return snprintf_ok(buf, l, "@" USEC_FMT, t / USEC_PER_SEC); /* round down µs → s */ } + utc = IN_SET(style, TIMESTAMP_UTC, TIMESTAMP_US_UTC, TIMESTAMP_DATE); + us = IN_SET(style, TIMESTAMP_US, TIMESTAMP_US_UTC); + + if (l < (size_t) (3 + /* week day */ + 1 + 10 + /* space and date */ + style == TIMESTAMP_DATE ? 0 : + (1 + 8 + /* space and time */ + (us ? 1 + 6 : 0) + /* "." and microsecond part */ + 1 + (utc ? 3 : 1)) + /* space and shortest possible zone */ + 1)) + return NULL; /* Not enough space even for the shortest form. */ + /* Let's not format times with years > 9999 */ if (t > USEC_TIMESTAMP_FORMATTABLE_MAX) { - assert(l >= STRLEN("--- XXXX-XX-XX XX:XX:XX") + 1); - strcpy(buf, "--- XXXX-XX-XX XX:XX:XX"); - return buf; + static const char* const xxx[_TIMESTAMP_STYLE_MAX] = { + [TIMESTAMP_PRETTY] = "--- XXXX-XX-XX XX:XX:XX", + [TIMESTAMP_US] = "--- XXXX-XX-XX XX:XX:XX.XXXXXX", + [TIMESTAMP_UTC] = "--- XXXX-XX-XX XX:XX:XX UTC", + [TIMESTAMP_US_UTC] = "--- XXXX-XX-XX XX:XX:XX.XXXXXX UTC", + [TIMESTAMP_DATE] = "--- XXXX-XX-XX", + }; + + assert(l >= strlen(xxx[style]) + 1); + return strcpy(buf, xxx[style]); } sec = (time_t) (t / USEC_PER_SEC); /* Round down */ @@ -367,6 +361,14 @@ char *format_timestamp_style( assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays)); memcpy(buf, weekdays[tm.tm_wday], 4); + if (style == TIMESTAMP_DATE) { + /* Special format string if only date should be shown. */ + if (strftime(buf + 3, l - 3, " %Y-%m-%d", &tm) <= 0) + return NULL; /* Doesn't fit */ + + return buf; + } + /* Add the main components */ if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0) return NULL; /* Doesn't fit */ diff --git a/src/basic/time-util.h b/src/basic/time-util.h index c98f95a530..c5ae0c98d4 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -35,6 +35,7 @@ typedef enum TimestampStyle { TIMESTAMP_UTC, TIMESTAMP_US_UTC, TIMESTAMP_UNIX, + TIMESTAMP_DATE, _TIMESTAMP_STYLE_MAX, _TIMESTAMP_STYLE_INVALID = -EINVAL, } TimestampStyle; diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index 51b2bde1f4..c77e2e8fd9 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -244,7 +244,7 @@ static int print_status_info(StatusInfo *i) { if (timestamp_is_set(i->firmware_date)) { r = table_add_many(table, TABLE_FIELD, "Firmware Date", - TABLE_TIMESTAMP, i->firmware_date); + TABLE_TIMESTAMP_DATE, i->firmware_date); if (r < 0) return table_log_add_error(r); } diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 4795e1ad80..fbac0f7498 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -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_DATE: case TABLE_TIMESPAN: case TABLE_TIMESPAN_MSEC: return sizeof(usec_t); @@ -894,6 +895,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_DATE: case TABLE_TIMESPAN: case TABLE_TIMESPAN_MSEC: buffer.usec = va_arg(ap, usec_t); @@ -1300,6 +1302,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_DATE: return CMP(a->timestamp, b->timestamp); case TABLE_TIMESPAN: @@ -1530,7 +1533,8 @@ 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_RELATIVE: + case TABLE_TIMESTAMP_DATE: { _cleanup_free_ char *p = NULL; char *ret; @@ -1542,6 +1546,8 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp); else if (d->type == TABLE_TIMESTAMP_UTC) ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_UTC); + 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); if (!ret) @@ -2578,6 +2584,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_DATE: if (d->timestamp == USEC_INFINITY) return json_variant_new_null(ret); diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 0e8aecffe6..0817846be2 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -23,6 +23,7 @@ typedef enum TableDataType { TABLE_TIMESTAMP, TABLE_TIMESTAMP_UTC, TABLE_TIMESTAMP_RELATIVE, + TABLE_TIMESTAMP_DATE, TABLE_TIMESPAN, TABLE_TIMESPAN_MSEC, TABLE_SIZE, diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c index 6b546fb9f5..33d5b89729 100644 --- a/src/test/test-time-util.c +++ b/src/test/test-time-util.c @@ -365,6 +365,11 @@ TEST(format_timestamp) { assert_se(parse_timestamp(buf, &y) >= 0); assert_se(x == y); + assert_se(format_timestamp_style(buf, sizeof(buf), x, TIMESTAMP_DATE)); + log_debug("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(y > usec_sub_unsigned(x, 2 * USEC_PER_DAY) && y < usec_add(x, 2* USEC_PER_DAY)); + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); log_debug("%s", buf); assert_se(parse_timestamp(buf, &y) >= 0); @@ -466,28 +471,42 @@ TEST(format_timestamp_relative) { assert_se(streq(buf, "2 weeks 2 days ago")); } -static void test_format_timestamp_utc_one(usec_t val, const char *result) { +static void test_format_timestamp_one(usec_t val, TimestampStyle style, const char *result) { char buf[FORMAT_TIMESTAMP_MAX]; const char *t; - t = format_timestamp_style(buf, sizeof(buf), val, TIMESTAMP_UTC); + t = format_timestamp_style(buf, sizeof(buf), val, style); assert_se(streq_ptr(t, result)); } -TEST(format_timestamp_utc) { - test_format_timestamp_utc_one(0, NULL); - test_format_timestamp_utc_one(1, "Thu 1970-01-01 00:00:00 UTC"); - test_format_timestamp_utc_one(USEC_PER_SEC, "Thu 1970-01-01 00:00:01 UTC"); +TEST(format_timestamp_range) { + test_format_timestamp_one(0, TIMESTAMP_UTC, NULL); + test_format_timestamp_one(0, TIMESTAMP_DATE, NULL); + test_format_timestamp_one(0, TIMESTAMP_US_UTC, NULL); + + test_format_timestamp_one(1, TIMESTAMP_UTC, "Thu 1970-01-01 00:00:00 UTC"); + test_format_timestamp_one(1, TIMESTAMP_DATE, "Thu 1970-01-01"); + test_format_timestamp_one(1, TIMESTAMP_US_UTC, "Thu 1970-01-01 00:00:00.000001 UTC"); + + test_format_timestamp_one(USEC_PER_SEC, TIMESTAMP_UTC, "Thu 1970-01-01 00:00:01 UTC"); + test_format_timestamp_one(USEC_PER_SEC, TIMESTAMP_DATE, "Thu 1970-01-01"); + test_format_timestamp_one(USEC_PER_SEC, TIMESTAMP_US_UTC, "Thu 1970-01-01 00:00:01.000000 UTC"); #if SIZEOF_TIME_T == 8 - test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX, "Thu 9999-12-30 23:59:59 UTC"); - test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, "--- XXXX-XX-XX XX:XX:XX"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_UTC, "Thu 9999-12-30 23:59:59 UTC"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_DATE, "Thu 9999-12-30"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_UTC, "--- XXXX-XX-XX XX:XX:XX UTC"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_US_UTC, "--- XXXX-XX-XX XX:XX:XX.XXXXXX UTC"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_DATE, "--- XXXX-XX-XX"); #elif SIZEOF_TIME_T == 4 - test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX, "Tue 2038-01-19 03:14:07 UTC"); - test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, "--- XXXX-XX-XX XX:XX:XX"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_UTC, "Tue 2038-01-19 03:14:07 UTC"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_DATE, "Tue 2038-01-19"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_UTC, "--- XXXX-XX-XX XX:XX:XX UTC"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_US_UTC, "--- XXXX-XX-XX XX:XX:XX.XXXXXX UTC"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_DATE, "--- XXXX-XX-XX"); #endif - test_format_timestamp_utc_one(USEC_INFINITY, NULL); + test_format_timestamp_one(USEC_INFINITY, TIMESTAMP_UTC, NULL); } TEST(deserialize_dual_timestamp) {