summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/datetime.c319
-rw-r--r--src/datetime.h3
-rw-r--r--src/formats.h255
-rw-r--r--src/i18n.h11
-rw-r--r--src/local.mk4
-rw-r--r--src/main.c192
6 files changed, 575 insertions, 209 deletions
diff --git a/src/datetime.c b/src/datetime.c
index 079266b..72f0219 100644
--- a/src/datetime.c
+++ b/src/datetime.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 P. J. McDermott
+ * Copyright (C) 2021, 2022 P. J. McDermott
*
* This file is part of @
*
@@ -18,6 +18,9 @@
*/
#define _XOPEN_SOURCE
+/* glibc requires these for timegm(): */
+#define _DEFAULT_SOURCE
+#define _BSD_SOURCE
#include <ctype.h>
#include <errno.h>
@@ -28,80 +31,33 @@
#include <string.h>
#include <time.h>
#include "datetime.h"
+#include "formats.h"
+#include "i18n.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_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)
@@ -117,58 +73,64 @@ _datetime_reset_tm(struct tm *tm)
}
static void
-_datetime_normalize(struct tm *arg_tm, time_t *arg_sec)
+_datetime_normalize(struct tm *now_tm, struct tm *arg_tm, time_t *arg_sec)
{
- time_t now_sec;
- struct tm *now_tm;
- int wday;
+ int days_in_mon[12] = {31, 28, 31, 30, 31, 30,
+ 31, 31, 30, 31, 30, 31};
+
+ if (now_tm->tm_year % 4 == 0 && (now_tm->tm_year % 100 != 0 ||
+ now_tm->tm_year % 400 == 0)) {
+ days_in_mon[1] = 29;
+ }
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)) {
+ if (difftime(*arg_sec, mktime(now_tm)) <= 0) {
/* 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_tm->tm_mday;
+ arg_tm->tm_wday = (arg_tm->tm_wday + 1) % 7;
+ if (arg_tm->tm_mday > days_in_mon[arg_tm->tm_mon]) {
+ arg_tm->tm_mday = 1;
+ ++arg_tm->tm_mon;
+ if (arg_tm->tm_mon >= 12) {
+ arg_tm->tm_mon = 0;
+ ++arg_tm->tm_year;
+ }
+ }
*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);
+ /* Only a weekday specified; try tomorrow or next week. */
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;
+ if (arg_tm->tm_wday <= now_tm->tm_wday) {
+ arg_tm->tm_mday += 7;
+ }
+ arg_tm->tm_mday += arg_tm->tm_wday - now_tm->tm_wday;
+ if (arg_tm->tm_mday > days_in_mon[arg_tm->tm_mon]) {
+ arg_tm->tm_mday -= days_in_mon[arg_tm->tm_mon];
+ ++arg_tm->tm_mon;
+ if (arg_tm->tm_mon >= 12) {
+ arg_tm->tm_mon = 0;
+ ++arg_tm->tm_year;
+ }
}
*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)) {
+ if (difftime(*arg_sec, mktime(now_tm)) <= 0) {
/* Specified time already happened this year; use next
* year. */
++arg_tm->tm_year;
@@ -180,71 +142,118 @@ _datetime_normalize(struct tm *arg_tm, time_t *arg_sec)
}
int
-datetime_parse(const char *input, time_t *arg_sec)
+datetime_parse(struct tm *now_tm, const char *input,
+ struct tm *arg_tm, 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(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));
+ 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",
+ 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') {
+ 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 (arg_tm->tm_year >= 0) {
free(fmt_buf);
- _datetime_normalize(&arg_tm, arg_sec);
return 0;
}
+ sec = *arg_sec;
+ got = true;
}
}
- 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);
+ 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') {
- free(fmt_buf);
- _datetime_normalize(&arg_tm, arg_sec);
- return 0;
+ _datetime_normalize(now_tm, arg_tm, arg_sec);
+ if (arg_tm->tm_year >= 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 (arg_tm->tm_year >= 0) {
+ free(fmt_buf);
+ return 0;
+ }
+ sec = *arg_sec;
+ got = true;
}
}
}
- 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);
+ 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') {
- free(fmt_buf);
- _datetime_normalize(&arg_tm, arg_sec);
- return 0;
+ _datetime_normalize(now_tm, arg_tm, arg_sec);
+ if (arg_tm->tm_year >= 0) {
+ free(fmt_buf);
+ return 0;
+ }
+ sec = *arg_sec;
+ got = true;
}
}
- free(fmt_buf);
- fputs("Unknown date format\n", stderr);
- return -1;
+ 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
@@ -283,30 +292,36 @@ _datetime_strftime(const char *fmts[], const struct tm *tm, char **out,
int resized;
if (*i == 0) {
- *buf_sz = strlen(fmts[0]);
+ *buf_sz = strlen(fmts[0]) + 1;
*buf = calloc(*buf_sz, sizeof(**buf));
if (*buf == NULL) {
- fprintf(stderr, "Failed to allocate buffer: %s\n",
+ 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",
+ 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, fmts[*i], tm) == 0) {
+ 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",
+ fprintf(stderr, _("Failed to allocate buffer: %s\n"),
strerror(errno));
return -1;
}
@@ -315,7 +330,7 @@ _datetime_strftime(const char *fmts[], const struct tm *tm, char **out,
if (resized > 0) {
*out = realloc(*out, *buf_sz * sizeof(**out));
if (*out == NULL) {
- fprintf(stderr, "Failed to allocate buffer: %s\n",
+ fprintf(stderr, _("Failed to allocate buffer: %s\n"),
strerror(errno));
return -1;
}
@@ -331,7 +346,7 @@ 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 (*i >= sizeof(FORMATS_DATE) / sizeof(FORMATS_DATE[0])) {
if (*out != NULL) {
free(*out);
}
@@ -341,14 +356,14 @@ datetime_strftime_date(const struct tm *tm, char **out, size_t *buf_sz,
return 0;
}
- return _datetime_strftime(DATETIME_DATE_FMTS_, tm, out, buf_sz, buf, i);
+ 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(DATETIME_TIME_FMTS_) / sizeof(DATETIME_TIME_FMTS_[0])){
+ if (*i >= sizeof(FORMATS_TIME) / sizeof(FORMATS_TIME[0])) {
if (*out != NULL) {
free(*out);
}
@@ -358,14 +373,14 @@ datetime_strftime_time(const struct tm *tm, char **out, size_t *buf_sz,
return 0;
}
- return _datetime_strftime(DATETIME_TIME_FMTS_, tm, out, buf_sz, buf, i);
+ 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(DATETIME_MISC_FMTS_) / sizeof(DATETIME_MISC_FMTS_[0])){
+ if (*i >= sizeof(FORMATS_MISC) / sizeof(FORMATS_MISC[0])) {
if (*out != NULL) {
free(*out);
}
@@ -375,5 +390,5 @@ datetime_strftime_misc(const struct tm *tm, char **out, size_t *buf_sz,
return 0;
}
- return _datetime_strftime(DATETIME_MISC_FMTS_, tm, out, buf_sz, buf, i);
+ return _datetime_strftime(FORMATS_MISC, tm, out, buf_sz, buf, i);
}
diff --git a/src/datetime.h b/src/datetime.h
index edff8f7..1094cf8 100644
--- a/src/datetime.h
+++ b/src/datetime.h
@@ -23,7 +23,8 @@
#include <time.h>
int
-datetime_parse(const char *input, time_t *arg_sec);
+datetime_parse(struct tm *now_tm, const char *input,
+ struct tm *arg_tm, time_t *arg_sec);
int
datetime_strftime_date(const struct tm *tm, char **out, size_t *out_sz,
diff --git a/src/formats.h b/src/formats.h
new file mode 100644
index 0000000..c88f731
--- /dev/null
+++ b/src/formats.h
@@ -0,0 +1,255 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef FORMATS_H_
+#define FORMATS_H_
+
+/*
+ * IMPORTANT:
+ *
+ * All of the format strings in each array must be padded with spaces to be of
+ * equal lengths.
+ *
+ * Conversion specifications are used by both strptime() and strftime(). As
+ * required by the former function, there shall be "white-space or other non-
+ * alphanumeric characters between any two conversion specifications unless all
+ * of the adjacent conversion specifications convert a known, fixed number of
+ * characters." [POSIX] The "^" character may be used to separate conversion
+ * specifications that should be separated (by a space) when parsing and
+ * adjacent when formatting for printing. For example, " %I^%M %p " will be
+ * changed to " %I %M %p " when parsing and to " %I%M %p " when formatting for
+ * printing.
+ */
+
+static const char *FORMATS_DATE[] = {
+ " %a ", /* Wed */
+ " %a. ", /* Wed. */
+ " %Y-%m-%d ", /* 1969-12-31 */
+ " %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 */
+ " %m.%d.%Y ", /* 12.31.1969 */
+ " %m.%d ", /* 12.31 */
+ " %e %b %Y ", /* 31 Dec 1969 */
+ " %e %b. %Y ", /* 31 Dec. 1969 */
+ " %e %b ", /* 31 Dec */
+ " %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. %Y ", /* Wed 31 Dec. 1969 */
+ " %a %e %b ", /* Wed 31 Dec */
+ " %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. %Y ", /* Wed. 31 Dec. 1969 */
+ " %a. %e %b ", /* Wed. 31 Dec */
+ " %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. %Y ", /* Wed, 31 Dec. 1969 */
+ " %a, %e %b ", /* Wed, 31 Dec */
+ " %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, %Y ", /* Dec. 31, 1969 */
+ " %b %e ", /* Dec 31 */
+ " %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, %Y ", /* Wed Dec. 31, 1969 */
+ " %a %b %e ", /* Wed Dec 31 */
+ " %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, %Y ", /* Wed. Dec. 31, 1969 */
+ " %a. %b %e ", /* Wed. Dec 31 */
+ " %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, %Y ", /* Wed, Dec. 31, 1969 */
+ " %a, %b %e ", /* Wed, Dec 31 */
+ " %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 */
+ "", /* End of parsing formats */
+ " %A ", /* Wednesday */
+ " %A %e %b %Y ", /* Wednesday 31 Dec 1969 */
+ " %A %e %b. %Y ", /* Wednesday 31 Dec. 1969 */
+ " %A %e %b ", /* Wednesday 31 Dec */
+ " %A %e %b. ", /* Wednesday 31 Dec. */
+ " %A %d-%b-%Y ", /* Wednesday 31-Dec-1969 */
+ " %A %d-%b ", /* Wednesday 31-Dec */
+ " %A %d/%b/%Y ", /* Wednesday 31/Dec/1969 */
+ " %A %d/%b ", /* Wednesday 31/Dec */
+ " %A, %e %b %Y ", /* Wednesday, 31 Dec 1969 */
+ " %A, %e %b. %Y ", /* Wednesday, 31 Dec. 1969 */
+ " %A, %e %b ", /* Wednesday, 31 Dec */
+ " %A, %e %b. ", /* Wednesday, 31 Dec. */
+ " %A, %d-%b-%Y ", /* Wednesday, 31-Dec-1969 */
+ " %A, %d-%b ", /* Wednesday, 31-Dec */
+ " %A, %d/%b/%Y ", /* Wednesday, 31/Dec/1969 */
+ " %A, %d/%b ", /* Wednesday, 31/Dec */
+ " %A %b %e, %Y ", /* Wednesday Dec 31, 1969 */
+ " %A %b. %e, %Y ", /* Wednesday Dec. 31, 1969 */
+ " %A %b %e ", /* Wednesday Dec 31 */
+ " %A %b. %e ", /* Wednesday Dec. 31 */
+ " %A %b-%d-%Y ", /* Wednesday Dec-31-1969 */
+ " %A %b-%d ", /* Wednesday Dec-31 */
+ " %A %b/%d/%Y ", /* Wednesday Dec/31/1969 */
+ " %A %b/%d ", /* Wednesday Dec/31 */
+ " %A, %b %e, %Y ", /* Wednesday, Dec 31, 1969 */
+ " %A, %b. %e, %Y ", /* Wednesday, Dec. 31, 1969 */
+ " %A, %b %e ", /* Wednesday, Dec 31 */
+ " %A, %b. %e ", /* Wednesday, Dec. 31 */
+ " %A, %b-%d-%Y ", /* Wednesday, Dec-31-1969 */
+ " %A, %b-%d ", /* Wednesday, Dec-31 */
+ " %A, %b/%d/%Y ", /* Wednesday, Dec/31/1969 */
+ " %A, %b/%d ", /* Wednesday, Dec/31 */
+ " %e %B %Y ", /* 31 December 1969 */
+ " %e %B ", /* 31 December */
+ " %d-%B-%Y ", /* 31-December-1969 */
+ " %d-%B ", /* 31-December */
+ " %d/%B/%Y ", /* 31/December/1969 */
+ " %d/%B ", /* 31/December */
+ " %a %e %B %Y ", /* Wed 31 December 1969 */
+ " %a %e %B ", /* Wed 31 December */
+ " %a %d-%B-%Y ", /* Wed 31-December-1969 */
+ " %a %d-%B ", /* Wed 31-December */
+ " %a %d/%B/%Y ", /* Wed 31/December/1969 */
+ " %a %d/%B ", /* Wed 31/December */
+ " %a. %e %B %Y ", /* Wed. 31 December 1969 */
+ " %a. %e %B ", /* Wed. 31 December */
+ " %a. %d-%B-%Y ", /* Wed. 31-December-1969 */
+ " %a. %d-%B ", /* Wed. 31-December */
+ " %a. %d/%B/%Y ", /* Wed. 31/December/1969 */
+ " %a. %d/%B ", /* Wed. 31/December */
+ " %a, %e %B %Y ", /* Wed, 31 December 1969 */
+ " %a, %e %B ", /* Wed, 31 December */
+ " %a, %d-%B-%Y ", /* Wed, 31-December-1969 */
+ " %a, %d-%B ", /* Wed, 31-December */
+ " %a, %d/%B/%Y ", /* Wed, 31/December/1969 */
+ " %a, %d/%B ", /* Wed, 31/December */
+ " %B %e, %Y ", /* December 31, 1969 */
+ " %B %e ", /* December 31 */
+ " %B-%d-%Y ", /* December-31-1969 */
+ " %B-%d ", /* December-31 */
+ " %B/%d/%Y ", /* December/31/1969 */
+ " %B/%d ", /* December/31 */
+ " %a %B %e, %Y ", /* Wed December 31, 1969 */
+ " %a %B %e ", /* Wed December 31 */
+ " %a %B-%d-%Y ", /* Wed December-31-1969 */
+ " %a %B-%d ", /* Wed December-31 */
+ " %a %B/%d/%Y ", /* Wed December/31/1969 */
+ " %a %B/%d ", /* Wed December/31 */
+ " %a. %B %e, %Y ", /* Wed. December 31, 1969 */
+ " %a. %B %e ", /* Wed. December 31 */
+ " %a. %B-%d-%Y ", /* Wed. December-31-1969 */
+ " %a. %B-%d ", /* Wed. December-31 */
+ " %a. %B/%d/%Y ", /* Wed. December/31/1969 */
+ " %a. %B/%d ", /* Wed. December/31 */
+ " %a, %B %e, %Y ", /* Wed, December 31, 1969 */
+ " %a, %B %e ", /* Wed, December 31 */
+ " %a, %B-%d-%Y ", /* Wed, December-31-1969 */
+ " %a, %B-%d ", /* Wed, December-31 */
+ " %a, %B/%d/%Y ", /* Wed, December/31/1969 */
+ " %a, %B/%d ", /* Wed, December/31 */
+ " %A %e %B %Y ", /* Wednesday 31 December 1969 */
+ " %A %e %B ", /* Wednesday 31 December */
+ " %A %d-%B-%Y ", /* Wednesday 31-December-1969 */
+ " %A %d-%B ", /* Wednesday 31-December */
+ " %A %d/%B/%Y ", /* Wednesday 31/December/1969 */
+ " %A %d/%B ", /* Wednesday 31/December */
+ " %A, %e %B %Y ", /* Wednesday, 31 December 1969 */
+ " %A, %e %B ", /* Wednesday, 31 December */
+ " %A, %d-%B-%Y ", /* Wednesday, 31-December-1969 */
+ " %A, %d-%B ", /* Wednesday, 31-December */
+ " %A, %d/%B/%Y ", /* Wednesday, 31/December/1969 */
+ " %A, %d/%B ", /* Wednesday, 31/December */
+ " %A %B %e, %Y ", /* Wednesday December 31, 1969 */
+ " %A %B %e ", /* Wednesday December 31 */
+ " %A %B-%d-%Y ", /* Wednesday December-31-1969 */
+ " %A %B-%d ", /* Wednesday December-31 */
+ " %A %B/%d/%Y ", /* Wednesday December/31/1969 */
+ " %A %B/%d ", /* Wednesday December/31 */
+ " %A, %B %e, %Y ", /* Wednesday, December 31, 1969 */
+ " %A, %B %e ", /* Wednesday, December 31 */
+ " %A, %B-%d-%Y ", /* Wednesday, December-31-1969 */
+ " %A, %B-%d ", /* Wednesday, December-31 */
+ " %A, %B/%d/%Y ", /* Wednesday, December/31/1969 */
+ " %A, %B/%d ", /* Wednesday, December/31 */
+};
+
+static const char *FORMATS_TIME[] = {
+ " %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 ", /* 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 *FORMATS_MISC[] = {
+ /* 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 ",
+};
+
+#endif /* FORMATS_H_ */
diff --git a/src/i18n.h b/src/i18n.h
new file mode 100644
index 0000000..4d8ee76
--- /dev/null
+++ b/src/i18n.h
@@ -0,0 +1,11 @@
+#ifdef ENABLE_NLS
+
+#include <libintl.h>
+
+#define _(msgid) gettext(msgid)
+
+#else
+
+#define _(msgid) (msgid)
+
+#endif
diff --git a/src/local.mk b/src/local.mk
index 0882580..1012945 100644
--- a/src/local.mk
+++ b/src/local.mk
@@ -1,4 +1,6 @@
-@_SOURCES += \
+atsign_SOURCES += \
%reldir%/datetime.c \
%reldir%/datetime.h \
+ %reldir%/formats.h \
+ %reldir%/i18n.h \
%reldir%/main.c
diff --git a/src/main.c b/src/main.c
index 10630e8..0ff76c9 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 P. J. McDermott
+ * Copyright (C) 2021, 2022 P. J. McDermott
*
* This file is part of @
*
@@ -17,6 +17,12 @@
* along with @. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "config.h"
+
+#ifdef ENABLE_NLS
+#include <locale.h>
+#endif
+
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
@@ -24,8 +30,8 @@
#include <string.h>
#include <time.h>
#include <unistd.h>
-#include "config.h"
#include "datetime.h"
+#include "i18n.h"
#if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
#include <getopt.h>
@@ -34,7 +40,7 @@
extern const char *PACKAGE_VERSION_GIT;
#if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
-struct option LONGOPTS_[] = {
+static const struct option LONGOPTS_[] = {
{
.name = "list-formats",
.has_arg = 0,
@@ -59,63 +65,83 @@ struct option LONGOPTS_[] = {
static void
_print_usage(FILE *stream, const char *program_name)
{
- fprintf(stream, "Usage: %s [date]time\n", program_name);
+ fprintf(stream, _("Usage: %s [date]time\n"), program_name);
}
static void
_print_help(const char *program_name)
{
_print_usage(stdout, program_name);
- puts("Options:");
#if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
- puts("\t-F, --list-formats List all supported date and time formats");
- puts("\t-h, --help Show this help information");
- puts("\t-V, --version Show version information");
+ puts(_("Options:\n"
+ " -F, --list-formats List all supported date and time formats\n"
+ " -h, --help Show this help information\n"
+ " -V, --version Show version information"));
#else
- puts("\t-F List all supported date and time formats");
- puts("\t-h Show this help information");
- puts("\t-V Show version information");
+ puts(_("Options:\n"
+ " -F List all supported date and time formats\n"
+ " -h Show this help information\n"
+ " -V Show version information"));
#endif
}
-static void
-_list_formats(void)
+#if defined(ENABLE_TESTS) && ENABLE_TESTS
+static struct tm *
+_set_now(char buf[])
{
- time_t tim;
- struct tm *tm;
- char *out;
- size_t buf_sz;
- char *buf;
- size_t i;
+ struct tm tm;
+ time_t t;
+ struct tm *tm_p;
- tim = time(NULL);
- tm = localtime(&tim);
+ sscanf(buf, "%04d-%02d-%02d %02d:%02d:%02d",
+ &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
+ &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
+ tm.tm_year -= 1900;
+ tm.tm_mon -= 1;
+ tm.tm_wday = -1;
+ tm.tm_yday = -1;
+ tm.tm_isdst = -1;
- puts("Time formats:");
+ t = mktime(&tm);
+ tm_p = localtime(&t);
+
+ return tm_p;
+}
+#endif
+
+static void
+_list_formats(const struct tm *tm)
+{
+ char *out;
+ size_t buf_sz;
+ char *buf;
+ size_t i;
+
+ puts(_("Time formats:"));
i = 0;
while (datetime_strftime_time(tm, &out, &buf_sz, &buf, &i) > 0) {
if (out[0] == '\0') {
continue;
}
- printf(" * %s\n", out);
+ printf(_(" * %s\n"), out);
}
- puts("Date formats:");
+ puts(_("Date formats:"));
i = 0;
while (datetime_strftime_date(tm, &out, &buf_sz, &buf, &i) > 0) {
if (out[0] == '\0') {
continue;
}
- printf(" * %s\n", out);
+ printf(_(" * %s\n"), out);
}
- puts("Additional formats:");
+ puts(_("Additional formats:"));
i = 0;
while (datetime_strftime_misc(tm, &out, &buf_sz, &buf, &i) > 0) {
if (out[0] == '\0') {
continue;
}
- printf(" * %s\n", out);
+ printf(_(" * %s\n"), out);
}
}
@@ -123,13 +149,12 @@ static void
_print_version(void)
{
printf("@ (atsign) %s%s\n", PACKAGE_VERSION, PACKAGE_VERSION_GIT);
- puts("Copyright (C) 2021 P. J. McDermott");
- puts("License GPLv3+: GNU GPL version 3 or later "
- "<http://gnu.org/licenses/gpl.html>.");
- puts("This is free software: you are free to change and redistribute "
- "it.");
- puts("There is NO WARRANTY, to the extent permitted by law.\n");
- printf("Please report bugs to <%s>.\n", PACKAGE_BUGREPORT);
+ puts(_("Copyright (C) 2021, 2022 P. J. McDermott\n"
+ "License GPLv3+: GNU GPL version 3 or later "
+ "<http://gnu.org/licenses/gpl.html>.\n"
+ "This is free software: you are free to change and redistribute it.\n"
+ "There is NO WARRANTY, to the extent permitted by law.\n"));
+ printf(_("Please report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
}
static char *
@@ -148,7 +173,7 @@ _concat_args(int argc, char * const argv[])
buf = calloc(buf_l, sizeof(*buf));
if (buf == NULL) {
- fprintf(stderr, "Failed to allocate buffer: %s\n",
+ fprintf(stderr, _("Failed to allocate buffer: %s\n"),
strerror(errno));
return NULL;
}
@@ -168,37 +193,76 @@ _concat_args(int argc, char * const argv[])
int
main(int argc, char * const argv[])
{
- int opt;
- char *buf;
- time_t arg;
- time_t now;
- time_t dif;
+#if defined(ENABLE_TESTS) && ENABLE_TESTS
+ bool dbg;
+#endif
+ bool fmt;
+ int opt;
+ char *buf;
+ time_t now;
+ struct tm *now_tm;
+ struct tm arg_tm;
+ time_t arg;
+ double dif;
+#ifdef ENABLE_NLS
+ bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
+#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
+ bind_textdomain_codeset(PACKAGE, "UTF-8");
+#endif
+ textdomain(PACKAGE);
+ setlocale(LC_ALL, "");
+#endif
+
+#if defined(ENABLE_TESTS) && ENABLE_TESTS
+ dbg = false;
+#endif
+ fmt = false;
optind = 1;
opterr = 0;
+ now = time(NULL);
+ now_tm = localtime(&now);
+
#if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
- while ((opt = getopt_long(argc, argv, "FhV", LONGOPTS_, NULL)) > 0) {
+#if defined(ENABLE_TESTS) && ENABLE_TESTS
+ while ((opt = getopt_long(argc, argv, "FhVd:", LONGOPTS_, NULL)) > 0) {
+#else
+ while ((opt = getopt_long(argc, argv, "FhV:", LONGOPTS_, NULL)) > 0) {
+#endif
+#else
+#if defined(ENABLE_TESTS) && ENABLE_TESTS
+ while ((opt = getopt(argc, argv, "FhVd:")) > 0) {
#else
- while ((opt = getopt(argc, argv, "FhV")) > 0) {
+ while ((opt = getopt(argc, argv, "FhV:")) > 0) {
+#endif
#endif
switch (opt) {
case 'F':
- _list_formats();
- return EXIT_SUCCESS;
+ fmt = true;
+ break;
case 'h':
_print_help(argv[0]);
return EXIT_SUCCESS;
case 'V':
_print_version();
return EXIT_SUCCESS;
+#if defined(ENABLE_TESTS) && ENABLE_TESTS
+ case 'd':
+ dbg = true;
+ now_tm = _set_now(optarg);
+ if (now_tm == NULL) {
+ return EXIT_FAILURE;
+ }
+ break;
+#endif
default:
_print_usage(stderr, argv[0]);
#if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG
- fprintf(stderr, "Try '%s --help' for more "
- "information.\n", argv[0]);
+ fprintf(stderr, _("Try '%s --help' for more "
+ "information.\n"), argv[0]);
#else
- fprintf(stderr, "Try '%s -h' for more "
- "information.\n", argv[0]);
+ fprintf(stderr, _("Try '%s -h' for more "
+ "information.\n"), argv[0]);
#endif
return EXIT_FAILURE;
}
@@ -206,34 +270,52 @@ main(int argc, char * const argv[])
argc -= optind;
argv += optind;
+ if (fmt == true) {
+ _list_formats(now_tm);
+ return EXIT_SUCCESS;
+ }
+
buf = _concat_args(argc, argv);
if (buf == NULL) {
return EXIT_FAILURE;
}
- if (datetime_parse(buf, &arg) < 0) {
+ if (datetime_parse(now_tm, buf, &arg_tm, &arg) < 0) {
free(buf);
return EXIT_FAILURE;
}
free(buf);
- time(&now);
- dif = arg - now;
+#if defined(ENABLE_TESTS) && ENABLE_TESTS
+ if (dbg == true) {
+ printf("%04d-%02d-%02d %02d:%02d:%02d\n",
+ arg_tm.tm_year + 1900,
+ arg_tm.tm_mon + 1,
+ arg_tm.tm_mday,
+ arg_tm.tm_hour,
+ arg_tm.tm_min,
+ arg_tm.tm_sec);
+ return EXIT_SUCCESS;
+ }
+#endif
+
+ now = time(NULL);
+ dif = difftime(arg, now);
if (dif >= 1000 * 24 * 60 * 60) {
- fputs("Date too far in the future\n", stderr);
+ fputs(_("Date too far in the future\n"), stderr);
return EXIT_FAILURE;
}
setvbuf(stdout, NULL, _IONBF, 0);
- while (arg > now) {
+ while (dif > 0) {
printf("\r%03d:%02d:%02d:%02d",
(int) dif / 60 / 60 / 24,
(int) dif / 60 / 60 % 24,
(int) dif / 60 % 60,
(int) dif % 60);
sleep(1);
- time(&now);
- dif = arg - now;
+ now = time(NULL);
+ dif = difftime(arg, now);
}
printf("\r000:00:00:00\n");