/* * Copyright (C) 2021 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" /* IMPORTANT: All of the format strings in each array must be padded with spaces * to be of equal lengths */ static const char *DATETIME_DATE_FMTS_[] = { " ", /* */ " %a ", /* Wed */ " %Y-%m-%d ", /* 1969-12-31 */ " %Y/%m/%d ", /* 1969/12/31 */ " %m-%d-%Y ", /* 12-31-1969 */ " %m-%d ", /* 12-31 */ " %m/%d/%Y ", /* 12/31/1969 */ " %m/%d ", /* 12/31 */ " %e %b %Y ", /* 31 Dec 1969 */ " %e %b ", /* 31 Dec */ " %d-%b-%Y ", /* 31-Dec-1969 */ " %d-%b ", /* 31-Dec */ " %d/%b/%Y ", /* 31/Dec/1969 */ " %d/%b ", /* 31/Dec */ " %a %e %b %Y ", /* Wed 31 Dec 1969 */ " %a %e %b ", /* Wed 31 Dec */ " %a %d-%b-%Y ", /* Wed 31-Dec-1969 */ " %a %d-%b ", /* Wed 31-Dec */ " %a %d/%b/%Y ", /* Wed 31/Dec/1969 */ " %a %d/%b ", /* Wed 31/Dec */ " %a, %e %b %Y ", /* Wed, 31 Dec 1969 */ " %a, %e %b ", /* Wed, 31 Dec */ " %a, %d-%b-%Y ", /* Wed, 31-Dec-1969 */ " %a, %d-%b ", /* Wed, 31-Dec */ " %a, %d/%b/%Y ", /* Wed, 31/Dec/1969 */ " %a, %d/%b ", /* Wed, 31/Dec */ " %b %e, %Y ", /* Dec 31, 1969 */ " %b %e ", /* Dec 31 */ " %b-%d-%Y ", /* Dec-31-1969 */ " %b-%d ", /* Dec-31 */ " %b/%d/%Y ", /* Dec/31/1969 */ " %b/%d ", /* Dec/31 */ " %a %b %e, %Y ", /* Wed Dec 31, 1969 */ " %a %b %e ", /* Wed Dec 31 */ " %a %b-%d-%Y ", /* Wed Dec-31-1969 */ " %a %b-%d ", /* Wed Dec-31 */ " %a %b/%d/%Y ", /* Wed Dec/31/1969 */ " %a %b/%d ", /* Wed Dec/31 */ " %a, %b %e, %Y ", /* Wed, Dec 31, 1969 */ " %a, %b %e ", /* Wed, Dec 31 */ " %a, %b-%d-%Y ", /* Wed, Dec-31-1969 */ " %a, %b-%d ", /* Wed, Dec-31 */ " %a, %b/%d/%Y ", /* Wed, Dec/31/1969 */ " %a, %b/%d ", /* Wed, Dec/31 */ }; static const char *DATETIME_TIME_FMTS_[] = { " %I:%M:%S %p ", /* 7:00:01 PM */ " %H:%M:%S ", /* 19:00:01 */ " %I:%M %p ", /* 7:00 PM */ " %H:%M ", /* 19:00 */ " %I%M%S %p ", /* 70001 PM */ " %H%M%S ", /* 190001 */ " %I%M %p ", /* 700 PM */ " %H%M ", /* 1900 */ }; static const char *DATETIME_MISC_FMTS_[] = { /* ISO 8601: 1969-12-31 delimited by "T" */ " %Y-%m-%dT%H:%M:%S ", " %Y-%m-%dT%H:%M ", /* ISO 8601: 19691231 delimited by "T" */ " %Y%m%dT%H%M%S ", " %Y%m%dT%H%M ", /* 19691231 */ " %Y%m%d %I%M%S %p ", " %Y%m%d %H%M%S ", " %Y%m%d %I%M %p ", " %Y%m%d %H%M ", /* Wed Dec 31 19:00:01 1969 */ " %a %b %d %H:%M:%S %Y ", }; 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 *arg_tm, time_t *arg_sec) { time_t now_sec; struct tm *now_tm; 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. */ now_sec = time(NULL); now_tm = localtime(&now_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_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; now_sec = time(NULL); now_tm = localtime(&now_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_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. */ now_sec = time(NULL); now_tm = localtime(&now_sec); 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(const char *input, time_t *arg_sec) { int date_fmt_len; int time_fmt_len; char *fmt_buf; size_t d; size_t t; char *end; struct tm arg_tm; date_fmt_len = strlen(DATETIME_DATE_FMTS_[0]); time_fmt_len = strlen(DATETIME_TIME_FMTS_[0]); fmt_buf = calloc(date_fmt_len + time_fmt_len + 1, sizeof(*fmt_buf)); if (fmt_buf == NULL) { fprintf(stderr, "Failed to allocate buffer: %s\n", strerror(errno)); return -1; } for (d = 0; d < sizeof(DATETIME_DATE_FMTS_) / sizeof(DATETIME_DATE_FMTS_[0]); ++d) { memcpy(fmt_buf, DATETIME_DATE_FMTS_[d], date_fmt_len); for (t = 0; t < sizeof(DATETIME_TIME_FMTS_) / sizeof(DATETIME_TIME_FMTS_[0]); ++t) { memcpy(fmt_buf + date_fmt_len, DATETIME_TIME_FMTS_[t], time_fmt_len); _datetime_reset_tm(&arg_tm); end = strptime(input, fmt_buf, &arg_tm); if (end != NULL && *end == '\0') { free(fmt_buf); _datetime_normalize(&arg_tm, arg_sec); return 0; } } } for (t = 0; t < sizeof(DATETIME_TIME_FMTS_) / sizeof(DATETIME_TIME_FMTS_[0]); ++t) { memcpy(fmt_buf, DATETIME_TIME_FMTS_[t], time_fmt_len); for (d = 0; d < sizeof(DATETIME_DATE_FMTS_) / sizeof(DATETIME_DATE_FMTS_[0]); ++d) { memcpy(fmt_buf + time_fmt_len, DATETIME_DATE_FMTS_[d], date_fmt_len); _datetime_reset_tm(&arg_tm); end = strptime(input, fmt_buf, &arg_tm); if (end != NULL && *end == '\0') { free(fmt_buf); _datetime_normalize(&arg_tm, arg_sec); return 0; } } } for (d = 0; d < sizeof(DATETIME_MISC_FMTS_) / sizeof(DATETIME_MISC_FMTS_[0]) ; ++d) { _datetime_reset_tm(&arg_tm); end = strptime(input, DATETIME_MISC_FMTS_[d], &arg_tm); if (end != NULL && *end == '\0') { free(fmt_buf); _datetime_normalize(&arg_tm, arg_sec); return 0; } } free(fmt_buf); fputs("Unknown date format\n", stderr); return -1; } static void _datetime_copy_trim_fmt(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 *out_sz, char **buf, size_t *i) { if (*buf == NULL) { *buf = calloc(strlen(fmts[0]), sizeof(**buf)); if (*buf == NULL) { fprintf(stderr, "Failed to allocate buffer: %s\n", strerror(errno)); return -1; } } if (*out == NULL) { *out_sz = strlen(fmts[0]); *out = calloc(*out_sz, sizeof(**out)); if (*out == NULL) { fprintf(stderr, "Failed to allocate buffer: %s\n", strerror(errno)); return -1; } } *buf[0] = '\0'; while (*buf[0] == '\0') { _datetime_copy_trim_fmt(*buf, fmts[*i]); ++*i; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" while (strftime(*out, *out_sz, *buf, tm) == 0) { #pragma GCC diagnostic pop ++*out_sz; *out = realloc(*out, *out_sz * sizeof(**out)); if (*out == NULL) { fprintf(stderr, "Failed to allocate buffer: %s\n", strerror(errno)); return -1; } } return 1; } int datetime_strftime_date(const struct tm *tm, char **out, size_t *out_sz, char **buf, size_t *i) { if (*i >= sizeof(DATETIME_DATE_FMTS_) / sizeof(DATETIME_DATE_FMTS_[0])){ return 0; } return _datetime_strftime(DATETIME_DATE_FMTS_, tm, out, out_sz, buf, i); } int datetime_strftime_time(const struct tm *tm, char **out, size_t *out_sz, char **buf, size_t *i) { if (*i >= sizeof(DATETIME_TIME_FMTS_) / sizeof(DATETIME_TIME_FMTS_[0])){ return 0; } return _datetime_strftime(DATETIME_TIME_FMTS_, tm, out, out_sz, buf, i); } int datetime_strftime_misc(const struct tm *tm, char **out, size_t *out_sz, char **buf, size_t *i) { if (*i >= sizeof(DATETIME_MISC_FMTS_) / sizeof(DATETIME_MISC_FMTS_[0])){ return 0; } return _datetime_strftime(DATETIME_MISC_FMTS_, tm, out, out_sz, buf, i); }