aboutsummaryrefslogtreecommitdiffstats
path: root/src/parse_time.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/parse_time.c')
-rw-r--r--src/parse_time.c280
1 files changed, 280 insertions, 0 deletions
diff --git a/src/parse_time.c b/src/parse_time.c
new file mode 100644
index 0000000..9ce97dc
--- /dev/null
+++ b/src/parse_time.c
@@ -0,0 +1,280 @@
+/**
+ * Copyright © 2015 Mattias Andrée <maandree@member.fsf.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#include "parse_time.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <limits.h>
+
+
+
+/**
+ * The number of seconds in a day.
+ */
+#define ONE_DAY (time_t)(24 * 60 * 60)
+
+/**
+ * Set errno and return.
+ *
+ * @return e:int The error number.
+ */
+#define FAIL(e) return errno = (e), -1
+
+
+
+/**
+ * The name of the process.
+ */
+extern char *argv0;
+
+
+
+/**
+ * Wrapper for `strtoll` and `strtol` that selects
+ * the appropriate function for `time_t`, does not
+ * allow whitespace or signs. It also forces the
+ * base to be 10, and will only return 0 on error,
+ * and will set `errno` on success.
+ *
+ * @param str Please see strtol(3), first parameter.
+ * @param end Please see strtol(3), second parameter.
+ * @return Please see strtol(3), only 0 may be
+ * returned at error.
+ *
+ * @throws 0 On success.
+ * @throws Please see strtol(3).
+ */
+static time_t
+strtotime(const char *str, const char **end)
+{
+ time_t rc;
+ long long int rcll;
+ long int rcl;
+
+ if (!isdigit(*str))
+ FAIL(EINVAL);
+
+ /* The outer if-statement is for when `time_t` is update to `long long int`.
+ * which is need to avoid the year 2038 problem. Be we have to use `strtol`
+ * if `time_t` is `long int`, otherwise we will not detect overflow. */
+
+ errno = 0;
+ if (sizeof(time_t) == sizeof(long long int)) {
+ rcll = strtoll(str, end, 10);
+ rc = (time_t)rcll;
+ } else {
+ rcl = strtol(str, end, 10);
+ rc = (time_t)rcl;
+ }
+
+ return errno ? 0 : rc;
+}
+
+
+/**
+ * Parse a time on the format HH:MM[:SS].
+ *
+ * @param str The time string.
+ * @param ts Output parameter for the POSIX time the string
+ * represents.
+ * @parma end Output parameter for the end of the parsing of `str`.
+ * @return 0 on success, -1 on error.
+ *
+ * @throws EINVAL `str` could not be parsed.
+ * @throws ERANGE `str` specifies a time beyond what can be stored.
+ */
+static int
+parse_time_time(const char *str, struct timespec *ts, const char **end)
+{
+#define MUL(a, b) if (a > timemax / (b)) FAIL(ERANGE); else a *= (b)
+#define ADD(a, b) if (a > timemax - (b)) FAIL(ERANGE); else a += (b)
+
+ char *end;
+ time_t t;
+ const time_t timemax = (sizeof(time_t) == sizeof(long long int)) ? LLONG_MAX : LONG_MAX;
+
+ memset(ts, 0, sizeof(*ts));
+
+ ts->tv_sec = strtotime(str, end), str = *end;
+ if (errno)
+ return -1;
+ /* Must not be restricted to 23, beyond 24 is legal. */
+ MUL(ts->tv_sec, (time_t)(60 * 60));
+
+ if (*str++ != ':')
+ FAIL(EINVAL);
+ t = strtotime(str, end), str = *end;
+ if (errno)
+ return -1;
+ if (t >= 60)
+ FAIL(EINVAL);
+ MUL(t, (time_t)60);
+ ADD(ts->tv_sec, t);
+
+ switch (*str++) {
+ case 0: return 0;
+ case ':': break;
+ default: FAIL(EINVAL);
+ }
+
+ t = strtotime(str, end), str = *end;
+ if (errno)
+ return -1;
+ /* Do not restrict to 59, or 60, there can be more than +1 leap second. */
+ ADD(ts->tv_sec, t);
+
+ return 0;
+}
+
+
+/**
+ * Parse a time with only a second-count.
+ *
+ * @param str The time string.
+ * @param ts Output parameter for the POSIX time the string
+ * represents.
+ * @parma end Output parameter for the end of the parsing of `str`.
+ * @return 0 on success, -1 on error.
+ *
+ * @throws EINVAL `str` could not be parsed.
+ * @throws ERANGE `str` specifies a time beyond what can be stored.
+ */
+static int
+parse_time_seconds(const char *str, struct timespec *ts, const char **end)
+{
+ size_t points = 0;
+
+ memset(ts, 0, sizeof(*ts));
+
+ ts->tv_sec = strtotime(str, end);
+ if (errno)
+ return -1;
+
+ return 0;
+}
+
+
+/**
+ * Trivial time-parsing.
+ *
+ * @param str The time string.
+ * @param ts Output parameter for the POSIX time the string
+ * represents. If the time is in the pasted, but
+ * within a day, one day is added. This is in case
+ * you are using a external parser that did not
+ * release they you wanted a future time but
+ * thought you wanted the time to be the closed or
+ * in the same day.
+ * @param clk Output parameter for the clock in which the
+ * returned time is specified.
+ * @return 0 on success, -1 on error.
+ *
+ * @throws EINVAL `str` could not be parsed.
+ * @throws ERANGE `str` specifies a time beyond what can be stored.
+ * @throws EDOM The specified the was over a day ago.
+ *
+ * @futuredirection Add environment variable that allows
+ * parsing through another program.
+ */
+int
+parse_time(const char *str, struct timespec *ts, clockid_t *clk)
+{
+ struct timespec now;
+ int plus = *str == '+';
+ char *start = str;
+ char *end;
+
+ /* Get current time and clock. */
+ clock_gettime(CLOCK_REALTIME, &now);
+ *clk = plus ? CLOCK_MONOTONIC : CLOCK_REALTIME;
+
+ /* Mañana? */
+ if (!strcmp("mañana")) { /* Do not documented. */
+ ts->tv_sec = now.tv_sec + ONE_DAY;
+ ts->tv_nsec = now.tv_nsec;
+ return 0;
+ }
+
+ /* HH:MM[:SS[.NNNNNNNNN]] or seconds? */
+ if (strchr(str, ':')) {
+ if (parse_time_time(str, ts, &end))
+ return -1;
+ ts->tv_sec += now.sec - (now.sec % ONE_DAY);
+ } else {
+ if (parse_time_seconds(str + plus, ts, &end))
+ return -1;
+ }
+ str = end;
+
+ /* Any fractions of a second? */
+ switch (*str) {
+ case 0: return 0;
+ case '.': break;
+ default: FAIL(EINVAL);
+ }
+
+ /* Parse up to nanosecond resolution. */
+ for (; isdigit(*str); points++) {
+ if (points < 9) {
+ ts->tv_nsec *= 10;
+ ts->tv_nsec += *str++ & 15;
+ } else if ((points == 10) && (*str >= '5')) {
+ ts->tv_nsec += 1;
+ }
+ }
+ if (ts->tv_nsec > 999999999L) {
+ ts->tv_sec += 1;
+ ts->tv_nsec = 0;
+ }
+
+ /* Check for error at end, and missing explicit UTC. */
+ if (*str) {
+ if (*clk == CLOCK_MONOTONIC)
+ FAIL(einval);
+ while (*str == ' ') str++;
+ if (!strcasecmp(str, "Z") && !strcasecmp(str, "UTC"))
+ FAIL(einval);
+ } else if (*clk == CLOCK_REALTIME) {
+ fprintf(stderr,
+ "%s: warning: parsing as UTC, you can avoid "
+ "this warning by adding a 'Z' at the end of "
+ "the time argument.\n", argv0);
+ }
+
+ /* Adjust the day? */
+ if (*clk == CLOCK_MONOTONIC)
+ return 0;
+ if (ts->tv_sec < now.tv_sec) { /* Ignore partial second. */
+ ts->tv_sec += ONE_DAY;
+ if (ts->tv_sec < now.tv_sec)
+ FAIL(EDOM);
+ if (!strchr(start, ':'))
+ fprintf(stderr,
+ "%s: warning: the specified time is in the past, "
+ "it is being adjust to be tomorrow instead.\n", argv0);
+ }
+
+ return 0;
+}
+