From bcbc00c3dc9b016019a9e890c0061b340b6e1097 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Wed, 29 Oct 2025 15:43:30 +0000 Subject: [PATCH] time: Add function to parse timestamps from humans Signed-off-by: Michael Tremer --- Makefile.am | 1 + src/daemon/time.c | 222 ++++++++++++++++++++++++++++++++++++++++++++++ src/daemon/time.h | 5 ++ 3 files changed, 228 insertions(+) create mode 100644 src/daemon/time.c diff --git a/Makefile.am b/Makefile.am index a819632..e7ad419 100644 --- a/Makefile.am +++ b/Makefile.am @@ -197,6 +197,7 @@ dist_telemetryd_SOURCES = \ src/daemon/sources/uptime.c \ src/daemon/sources/uptime.h \ src/daemon/string.h \ + src/daemon/time.c \ src/daemon/time.h \ src/daemon/util.c \ src/daemon/util.h diff --git a/src/daemon/time.c b/src/daemon/time.c new file mode 100644 index 0000000..aa82d5a --- /dev/null +++ b/src/daemon/time.c @@ -0,0 +1,222 @@ +/*############################################################################# +# # +# telemetryd - The IPFire Telemetry Collection Service # +# Copyright (C) 2025 IPFire Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +#############################################################################*/ + +#include +#include + +#include "string.h" +#include "time.h" + +static int find_multiplier(char** p) { + int r; + + const struct multiplier { + const char* unit; + int multiplier; + } multipliers[] = { + { "seconds", 1 }, + { "second", 1 }, + { "sec", 1 }, + { "s", 1 }, + { "minutes", 60 }, + { "minute", 60 }, + { "min", 60 }, + { "m", 60 }, + { "hours", 3600 }, + { "hour", 3600 }, + { "hrs", 3600 }, + { "hr", 3600 }, + { "h", 3600 }, + { "days", 3600 * 24 }, + { "day", 3600 * 24 }, + { "d", 3600 * 24 }, + { "months", 3600 * 24 * 30 }, + { "month", 3600 * 24 * 30 }, + { "years", 3600 * 24 * 365 }, + { "year", 3600 * 24 * 365 }, + { NULL }, + }; + + // Find the multiplier + for (const struct multiplier* m = multipliers; m->unit; m++) { + r = td_string_consume(p, m->unit); + if (r < 0) + continue; + + // Return the multiplier + return m->multiplier; + } + + return -1; +} + +static int time_parse_relative(time_t* t, const char* arg, time_t now) { + unsigned long num = 0; + int direction = 0; + int multiplier; + int r; + + char* p = (char*)arg; + + // Look for a leading +/- + switch (*p) { + case '+': + direction = 1; + p++; + break; + + case '-': + direction = -1; + p++; + break; + + default: + break; + } + + // Parse the number + num = strtoul(p, &p, 10); + + // Handle errors + if (num == ULONG_MAX) + return -errno; + + // Skip any whitespace + while (*p && isspace(*p)) + p++; + + // Find the multiplier + multiplier = find_multiplier(&p); + if (multiplier < 0) + return -EINVAL; + + // Is there more to parse? + while (*p) { + // Skip whitespace + while (*p && isspace(*p)) + p++; + + // Break if we have consumed all the whitespace + if (!*p) + break; + + // If we already have a direction, there should not be anything left to parse + if (direction) + return -EINVAL; + + // We know how to handle "ago" + r = td_string_consume(&p, "ago"); + if (r == 0) + direction = -1; + + // Everything else I don't know + else + return -EINVAL; + } + + // Now let's do the maths + return now + (direction * num * multiplier); +} + +/* + Supported strings are: + * now + * today (time will be set to 00:00:00) + * yesterday (time will be set to 00:00:00) + * tomorrow (time will be set to 00:00:00) + * YYYY-MM-DD HH:MM:SS + * YYYY-MM-DD HH:MM + * YYYY-MM-DD + * HH:MM:SS (date will be set to today) + * HH:MM (date will be set to today, seconds to zero) +*/ + +int time_parse(time_t* t, const char* arg) { + struct tm tm = {}; + time_t now = -1; + char* p = NULL; + + // Fetch the current time + now = time(NULL); + if (now < 0) + return -errno; + + // Parse the current time + if (!gmtime_r(&now, &tm)) + return -errno; + + // Handle "now" + if (td_string_equals(arg, "now")) + goto DONE; + + // Handle "today" + if (td_string_equals(arg, "today")) { + tm.tm_hour = tm.tm_min = tm.tm_sec = 0; + goto DONE; + } + + // Handle "yesterday" + if (td_string_equals(arg, "yesterday")) { + tm.tm_hour = tm.tm_min = tm.tm_sec = 0; + tm.tm_mday--; + goto DONE; + } + + // Handle "tomorrow" + if (td_string_equals(arg, "tomorrow")) { + tm.tm_hour = tm.tm_min = tm.tm_sec = 0; + tm.tm_mday++; + goto DONE; + } + + // Handle YYYY-MM-DD HH:MM:SS + p = strptime(arg, "%Y-%m-%d %H:%M:%S", &tm); + if (p && !*p) + goto DONE; + + // Handle YYYY-MM-DD HH:MM + p = strptime(arg, "%Y-%m-%d %H:%M", &tm); + if (p && !*p) + goto DONE; + + // Handle YYYY-MM-DD + p = strptime(arg, "%Y-%m-%d", &tm); + if (p && !*p) + goto DONE; + + // Handle HH:MM:SS + p = strptime(arg, "%H:%M:%S", &tm); + if (p && !*p) + goto DONE; + + // Handle HH:MM + p = strptime(arg, "%H:%M", &tm); + if (p && !*p) + goto DONE; + + // Try parsing relative time + return time_parse_relative(t, arg, now); + +DONE: + // Return as integer + *t = mktime(&tm); + + return 0; +} diff --git a/src/daemon/time.h b/src/daemon/time.h index f22fbbc..2409c0b 100644 --- a/src/daemon/time.h +++ b/src/daemon/time.h @@ -21,6 +21,8 @@ #ifndef TELEMETRY_TIME_H #define TELEMETRY_TIME_H +#include + // Seconds to milliseconds #define SEC_TO_MSEC(s) ((s) * 1000UL) @@ -48,4 +50,7 @@ // Nanoseconds to microseconds #define NSEC_TO_USEC(ns) ((ns) / 1000UL) +// Parse a timestamp +int time_parse(time_t* t, const char* arg); + #endif /* TELEMETRY_TIME_H */ -- 2.47.3