--- /dev/null
+/*#############################################################################
+# #
+# 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 <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <limits.h>
+#include <time.h>
+
+#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;
+}