/*
* 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 *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;
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 (t = 0; t < sizeof(DATETIME_TIME_FMTS_) /
sizeof(DATETIME_TIME_FMTS_[0]) ; ++t) {
_datetime_reset_tm(&arg_tm);
end = strptime(input, DATETIME_TIME_FMTS_[t], &arg_tm);
if (end != NULL && *end == '\0') {
free(fmt_buf);
_datetime_normalize(now_tm, &arg_tm, arg_sec);
return 0;
}
}
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(now_tm, &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(now_tm, &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(now_tm, &arg_tm, arg_sec);
return 0;
}
}
free(fmt_buf);
fputs("Unknown date format\n", stderr);
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]);
*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;
}
}
resized = 0;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
while (strftime(*buf, *buf_sz, fmts[*i], 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(DATETIME_DATE_FMTS_) / sizeof(DATETIME_DATE_FMTS_[0])){
if (*out != NULL) {
free(*out);
}
if (*buf != NULL) {
free(*buf);
}
return 0;
}
return _datetime_strftime(DATETIME_DATE_FMTS_, 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(DATETIME_TIME_FMTS_) / sizeof(DATETIME_TIME_FMTS_[0])){
if (*out != NULL) {
free(*out);
}
if (*buf != NULL) {
free(*buf);
}
return 0;
}
return _datetime_strftime(DATETIME_TIME_FMTS_, 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(DATETIME_MISC_FMTS_) / sizeof(DATETIME_MISC_FMTS_[0])){
if (*out != NULL) {
free(*out);
}
if (*buf != NULL) {
free(*buf);
}
return 0;
}
return _datetime_strftime(DATETIME_MISC_FMTS_, tm, out, buf_sz, buf, i);
}