/* * Copyright (C) 2021, 2022 P. J. McDermott * * This file is part of @ * * @ 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. * * @ 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 @. If not, see . */ #define _XOPEN_SOURCE #include #include #include #include #include #include #include #include #include "datetime.h" #include "formats.h" static struct tm DATETIME_EPOCH_ = { .tm_sec = 0, .tm_min = 0, .tm_hour = 0, .tm_mday = 1, .tm_mon = 0, .tm_year = 70, .tm_wday = 4, .tm_yday = 0, .tm_isdst = -1, }; static void _datetime_buf_cpy_p(char *dst, const char *src) { for (; *src != '\0'; ++src, ++dst) { if (*src == '^') { *dst = ' '; } else { *dst = *src; } } *dst = '\0'; } static void _datetime_buf_cpy_f(char *dst, const char *src) { for (; *src != '\0'; ++src, ++dst) { if (*src == '^') { ++src; } *dst = *src; } *dst = '\0'; } static void _datetime_reset_tm(struct tm *tm) { tm->tm_year = INT_MIN; tm->tm_mon = INT_MIN; tm->tm_mday = INT_MIN; tm->tm_wday = INT_MIN; tm->tm_hour = INT_MIN; tm->tm_min = INT_MIN; tm->tm_sec = INT_MIN; tm->tm_isdst = INT_MIN; } static void _datetime_normalize(struct tm *now_tm, struct tm *arg_tm, time_t *arg_sec) { int wday; if (arg_tm->tm_sec == INT_MIN) { arg_tm->tm_sec = 0; } if (arg_tm->tm_mday == INT_MIN && arg_tm->tm_wday == INT_MIN) { /* No date specified; try today. */ arg_tm->tm_year = now_tm->tm_year; arg_tm->tm_mon = now_tm->tm_mon; arg_tm->tm_mday = now_tm->tm_mday; arg_tm->tm_wday = now_tm->tm_wday; *arg_sec = mktime(arg_tm); if (*arg_sec <= mktime(now_tm)) { /* Specified time already happened today; use tomorrow. * Adding the number of seconds in a day is a shortcut * that ignores leap seconds. One better method would * be to increment tm_mday % days in tm_mon, etc. */ *arg_sec += 60 * 60 * 24; now_tm = localtime(arg_sec); arg_tm->tm_year = now_tm->tm_year; arg_tm->tm_mon = now_tm->tm_mon; arg_tm->tm_mday = now_tm->tm_mday; *arg_sec = mktime(arg_tm); } } else if (arg_tm->tm_mday == INT_MIN) { /* Only a weekday specified; try tomorrow or next week. Uses * same shortcut as above. */ wday = arg_tm->tm_wday; arg_tm->tm_year = now_tm->tm_year; arg_tm->tm_mon = now_tm->tm_mon; arg_tm->tm_mday = now_tm->tm_mday; arg_tm->tm_wday = now_tm->tm_wday; if (wday <= now_tm->tm_wday) { wday += 7; } *arg_sec = mktime(arg_tm); *arg_sec += (60 * 60 * 24) * (wday - now_tm->tm_wday); } else if (arg_tm->tm_year == INT_MIN) { /* No year specified; try this year. */ arg_tm->tm_year = now_tm->tm_year; *arg_sec = mktime(arg_tm); if (*arg_sec <= mktime(now_tm)) { /* Specified time already happened this year; use next * year. */ ++arg_tm->tm_year; *arg_sec = mktime(arg_tm); } } else { *arg_sec = mktime(arg_tm); } } int datetime_parse(struct tm *now_tm, const char *input, time_t *arg_sec) { int date_fmt_len; int time_fmt_len; int misc_fmt_len; int fmt_len; char *fmt_buf; size_t d; size_t t; char *end; struct tm arg_tm; time_t sec; bool got; date_fmt_len = strlen(FORMATS_DATE[0]); time_fmt_len = strlen(FORMATS_TIME[0]); misc_fmt_len = strlen(FORMATS_MISC[0]); fmt_len = date_fmt_len + time_fmt_len; if (misc_fmt_len > fmt_len) { fmt_len = misc_fmt_len; } fmt_buf = calloc(fmt_len + 1, sizeof(*fmt_buf)); if (fmt_buf == NULL) { fprintf(stderr, "Failed to allocate buffer: %s\n", strerror(errno)); return -1; } sec = *arg_sec; /* GCC is dumb. */ got = false; for (t = 0; t < sizeof(FORMATS_TIME) / sizeof(FORMATS_TIME[0]) ; ++t) { _datetime_buf_cpy_p(fmt_buf, FORMATS_TIME[t]); _datetime_reset_tm(&arg_tm); end = strptime(input, fmt_buf, &arg_tm); if (end != NULL && *end == '\0') { _datetime_normalize(now_tm, &arg_tm, arg_sec); if (datetime_diff_epoch(*arg_sec) >= 0) { free(fmt_buf); return 0; } sec = *arg_sec; got = true; } } for (d = 0; d < sizeof(FORMATS_DATE) / sizeof(FORMATS_DATE[0]); ++d) { if (FORMATS_DATE[d][0] == '\0') { break; } _datetime_buf_cpy_p(fmt_buf, FORMATS_DATE[d]); for (t = 0; t < sizeof(FORMATS_TIME) / sizeof(FORMATS_TIME[0]); ++t) { _datetime_buf_cpy_p(fmt_buf + date_fmt_len, FORMATS_TIME[t]); _datetime_reset_tm(&arg_tm); end = strptime(input, fmt_buf, &arg_tm); if (end != NULL && *end == '\0') { _datetime_normalize(now_tm, &arg_tm, arg_sec); if (datetime_diff_epoch(*arg_sec) >= 0) { free(fmt_buf); return 0; } sec = *arg_sec; got = true; } } } for (t = 0; t < sizeof(FORMATS_TIME) / sizeof(FORMATS_TIME[0]); ++t) { _datetime_buf_cpy_p(fmt_buf, FORMATS_TIME[t]); for (d = 0; d < sizeof(FORMATS_DATE) / sizeof(FORMATS_DATE[0]); ++d) { if (FORMATS_DATE[d][0] == '\0') { break; } _datetime_buf_cpy_p(fmt_buf + time_fmt_len, FORMATS_DATE[d]); _datetime_reset_tm(&arg_tm); end = strptime(input, fmt_buf, &arg_tm); if (end != NULL && *end == '\0') { _datetime_normalize(now_tm, &arg_tm, arg_sec); if (datetime_diff_epoch(*arg_sec) >= 0) { free(fmt_buf); return 0; } sec = *arg_sec; got = true; } } } for (d = 0; d < sizeof(FORMATS_MISC) / sizeof(FORMATS_MISC[0]) ; ++d) { _datetime_buf_cpy_p(fmt_buf, FORMATS_MISC[d]); _datetime_reset_tm(&arg_tm); end = strptime(input, fmt_buf, &arg_tm); if (end != NULL && *end == '\0') { _datetime_normalize(now_tm, &arg_tm, arg_sec); if (datetime_diff_epoch(*arg_sec) >= 0) { free(fmt_buf); return 0; } sec = *arg_sec; got = true; } } if (got == true) { *arg_sec = sec; free(fmt_buf); return 0; } else { fputs("Unknown date format\n", stderr); free(fmt_buf); return -1; } } static void _datetime_copy_collapse(char *buf, const char *fmt) { int buf_i; int l; bool was_space; int i; buf_i = 0; l = strlen(fmt); was_space = true; for (i = 0; i < l; ++i) { if (isspace(fmt[i]) != 0) { if (was_space == false) { buf[buf_i++] = ' '; was_space = true; } } else { buf[buf_i++] = fmt[i]; was_space = false; } } if (buf_i > 0) { buf[buf_i - 1] = '\0'; } else { buf[0] = '\0'; } } static int _datetime_strftime(const char *fmts[], const struct tm *tm, char **out, size_t *buf_sz, char **buf, size_t *i) { int resized; if (*i == 0) { *buf_sz = strlen(fmts[0]) + 1; *buf = calloc(*buf_sz, sizeof(**buf)); if (*buf == NULL) { fprintf(stderr, "Failed to allocate buffer: %s\n", strerror(errno)); return -1; } *out = calloc(*buf_sz, sizeof(**out)); if (*out == NULL) { fprintf(stderr, "Failed to allocate buffer: %s\n", strerror(errno)); return -1; } } if (fmts[*i][0] == '\0') { ++*i; } _datetime_buf_cpy_f(*out, fmts[*i]); resized = 0; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" while (strftime(*buf, *buf_sz, *out, tm) == 0) { #pragma GCC diagnostic pop ++*buf_sz; *buf = realloc(*buf, *buf_sz * sizeof(**buf)); if (*buf == NULL) { fprintf(stderr, "Failed to allocate buffer: %s\n", strerror(errno)); return -1; } resized = 1; } if (resized > 0) { *out = realloc(*out, *buf_sz * sizeof(**out)); if (*out == NULL) { fprintf(stderr, "Failed to allocate buffer: %s\n", strerror(errno)); return -1; } } _datetime_copy_collapse(*out, *buf); ++*i; return 1; } int datetime_strftime_date(const struct tm *tm, char **out, size_t *buf_sz, char **buf, size_t *i) { if (*i >= sizeof(FORMATS_DATE) / sizeof(FORMATS_DATE[0])) { if (*out != NULL) { free(*out); } if (*buf != NULL) { free(*buf); } return 0; } return _datetime_strftime(FORMATS_DATE, tm, out, buf_sz, buf, i); } int datetime_strftime_time(const struct tm *tm, char **out, size_t *buf_sz, char **buf, size_t *i) { if (*i >= sizeof(FORMATS_TIME) / sizeof(FORMATS_TIME[0])) { if (*out != NULL) { free(*out); } if (*buf != NULL) { free(*buf); } return 0; } return _datetime_strftime(FORMATS_TIME, tm, out, buf_sz, buf, i); } int datetime_strftime_misc(const struct tm *tm, char **out, size_t *buf_sz, char **buf, size_t *i) { if (*i >= sizeof(FORMATS_MISC) / sizeof(FORMATS_MISC[0])) { if (*out != NULL) { free(*out); } if (*buf != NULL) { free(*buf); } return 0; } return _datetime_strftime(FORMATS_MISC, tm, out, buf_sz, buf, i); } double datetime_diff_epoch(time_t t) { time_t epoch; epoch = timegm(&DATETIME_EPOCH_); return difftime(t, epoch); }