summaryrefslogtreecommitdiffstats
path: root/lib/timeutils.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/timeutils.c255
1 files changed, 227 insertions, 28 deletions
diff --git a/lib/timeutils.c b/lib/timeutils.c
index 34c7c8d..f53ec8f 100644
--- a/lib/timeutils.c
+++ b/lib/timeutils.c
@@ -161,6 +161,23 @@ static int parse_subseconds(const char *t, usec_t *usec)
return 0;
}
+static const char *parse_epoch_seconds(const char *t, struct tm *tm)
+{
+ int64_t s;
+ time_t st;
+ int f, c;
+
+ f = sscanf(t, "%"SCNd64"%n", &s, &c);
+ if (f < 1)
+ return NULL;
+ st = s;
+ if ((int64_t) st < s)
+ return NULL;
+ if (!localtime_r(&st, tm))
+ return NULL;
+ return t + c;
+}
+
static int parse_timestamp_reference(time_t x, const char *t, usec_t *usec)
{
static const struct {
@@ -254,7 +271,7 @@ static int parse_timestamp_reference(time_t x, const char *t, usec_t *usec)
goto finish;
} else if (t[0] == '@') {
- k = strptime(t + 1, "%s", &tm);
+ k = parse_epoch_seconds(t + 1, &tm);
if (k && *k == 0)
goto finish;
else if (k && parse_subseconds(k, &ret) == 0)
@@ -373,11 +390,13 @@ static int parse_timestamp_reference(time_t x, const char *t, usec_t *usec)
ret += (usec_t) x * USEC_PER_SEC;
+ if (minus > ret)
+ return -ERANGE;
+ if ((ret + plus) < ret)
+ return -ERANGE;
+
ret += plus;
- if (ret > minus)
- ret -= minus;
- else
- ret = 0;
+ ret -= minus;
*usec = ret;
@@ -448,8 +467,9 @@ int get_gmtoff(const struct tm *tp)
#endif
}
-static int format_iso_time(struct tm *tm, suseconds_t usec, int flags, char *buf, size_t bufsz)
+static int format_iso_time(const struct tm *tm, uint32_t nsec, int flags, char *buf, size_t bufsz)
{
+ uint32_t usec = nsec / NSEC_PER_USEC;
char *p = buf;
int len;
@@ -479,15 +499,28 @@ static int format_iso_time(struct tm *tm, suseconds_t usec, int flags, char *buf
p += len;
}
- if (flags & ISO_DOTUSEC) {
- len = snprintf(p, bufsz, ".%06"PRId64, (int64_t) usec);
+ if (flags & ISO_DOTNSEC) {
+ len = snprintf(p, bufsz, ".%09"PRIu32, nsec);
+ if (len < 0 || (size_t) len > bufsz)
+ goto err;
+ bufsz -= len;
+ p += len;
+
+ } else if (flags & ISO_COMMANSEC) {
+ len = snprintf(p, bufsz, ",%09"PRIu32, nsec);
+ if (len < 0 || (size_t) len > bufsz)
+ goto err;
+ bufsz -= len;
+ p += len;
+ } else if (flags & ISO_DOTUSEC) {
+ len = snprintf(p, bufsz, ".%06"PRIu32, usec);
if (len < 0 || (size_t) len > bufsz)
goto err;
bufsz -= len;
p += len;
} else if (flags & ISO_COMMAUSEC) {
- len = snprintf(p, bufsz, ",%06"PRId64, (int64_t) usec);
+ len = snprintf(p, bufsz, ",%06"PRIu32, usec);
if (len < 0 || (size_t) len > bufsz)
goto err;
bufsz -= len;
@@ -508,26 +541,37 @@ static int format_iso_time(struct tm *tm, suseconds_t usec, int flags, char *buf
return -1;
}
-/* timeval to ISO 8601 */
-int strtimeval_iso(struct timeval *tv, int flags, char *buf, size_t bufsz)
+/* timespec to ISO 8601 */
+int strtimespec_iso(const struct timespec *ts, int flags, char *buf, size_t bufsz)
{
struct tm tm;
struct tm *rc;
if (flags & ISO_GMTIME)
- rc = gmtime_r(&tv->tv_sec, &tm);
+ rc = gmtime_r(&ts->tv_sec, &tm);
else
- rc = localtime_r(&tv->tv_sec, &tm);
+ rc = localtime_r(&ts->tv_sec, &tm);
if (rc)
- return format_iso_time(&tm, tv->tv_usec, flags, buf, bufsz);
+ return format_iso_time(&tm, ts->tv_nsec, flags, buf, bufsz);
- warnx(_("time %"PRId64" is out of range."), (int64_t)(tv->tv_sec));
+ warnx(_("time %"PRId64" is out of range."), (int64_t)(ts->tv_sec));
return -1;
}
+/* timeval to ISO 8601 */
+int strtimeval_iso(const struct timeval *tv, int flags, char *buf, size_t bufsz)
+{
+ struct timespec ts = {
+ .tv_sec = tv->tv_sec,
+ .tv_nsec = tv->tv_usec * NSEC_PER_USEC,
+ };
+
+ return strtimespec_iso(&ts, flags, buf, bufsz);
+}
+
/* struct tm to ISO 8601 */
-int strtm_iso(struct tm *tm, int flags, char *buf, size_t bufsz)
+int strtm_iso(const struct tm *tm, int flags, char *buf, size_t bufsz)
{
return format_iso_time(tm, 0, flags, buf, bufsz);
}
@@ -592,6 +636,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)
{
@@ -625,6 +726,7 @@ static int run_unittest_timestamp(void)
{ "2012-09-22 16:34:22.012", 1348331662012000 },
{ "@1348331662" , 1348331662000000 },
{ "@1348331662.234567" , 1348331662234567 },
+ { "@0" , 0 },
{ "2012-09-22 16:34" , 1348331640000000 },
{ "2012-09-22" , 1348272000000000 },
{ "16:34:22" , 1674232462000000 },
@@ -662,45 +764,142 @@ static int run_unittest_timestamp(void)
return rc;
}
+static int run_unittest_format(void)
+{
+ int rc = EXIT_SUCCESS;
+ const struct timespec ts = {
+ .tv_sec = 1674180427,
+ .tv_nsec = 12345,
+ };
+ char buf[FORMAT_TIMESTAMP_MAX];
+ static const struct testcase {
+ int flags;
+ const char * const expected;
+ } testcases[] = {
+ { ISO_DATE, "2023-01-20" },
+ { ISO_TIME, "02:07:07" },
+ { ISO_TIMEZONE, "+00:00" },
+ { ISO_TIMESTAMP_T, "2023-01-20T02:07:07+00:00" },
+ { ISO_TIMESTAMP_COMMA_G, "2023-01-20 02:07:07,000012+00:00" },
+ { ISO_TIME | ISO_DOTNSEC, "02:07:07.000012345" },
+ };
+
+ setenv("TZ", "GMT", 1);
+ tzset();
+
+ for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
+ struct testcase t = testcases[i];
+ int r = strtimespec_iso(&ts, t.flags, 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;
+}
+
+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 timeval tv = { 0 };
+ struct timespec ts = { 0 };
char buf[ISO_BUFSIZ];
+ int r;
if (argc < 2) {
fprintf(stderr, "usage: %s [<time> [<usec>]] | [--timestamp <str>] | [--unittest-timestamp]\n", argv[0]);
exit(EXIT_FAILURE);
}
- if (strcmp(argv[1], "--unittest-timestamp") == 0) {
+ if (strcmp(argv[1], "--unittest-timestamp") == 0)
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;
- parse_timestamp(argv[2], &usec);
- tv.tv_sec = (time_t) (usec / 1000000);
- tv.tv_usec = usec % 1000000;
+ r = parse_timestamp(argv[2], &usec);
+ if (r)
+ errx(EXIT_FAILURE, "Can not parse '%s': %s", argv[2], strerror(-r));
+ ts.tv_sec = (time_t) (usec / USEC_PER_SEC);
+ ts.tv_nsec = (usec % USEC_PER_SEC) * NSEC_PER_USEC;
} else {
- tv.tv_sec = strtos64_or_err(argv[1], "failed to parse <time>");
+ ts.tv_sec = strtos64_or_err(argv[1], "failed to parse <time>");
if (argc == 3)
- tv.tv_usec = strtos64_or_err(argv[2], "failed to parse <usec>");
+ ts.tv_nsec = strtos64_or_err(argv[2], "failed to parse <usec>")
+ * NSEC_PER_USEC;
}
- strtimeval_iso(&tv, ISO_DATE, buf, sizeof(buf));
+ strtimespec_iso(&ts, ISO_DATE, buf, sizeof(buf));
printf("Date: '%s'\n", buf);
- strtimeval_iso(&tv, ISO_TIME, buf, sizeof(buf));
+ strtimespec_iso(&ts, ISO_TIME, buf, sizeof(buf));
printf("Time: '%s'\n", buf);
- strtimeval_iso(&tv, ISO_DATE | ISO_TIME | ISO_COMMAUSEC | ISO_T,
+ strtimespec_iso(&ts, ISO_DATE | ISO_TIME | ISO_COMMAUSEC | ISO_T,
buf, sizeof(buf));
printf("Full: '%s'\n", buf);
- strtimeval_iso(&tv, ISO_TIMESTAMP_DOT, buf, sizeof(buf));
+ 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;
}