summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore12
-rw-r--r--Makefile.am96
-rw-r--r--README84
-rw-r--r--TODO4
-rwxr-xr-xautogen.sh8
-rw-r--r--build-aux/tap-driver.sh651
-rw-r--r--configure.ac51
-rw-r--r--m4/.gitignore10
-rw-r--r--m4/ax_append_flag.m431
-rw-r--r--m4/ax_cflags_warn_all.m4180
-rw-r--r--m4/ax_check_compile_flag.m431
-rw-r--r--m4/ax_compiler_vendor.m4119
-rw-r--r--m4/ax_prepend_flag.m451
-rw-r--r--m4/gcc_version.m434
-rw-r--r--m4/gl_manywarnings.m4281
-rw-r--r--m4/gl_warnings.m4118
-rw-r--r--man/.gitignore1
-rw-r--r--man/atsign.1in41
-rw-r--r--man/local.mk43
-rw-r--r--po/.gitignore11
-rw-r--r--po/Makevars26
-rw-r--r--po/POTFILES.in2
-rwxr-xr-xscripts/update-m4.sh30
-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
-rw-r--r--tests/.gitignore2
-rw-r--r--tests/aux/tap-functions.sh229
-rwxr-xr-xtests/disabled.sh2
-rw-r--r--tests/formats.exp204
-rwxr-xr-xtests/formats.sh43
-rw-r--r--tests/local.mk21
-rwxr-xr-xtests/parse.sh77
36 files changed, 2601 insertions, 676 deletions
diff --git a/.gitignore b/.gitignore
index 9e096c8..3c7427f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,13 +4,16 @@
*~
*.orig
-# Files generated by aclocal, autoconf, and automake
+# Files generated by gettextize, aclocal, autoconf, and automake
+/ABOUT-NLS
/aclocal.m4
/autom4te.cache/
/configure
/config.h.in
-/build-aux/
+/build-aux/*
+!/build-aux/tap-driver.sh
/ChangeLog
+/FORMATS
/INSTALL
Makefile.in
@@ -26,6 +29,9 @@ Makefile
.dirstamp
*.o
/version.c
-/@
+/atsign
+/test-suite.log
/atsign-*.tar*
/atsign-*/
+/MD5SUMS
+/SHA256SUMS
diff --git a/Makefile.am b/Makefile.am
index ecdbc4c..b53c109 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,6 @@
# Process this file with automake to produce an input makefile.
#
-# Copyright (C) 2013, 2014, 2017, 2018, 2021 P. J. McDermott
+# Copyright (C) 2013, 2014, 2017, 2018, 2021, 2022 P. J. McDermott
#
# This file is part of @
#
@@ -40,33 +40,39 @@ endif
# * Our release target
PACKAGE_STRING = $(PACKAGE_NAME) $(PACKAGE_VERSION)$(PACKAGE_VERSION_GIT)
-bin_PROGRAMS = @
+# Automake lib/am/check.am since version 1.16.3 defines this with single quotes,
+# which breaks PACKAGE_VERSION_GIT above.
+AM_TESTSUITE_SUMMARY_HEADER = " for $(PACKAGE_STRING)"
-@_SOURCES =
-@_CFLAGS = \
+bin_PROGRAMS = atsign
+
+atsign_SOURCES =
+atsign_CFLAGS = \
$(WARN_CFLAGS) \
$(ASAN_CFLAGS)
-@_CPPFLAGS = \
+atsign_CPPFLAGS = \
$(SDL_CFLAGS) \
$(SDL_TTF_CFLAGS) \
-DABS_BUILDDIR=\"$(abs_builddir)\" \
- -DFONTSDIR=\"$(fontsdir)\"
-@_LDADD = \
+ -DLOCALEDIR=\"$(localedir)\"
+atsign_LDADD = \
$(ASAN_CFLAGS) \
$(SDL_LIBS) \
$(SDL_TTF_LIBS)
-@_LINK = $(LINK) version.c
-EXTRA_@_DEPENDENCIES = version.c
+atsign_LINK = $(LINK) version.c
+EXTRA_atsign_DEPENDENCIES = version.c
CLEANFILES = version.c
CONFIG_CLEAN_FILES =
-EXTRA_DIST = autogen.sh
+EXTRA_DIST = build-aux/config.rpath autogen.sh
V_REDIR = $(V_REDIR_$(V))
V_REDIR_ = $(V_REDIR_$(AM_DEFAULT_VERBOSITY))
V_REDIR_0 = 1>/dev/null 2>&1
V_REDIR_1 =
-version.c: $(@_OBJECTS) $(@_DEPENDENCIES)
+SUFFIXES =
+
+version.c: $(atsign_OBJECTS) $(atsign_DEPENDENCIES)
$(AM_V_GEN)printf 'const char *PACKAGE_VERSION_GIT = "%s";\n' \
"$(PACKAGE_VERSION_GIT)" >version.c
@@ -77,15 +83,65 @@ really-clean: distclean
$(srcdir)/build-aux/ $(srcdir)/INSTALL $(srcdir)/Makefile.in \
$(srcdir)/ChangeLog
+if ENABLE_SYMLINK
+install-exec-hook:
+ @set -e; \
+ dir="$(DESTDIR)$(bindir)"; \
+ dst="$$(printf 'atsign' | \
+ sed '$(transform); s/$$/$(EXEEXT)/')"; \
+ src='@$(EXEEXT)'; \
+ echo " (cd '$${dir}' && $(LN_S) '$${dst}' '$${src}')"; \
+ (cd "$${dir}" && $(LN_S) "$${dst}" "$${src}")
+install-data-hook:
+ @set -e; \
+ dir="$(DESTDIR)$(man1dir)"; \
+ dst="$$(printf 'atsign' | \
+ sed '$(transform); s/$$/$(EXEEXT)/').1"; \
+ src='@$(EXEEXT).1'; \
+ echo " (cd '$${dir}' && $(LN_S) '$${dst}' '$${src}')"; \
+ (cd "$${dir}" && $(LN_S) "$${dst}" "$${src}")
+uninstall-hook:
+ echo " rm -f '$(DESTDIR)$(bindir)/@$(EXEEXT)'"
+ rm -f "$(DESTDIR)$(bindir)/@$(EXEEXT)"
+endif
+
+if ENABLE_TESTS
+if CROSS_COMPILING
dist-hook:
- set -e; \
- if [ -d '$(srcdir)/.git' ]; then \
- printf 'Generated file. Do not edit.\n\n' \
- >'$(distdir)/ChangeLog~'; \
- GIT_DIR='$(srcdir)/.git' git log --stat --color=never \
- >>'$(distdir)/ChangeLog~'; \
- mv '$(distdir)/ChangeLog~' '$(distdir)/ChangeLog'; \
+else !CROSS_COMPILING
+dist-hook: atsign$(EXEEXT)
+endif !CROSS_COMPILING
+else !ENABLE_TESTS
+dist-hook:
+endif !ENABLE_TESTS
+if IN_GIT
+ printf 'Generated file. Do not edit.\n\n' 1>'$(distdir)/ChangeLog~'
+ GIT_DIR='$(srcdir)/.git' git log --stat --color=never \
+ 1>>'$(distdir)/ChangeLog~'
+ mv '$(distdir)/ChangeLog~' '$(distdir)/ChangeLog'
+endif
+if ENABLE_TESTS
+if CROSS_COMPILING
+ if [ -f 'FORMATS' ]; then \
+ cp -p 'FORMATS' '$(distdir)/FORMATS'; \
+ else \
+ printf 'Error: "FORMATS" file cannot be generated when %s' \
+ 'cross compiling' 1>&2; \
+ exit 1; \
fi
+else !CROSS_COMPILING
+ $(builddir)/atsign$(EXEEXT) -d '1969-12-31 19:00:01' -F \
+ 1>'$(distdir)/FORMATS'
+endif !CROSS_COMPILING
+else !ENABLE_TESTS
+ if [ -f 'FORMATS' ]; then \
+ cp -p 'FORMATS' '$(distdir)/FORMATS'; \
+ else \
+ printf 'Error: "FORMATS" file cannot be generated without %s' \
+ 'test suite support' 1>&2; \
+ exit 1; \
+ fi
+endif !ENABLE_TESTS
release:
sed "s/^Released: ????-??-??\$$/Released: $$(date '+%Y-%m-%d')/" \
@@ -112,3 +168,7 @@ release:
files@files.pehjota.net:files/pub/$(PACKAGE)/$(VERSION)/
include $(top_srcdir)/src/local.mk
+include $(top_srcdir)/man/local.mk
+include $(top_srcdir)/tests/local.mk
+
+SUBDIRS = . po
diff --git a/README b/README
index 77e0090..82796f6 100644
--- a/README
+++ b/README
@@ -8,82 +8,22 @@ commands, e.g.:
@ '19 January 2038 03:14:07'; halt
@ 9:00am mon; printf '\a'
+@ is similar to the standard sleep utility except that it accepts a time
+and optional date instead of a delay duration. @ is distinct from the
+at daemon package by Jose M Calhariz in that @ is not a daemon that runs
+jobs in the background; instead it delays subsequent commands in a list,
+allowing such commands to remain connected to a controlling terminal for
+user interaction.
+
Date/Time Formats
-----------------
-@ currently supports 704 date/time formats, any one of which may be
-specified on the command line in one or more arguments. All spaces
-shown below are optional.
-
-A time may be specified in any of the following eight formats:
-
- * 7:00:01 PM
- * 19:00:01
- * 7:00 PM
- * 19:00
- * 70001 PM
- * 190001
- * 700 PM
- * 1900
-
-A date may be omitted or specified (before or after the time) in any of
-the following 43 formats:
-
- * Wed
- * 1969-12-31
- * 1969/12/31
- * 12-31-1969
- * 12-31
- * 12/31/1969
- * 12/31
- * 31 Dec 1969
- * 31 Dec
- * 31-Dec-1969
- * 31-Dec
- * 31/Dec/1969
- * 31/Dec
- * Wed 31 Dec 1969
- * Wed 31 Dec
- * Wed 31-Dec-1969
- * Wed 31-Dec
- * Wed 31/Dec/1969
- * Wed 31/Dec
- * Wed, 31 Dec 1969
- * Wed, 31 Dec
- * Wed, 31-Dec-1969
- * Wed, 31-Dec
- * Wed, 31/Dec/1969
- * Wed, 31/Dec
- * Dec 31, 1969
- * Dec 31
- * Dec-31-1969
- * Dec-31
- * Dec/31/1969
- * Dec/31
- * Wed Dec 31, 1969
- * Wed Dec 31
- * Wed Dec-31-1969
- * Wed Dec-31
- * Wed Dec/31/1969
- * Wed Dec/31
- * Wed, Dec 31, 1969
- * Wed, Dec 31
- * Wed, Dec-31-1969
- * Wed, Dec-31
- * Wed, Dec/31/1969
- * Wed, Dec/31
-
-Additionally, the following nine date/time formats are supported:
+@ currently supports 4341 date/time formats, any one of which may be
+specified on the command line in one or more arguments. The date may be
+omitted or specified before or after the time.
- * 1969-12-31T19:00:01
- * 1969-12-31T19:00
- * 19691231T190001
- * 19691231T1900
- * 19691231 70001 PM
- * 19691231 190001
- * 19691231 700 PM
- * 19691231 1900
- * Wed Dec 31 19:00:01 1969
+See the `FORMATS` file for a list of supported formats. All spaces
+shown are optional.
Development
-----------
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..6396ebe
--- /dev/null
+++ b/TODO
@@ -0,0 +1,4 @@
+try to fix dst bug
+test with other libc's (newlib? musl? fbsd? obsd?)
+`./@ 7:00 PM` doesn't work with musl strptime()
+test other times
diff --git a/autogen.sh b/autogen.sh
index c80fde1..d11efe6 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -2,7 +2,7 @@
#
# Script to generate the build system.
#
-# Copyright (C) 2013 P. J. McDermott
+# Copyright (C) 2013, 2019 P. J. McDermott
#
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
@@ -19,6 +19,12 @@ srcdir="${0%/*}"
cat >ChangeLog <<-EOF
This file is generated upon release. Run \`git log\` for a list of changes.
EOF
+ # Force gettextize to be non-interactive. Stupid but necessary.
+ LC_ALL=C expect -f - <<-EOF
+ spawn gettextize --force --no-changelog
+ expect "Press Return to acknowledge the previous * paragraphs."
+ send "\n"
+ EOF
aclocal
autoconf
autoheader
diff --git a/build-aux/tap-driver.sh b/build-aux/tap-driver.sh
new file mode 100644
index 0000000..bc7b1cc
--- /dev/null
+++ b/build-aux/tap-driver.sh
@@ -0,0 +1,651 @@
+#! /bin/sh
+# Copyright (C) 2011-2022 Free Software Foundation, Inc.
+#
+# This program 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 2, or (at your option)
+# any later version.
+#
+# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to <bug-automake@gnu.org> or send patches to
+# <automake-patches@gnu.org>.
+
+scriptversion=2013-12-23.17; # UTC
+
+# Make unconditional expansion of undefined variables an error. This
+# helps a lot in preventing typo-related bugs.
+set -u
+
+me=tap-driver.sh
+
+fatal ()
+{
+ echo "$me: fatal: $*" >&2
+ exit 1
+}
+
+usage_error ()
+{
+ echo "$me: $*" >&2
+ print_usage >&2
+ exit 2
+}
+
+print_usage ()
+{
+ cat <<END
+Usage:
+ tap-driver.sh --test-name=NAME --log-file=PATH --trs-file=PATH
+ [--expect-failure={yes|no}] [--color-tests={yes|no}]
+ [--enable-hard-errors={yes|no}] [--ignore-exit]
+ [--diagnostic-string=STRING] [--merge|--no-merge]
+ [--comments|--no-comments] [--] TEST-COMMAND
+The '--test-name', '-log-file' and '--trs-file' options are mandatory.
+END
+}
+
+# TODO: better error handling in option parsing (in particular, ensure
+# TODO: $log_file, $trs_file and $test_name are defined).
+test_name= # Used for reporting.
+log_file= # Where to save the result and output of the test script.
+trs_file= # Where to save the metadata of the test run.
+expect_failure=0
+color_tests=0
+merge=0
+ignore_exit=0
+comments=0
+diag_string='#'
+while test $# -gt 0; do
+ case $1 in
+ --help) print_usage; exit $?;;
+ --version) echo "$me $scriptversion"; exit $?;;
+ --test-name) test_name=$2; shift;;
+ --log-file) log_file=$2; shift;;
+ --trs-file) trs_file=$2; shift;;
+ --color-tests) color_tests=$2; shift;;
+ --expect-failure) expect_failure=$2; shift;;
+ --enable-hard-errors) shift;; # No-op.
+ --merge) merge=1;;
+ --no-merge) merge=0;;
+ --ignore-exit) ignore_exit=1;;
+ --comments) comments=1;;
+ --no-comments) comments=0;;
+ --diagnostic-string) diag_string=$2; shift;;
+ --) shift; break;;
+ -*) usage_error "invalid option: '$1'";;
+ esac
+ shift
+done
+
+test $# -gt 0 || usage_error "missing test command"
+
+case $expect_failure in
+ yes) expect_failure=1;;
+ *) expect_failure=0;;
+esac
+
+if test $color_tests = yes; then
+ init_colors='
+ color_map["red"]="" # Red.
+ color_map["grn"]="" # Green.
+ color_map["lgn"]="" # Light green.
+ color_map["blu"]="" # Blue.
+ color_map["mgn"]="" # Magenta.
+ color_map["std"]="" # No color.
+ color_for_result["ERROR"] = "mgn"
+ color_for_result["PASS"] = "grn"
+ color_for_result["XPASS"] = "red"
+ color_for_result["FAIL"] = "red"
+ color_for_result["XFAIL"] = "lgn"
+ color_for_result["SKIP"] = "blu"'
+else
+ init_colors=''
+fi
+
+# :; is there to work around a bug in bash 3.2 (and earlier) which
+# does not always set '$?' properly on redirection failure.
+# See the Autoconf manual for more details.
+:;{
+ (
+ # Ignore common signals (in this subshell only!), to avoid potential
+ # problems with Korn shells. Some Korn shells are known to propagate
+ # to themselves signals that have killed a child process they were
+ # waiting for; this is done at least for SIGINT (and usually only for
+ # it, in truth). Without the `trap' below, such a behaviour could
+ # cause a premature exit in the current subshell, e.g., in case the
+ # test command it runs gets terminated by a SIGINT. Thus, the awk
+ # script we are piping into would never seen the exit status it
+ # expects on its last input line (which is displayed below by the
+ # last `echo $?' statement), and would thus die reporting an internal
+ # error.
+ # For more information, see the Autoconf manual and the threads:
+ # <https://lists.gnu.org/archive/html/bug-autoconf/2011-09/msg00004.html>
+ # <http://mail.opensolaris.org/pipermail/ksh93-integration-discuss/2009-February/004121.html>
+ trap : 1 3 2 13 15
+ if test $merge -gt 0; then
+ exec 2>&1
+ else
+ exec 2>&3
+ fi
+ "$@"
+ echo $?
+ ) | LC_ALL=C ${AM_TAP_AWK-awk} \
+ -v me="$me" \
+ -v test_script_name="$test_name" \
+ -v log_file="$log_file" \
+ -v trs_file="$trs_file" \
+ -v expect_failure="$expect_failure" \
+ -v merge="$merge" \
+ -v ignore_exit="$ignore_exit" \
+ -v comments="$comments" \
+ -v diag_string="$diag_string" \
+'
+# TODO: the usages of "cat >&3" below could be optimized when using
+# GNU awk, and/on on systems that supports /dev/fd/.
+
+# Implementation note: in what follows, `result_obj` will be an
+# associative array that (partly) simulates a TAP result object
+# from the `TAP::Parser` perl module.
+
+## ----------- ##
+## FUNCTIONS ##
+## ----------- ##
+
+function fatal(msg)
+{
+ print me ": " msg | "cat >&2"
+ exit 1
+}
+
+function abort(where)
+{
+ fatal("internal error " where)
+}
+
+# Convert a boolean to a "yes"/"no" string.
+function yn(bool)
+{
+ return bool ? "yes" : "no";
+}
+
+function add_test_result(result)
+{
+ if (!test_results_index)
+ test_results_index = 0
+ test_results_list[test_results_index] = result
+ test_results_index += 1
+ test_results_seen[result] = 1;
+}
+
+# Whether the test script should be re-run by "make recheck".
+function must_recheck()
+{
+ for (k in test_results_seen)
+ if (k != "XFAIL" && k != "PASS" && k != "SKIP")
+ return 1
+ return 0
+}
+
+# Whether the content of the log file associated to this test should
+# be copied into the "global" test-suite.log.
+function copy_in_global_log()
+{
+ for (k in test_results_seen)
+ if (k != "PASS")
+ return 1
+ return 0
+}
+
+function get_global_test_result()
+{
+ if ("ERROR" in test_results_seen)
+ return "ERROR"
+ if ("FAIL" in test_results_seen || "XPASS" in test_results_seen)
+ return "FAIL"
+ all_skipped = 1
+ for (k in test_results_seen)
+ if (k != "SKIP")
+ all_skipped = 0
+ if (all_skipped)
+ return "SKIP"
+ return "PASS";
+}
+
+function stringify_result_obj(result_obj)
+{
+ if (result_obj["is_unplanned"] || result_obj["number"] != testno)
+ return "ERROR"
+
+ if (plan_seen == LATE_PLAN)
+ return "ERROR"
+
+ if (result_obj["directive"] == "TODO")
+ return result_obj["is_ok"] ? "XPASS" : "XFAIL"
+
+ if (result_obj["directive"] == "SKIP")
+ return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL;
+
+ if (length(result_obj["directive"]))
+ abort("in function stringify_result_obj()")
+
+ return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL
+}
+
+function decorate_result(result)
+{
+ color_name = color_for_result[result]
+ if (color_name)
+ return color_map[color_name] "" result "" color_map["std"]
+ # If we are not using colorized output, or if we do not know how
+ # to colorize the given result, we should return it unchanged.
+ return result
+}
+
+function report(result, details)
+{
+ if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/)
+ {
+ msg = ": " test_script_name
+ add_test_result(result)
+ }
+ else if (result == "#")
+ {
+ msg = " " test_script_name ":"
+ }
+ else
+ {
+ abort("in function report()")
+ }
+ if (length(details))
+ msg = msg " " details
+ # Output on console might be colorized.
+ print decorate_result(result) msg
+ # Log the result in the log file too, to help debugging (this is
+ # especially true when said result is a TAP error or "Bail out!").
+ print result msg | "cat >&3";
+}
+
+function testsuite_error(error_message)
+{
+ report("ERROR", "- " error_message)
+}
+
+function handle_tap_result()
+{
+ details = result_obj["number"];
+ if (length(result_obj["description"]))
+ details = details " " result_obj["description"]
+
+ if (plan_seen == LATE_PLAN)
+ {
+ details = details " # AFTER LATE PLAN";
+ }
+ else if (result_obj["is_unplanned"])
+ {
+ details = details " # UNPLANNED";
+ }
+ else if (result_obj["number"] != testno)
+ {
+ details = sprintf("%s # OUT-OF-ORDER (expecting %d)",
+ details, testno);
+ }
+ else if (result_obj["directive"])
+ {
+ details = details " # " result_obj["directive"];
+ if (length(result_obj["explanation"]))
+ details = details " " result_obj["explanation"]
+ }
+
+ report(stringify_result_obj(result_obj), details)
+}
+
+# `skip_reason` should be empty whenever planned > 0.
+function handle_tap_plan(planned, skip_reason)
+{
+ planned += 0 # Avoid getting confused if, say, `planned` is "00"
+ if (length(skip_reason) && planned > 0)
+ abort("in function handle_tap_plan()")
+ if (plan_seen)
+ {
+ # Error, only one plan per stream is acceptable.
+ testsuite_error("multiple test plans")
+ return;
+ }
+ planned_tests = planned
+ # The TAP plan can come before or after *all* the TAP results; we speak
+ # respectively of an "early" or a "late" plan. If we see the plan line
+ # after at least one TAP result has been seen, assume we have a late
+ # plan; in this case, any further test result seen after the plan will
+ # be flagged as an error.
+ plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN)
+ # If testno > 0, we have an error ("too many tests run") that will be
+ # automatically dealt with later, so do not worry about it here. If
+ # $plan_seen is true, we have an error due to a repeated plan, and that
+ # has already been dealt with above. Otherwise, we have a valid "plan
+ # with SKIP" specification, and should report it as a particular kind
+ # of SKIP result.
+ if (planned == 0 && testno == 0)
+ {
+ if (length(skip_reason))
+ skip_reason = "- " skip_reason;
+ report("SKIP", skip_reason);
+ }
+}
+
+function extract_tap_comment(line)
+{
+ if (index(line, diag_string) == 1)
+ {
+ # Strip leading `diag_string` from `line`.
+ line = substr(line, length(diag_string) + 1)
+ # And strip any leading and trailing whitespace left.
+ sub("^[ \t]*", "", line)
+ sub("[ \t]*$", "", line)
+ # Return what is left (if any).
+ return line;
+ }
+ return "";
+}
+
+# When this function is called, we know that line is a TAP result line,
+# so that it matches the (perl) RE "^(not )?ok\b".
+function setup_result_obj(line)
+{
+ # Get the result, and remove it from the line.
+ result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0)
+ sub("^(not )?ok[ \t]*", "", line)
+
+ # If the result has an explicit number, get it and strip it; otherwise,
+ # automatically assign the next test number to it.
+ if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/)
+ {
+ match(line, "^[0-9]+")
+ # The final `+ 0` is to normalize numbers with leading zeros.
+ result_obj["number"] = substr(line, 1, RLENGTH) + 0
+ line = substr(line, RLENGTH + 1)
+ }
+ else
+ {
+ result_obj["number"] = testno
+ }
+
+ if (plan_seen == LATE_PLAN)
+ # No further test results are acceptable after a "late" TAP plan
+ # has been seen.
+ result_obj["is_unplanned"] = 1
+ else if (plan_seen && testno > planned_tests)
+ result_obj["is_unplanned"] = 1
+ else
+ result_obj["is_unplanned"] = 0
+
+ # Strip trailing and leading whitespace.
+ sub("^[ \t]*", "", line)
+ sub("[ \t]*$", "", line)
+
+ # This will have to be corrected if we have a "TODO"/"SKIP" directive.
+ result_obj["description"] = line
+ result_obj["directive"] = ""
+ result_obj["explanation"] = ""
+
+ if (index(line, "#") == 0)
+ return # No possible directive, nothing more to do.
+
+ # Directives are case-insensitive.
+ rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*"
+
+ # See whether we have the directive, and if yes, where.
+ pos = match(line, rx "$")
+ if (!pos)
+ pos = match(line, rx "[^a-zA-Z0-9_]")
+
+ # If there was no TAP directive, we have nothing more to do.
+ if (!pos)
+ return
+
+ # Let`s now see if the TAP directive has been escaped. For example:
+ # escaped: ok \# SKIP
+ # not escaped: ok \\# SKIP
+ # escaped: ok \\\\\# SKIP
+ # not escaped: ok \ # SKIP
+ if (substr(line, pos, 1) == "#")
+ {
+ bslash_count = 0
+ for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--)
+ bslash_count += 1
+ if (bslash_count % 2)
+ return # Directive was escaped.
+ }
+
+ # Strip the directive and its explanation (if any) from the test
+ # description.
+ result_obj["description"] = substr(line, 1, pos - 1)
+ # Now remove the test description from the line, that has been dealt
+ # with already.
+ line = substr(line, pos)
+ # Strip the directive, and save its value (normalized to upper case).
+ sub("^[ \t]*#[ \t]*", "", line)
+ result_obj["directive"] = toupper(substr(line, 1, 4))
+ line = substr(line, 5)
+ # Now get the explanation for the directive (if any), with leading
+ # and trailing whitespace removed.
+ sub("^[ \t]*", "", line)
+ sub("[ \t]*$", "", line)
+ result_obj["explanation"] = line
+}
+
+function get_test_exit_message(status)
+{
+ if (status == 0)
+ return ""
+ if (status !~ /^[1-9][0-9]*$/)
+ abort("getting exit status")
+ if (status < 127)
+ exit_details = ""
+ else if (status == 127)
+ exit_details = " (command not found?)"
+ else if (status >= 128 && status <= 255)
+ exit_details = sprintf(" (terminated by signal %d?)", status - 128)
+ else if (status > 256 && status <= 384)
+ # We used to report an "abnormal termination" here, but some Korn
+ # shells, when a child process die due to signal number n, can leave
+ # in $? an exit status of 256+n instead of the more standard 128+n.
+ # Apparently, both behaviours are allowed by POSIX (2008), so be
+ # prepared to handle them both. See also Austing Group report ID
+ # 0000051 <http://www.austingroupbugs.net/view.php?id=51>
+ exit_details = sprintf(" (terminated by signal %d?)", status - 256)
+ else
+ # Never seen in practice.
+ exit_details = " (abnormal termination)"
+ return sprintf("exited with status %d%s", status, exit_details)
+}
+
+function write_test_results()
+{
+ print ":global-test-result: " get_global_test_result() > trs_file
+ print ":recheck: " yn(must_recheck()) > trs_file
+ print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file
+ for (i = 0; i < test_results_index; i += 1)
+ print ":test-result: " test_results_list[i] > trs_file
+ close(trs_file);
+}
+
+BEGIN {
+
+## ------- ##
+## SETUP ##
+## ------- ##
+
+'"$init_colors"'
+
+# Properly initialized once the TAP plan is seen.
+planned_tests = 0
+
+COOKED_PASS = expect_failure ? "XPASS": "PASS";
+COOKED_FAIL = expect_failure ? "XFAIL": "FAIL";
+
+# Enumeration-like constants to remember which kind of plan (if any)
+# has been seen. It is important that NO_PLAN evaluates "false" as
+# a boolean.
+NO_PLAN = 0
+EARLY_PLAN = 1
+LATE_PLAN = 2
+
+testno = 0 # Number of test results seen so far.
+bailed_out = 0 # Whether a "Bail out!" directive has been seen.
+
+# Whether the TAP plan has been seen or not, and if yes, which kind
+# it is ("early" is seen before any test result, "late" otherwise).
+plan_seen = NO_PLAN
+
+## --------- ##
+## PARSING ##
+## --------- ##
+
+is_first_read = 1
+
+while (1)
+ {
+ # Involutions required so that we are able to read the exit status
+ # from the last input line.
+ st = getline
+ if (st < 0) # I/O error.
+ fatal("I/O error while reading from input stream")
+ else if (st == 0) # End-of-input
+ {
+ if (is_first_read)
+ abort("in input loop: only one input line")
+ break
+ }
+ if (is_first_read)
+ {
+ is_first_read = 0
+ nextline = $0
+ continue
+ }
+ else
+ {
+ curline = nextline
+ nextline = $0
+ $0 = curline
+ }
+ # Copy any input line verbatim into the log file.
+ print | "cat >&3"
+ # Parsing of TAP input should stop after a "Bail out!" directive.
+ if (bailed_out)
+ continue
+
+ # TAP test result.
+ if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/)
+ {
+ testno += 1
+ setup_result_obj($0)
+ handle_tap_result()
+ }
+ # TAP plan (normal or "SKIP" without explanation).
+ else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/)
+ {
+ # The next two lines will put the number of planned tests in $0.
+ sub("^1\\.\\.", "")
+ sub("[^0-9]*$", "")
+ handle_tap_plan($0, "")
+ continue
+ }
+ # TAP "SKIP" plan, with an explanation.
+ else if ($0 ~ /^1\.\.0+[ \t]*#/)
+ {
+ # The next lines will put the skip explanation in $0, stripping
+ # any leading and trailing whitespace. This is a little more
+ # tricky in truth, since we want to also strip a potential leading
+ # "SKIP" string from the message.
+ sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "")
+ sub("[ \t]*$", "");
+ handle_tap_plan(0, $0)
+ }
+ # "Bail out!" magic.
+ # Older versions of prove and TAP::Harness (e.g., 3.17) did not
+ # recognize a "Bail out!" directive when preceded by leading
+ # whitespace, but more modern versions (e.g., 3.23) do. So we
+ # emulate the latter, "more modern" behaviour.
+ else if ($0 ~ /^[ \t]*Bail out!/)
+ {
+ bailed_out = 1
+ # Get the bailout message (if any), with leading and trailing
+ # whitespace stripped. The message remains stored in `$0`.
+ sub("^[ \t]*Bail out![ \t]*", "");
+ sub("[ \t]*$", "");
+ # Format the error message for the
+ bailout_message = "Bail out!"
+ if (length($0))
+ bailout_message = bailout_message " " $0
+ testsuite_error(bailout_message)
+ }
+ # Maybe we have too look for dianogtic comments too.
+ else if (comments != 0)
+ {
+ comment = extract_tap_comment($0);
+ if (length(comment))
+ report("#", comment);
+ }
+ }
+
+## -------- ##
+## FINISH ##
+## -------- ##
+
+# A "Bail out!" directive should cause us to ignore any following TAP
+# error, as well as a non-zero exit status from the TAP producer.
+if (!bailed_out)
+ {
+ if (!plan_seen)
+ {
+ testsuite_error("missing test plan")
+ }
+ else if (planned_tests != testno)
+ {
+ bad_amount = testno > planned_tests ? "many" : "few"
+ testsuite_error(sprintf("too %s tests run (expected %d, got %d)",
+ bad_amount, planned_tests, testno))
+ }
+ if (!ignore_exit)
+ {
+ # Fetch exit status from the last line.
+ exit_message = get_test_exit_message(nextline)
+ if (exit_message)
+ testsuite_error(exit_message)
+ }
+ }
+
+write_test_results()
+
+exit 0
+
+} # End of "BEGIN" block.
+'
+
+# TODO: document that we consume the file descriptor 3 :-(
+} 3>"$log_file"
+
+test $? -eq 0 || fatal "I/O or internal error"
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'before-save-hook 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC0"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/configure.ac b/configure.ac
index 3ff9f79..5b527de 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
# Process this file with autoconf to produce a configure script.
#
-# Copyright (C) 2013, 2017, 2019, 2021 P. J. McDermott
+# Copyright (C) 2013, 2017, 2019, 2021, 2022 P. J. McDermott
#
# This file is part of @
#
@@ -30,6 +30,14 @@ AM_CONDITIONAL([IN_GIT],
AM_CONDITIONAL([OUT_OF_TREE], [test x"${srcdir}" != x'.' ])
+AM_CONDITIONAL([CROSS_COMPILING], [! test x"${build_alias}" = x"${host_alias}"])
+
+GETTEXT_PACKAGE="${PACKAGE}"
+AC_SUBST([GETTEXT_PACKAGE])
+AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["${GETTEXT_PACKAGE}"],
+ [Define the gettext package to be used.])
+AM_GNU_GETTEXT([external])
+
save_CFLAGS="${CFLAGS-}"
AC_PROG_CC()
AM_PROG_CC_C_O()
@@ -38,8 +46,9 @@ test -d "${srcdir}/.git" || CFLAGS="${save_CFLAGS}"
funcs_missing=false
AC_CHECK_FUNCS(
[\
- calloc fprintf fputs free isspace localtime mktime printf \
- realloc setvbuf sleep strerror strlen strftime strptime time
+ calloc difftime fprintf fputs free isspace localtime mktime \
+ printf realloc setvbuf sleep strerror strlen strftime strptime \
+ time
],
[],
[funcs_missing=true])
@@ -53,6 +62,40 @@ if ${funcs_missing}; then
AC_MSG_ERROR([required functions are missing])
fi
+AC_PROG_LN_S()
+AC_ARG_ENABLE([symlink],
+ [AS_HELP_STRING([--disable-symlink],
+ [don't install \`@' symbolic link])],
+ [case "${enableval}" in yes|no) symlink=${enableval};;
+ *) AC_MSG_ERROR(
+ [bad value ${enableval} for symlink option]);;
+ esac],
+ [symlink=yes]
+)
+AM_CONDITIONAL([ENABLE_SYMLINK], [test x"${symlink}" = x'yes'])
+
+AC_ARG_ENABLE([tests],
+ [AS_HELP_STRING([--disable-tests],
+ [exclude test suite support from executable])],
+ [case "${enableval}" in yes|no) tests=${enableval};;
+ *) AC_MSG_ERROR(
+ [bad value ${enableval} for tests option]);;
+ esac],
+ [tests=yes]
+)
+if test x"${tests}" = x'yes'; then
+ AC_DEFINE([ENABLE_TESTS], [1],
+ [Define to 1 to include test suite support])
+ AC_CHECK_FUNCS(
+ [sscanf],
+ [],
+ [AC_MSG_ERROR([required functions are missing])])
+else
+ AC_DEFINE([ENABLE_TESTS], [0],
+ [Define to 1 to include test suite support])
+fi
+AM_CONDITIONAL([ENABLE_TESTS], [test x"${tests}" = x'yes'])
+
AX_CFLAGS_WARN_ALL() dnl Adds -Wall or equivalent
AX_CHECK_COMPILE_FLAG([-Wpedantic], [AX_APPEND_FLAG([-Wpedantic])])
AX_CHECK_COMPILE_FLAG([-Wextra], [AX_APPEND_FLAG([-Wextra])])
@@ -114,6 +157,6 @@ for flag in -Os -fno-unwind-tables -fno-asynchronous-unwind-tables \
AX_CHECK_COMPILE_FLAG([${flag}], [AX_APPEND_FLAG([${flag}])])
done
-AC_CONFIG_FILES([Makefile])
+AC_CONFIG_FILES([Makefile po/Makefile.in])
AC_CONFIG_HEADERS([config.h])
AC_OUTPUT()
diff --git a/m4/.gitignore b/m4/.gitignore
new file mode 100644
index 0000000..a9c97ca
--- /dev/null
+++ b/m4/.gitignore
@@ -0,0 +1,10 @@
+/*.m4
+!/ax_append_flag.m4
+!/ax_cflags_warn_all.m4
+!/ax_check_compile_flag.m4
+!/ax_compiler_vendor.m4
+!/ax_prepend_flag.m4
+!/ax_require_defined.m4
+!/gcc_version.m4
+!/gl_manywarnings.m4
+!/gl_warnings.m4
diff --git a/m4/ax_append_flag.m4 b/m4/ax_append_flag.m4
index e8c5312..dd6d8b6 100644
--- a/m4/ax_append_flag.m4
+++ b/m4/ax_append_flag.m4
@@ -23,33 +23,12 @@
# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
#
-# This program 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.
-#
-# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
-#
-# As a special exception, the respective Autoconf Macro's copyright owner
-# gives unlimited permission to copy, distribute and modify the configure
-# scripts that are the output of Autoconf when processing the Macro. You
-# need not follow the terms of the GNU General Public License when using
-# or distributing such scripts, even though portions of the text of the
-# Macro appear in them. The GNU General Public License (GPL) does govern
-# all other use of the material that constitutes the Autoconf Macro.
-#
-# This special exception to the GPL applies to versions of the Autoconf
-# Macro released by the Autoconf Archive. When you make and distribute a
-# modified version of the Autoconf Macro, you may extend this special
-# exception to the GPL to apply to your modified version as well.
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
-#serial 7
+#serial 8
AC_DEFUN([AX_APPEND_FLAG],
[dnl
diff --git a/m4/ax_cflags_warn_all.m4 b/m4/ax_cflags_warn_all.m4
index 094577e..9235a18 100644
--- a/m4/ax_cflags_warn_all.m4
+++ b/m4/ax_cflags_warn_all.m4
@@ -4,33 +4,54 @@
#
# SYNOPSIS
#
-# AX_CFLAGS_WARN_ALL [(shellvar [,default, [A/NA]])]
-# AX_CXXFLAGS_WARN_ALL [(shellvar [,default, [A/NA]])]
-# AX_FCFLAGS_WARN_ALL [(shellvar [,default, [A/NA]])]
+# AX_CFLAGS_WARN_ALL [(shellvar[, default[, action-if-found[, action-if-not-found]]])]
+# AX_CXXFLAGS_WARN_ALL [(shellvar[, default[, action-if-found[, action-if-not-found]]])]
+# AX_FCFLAGS_WARN_ALL [(shellvar[, default[, action-if-found[, action-if-not-found]]])]
#
# DESCRIPTION
#
-# Try to find a compiler option that enables most reasonable warnings.
+# Specify compiler options that enable most reasonable warnings. For the
+# GNU Compiler Collection (GCC), for example, it will be "-Wall". The
+# result is added to shellvar, one of CFLAGS, CXXFLAGS or FCFLAGS if the
+# first parameter is not specified.
#
-# For the GNU compiler it will be -Wall (and -ansi -pedantic) The result
-# is added to the shellvar being CFLAGS, CXXFLAGS, or FCFLAGS by default.
+# Each of these macros accepts the following optional arguments:
#
-# Currently this macro knows about the GCC, Solaris, Digital Unix, AIX,
-# HP-UX, IRIX, NEC SX-5 (Super-UX 10), Cray J90 (Unicos 10.0.0.8), and
-# Intel compilers. For a given compiler, the Fortran flags are much more
-# experimental than their C equivalents.
+# - $1 - shellvar
+# shell variable to use (CFLAGS, CXXFLAGS or FCFLAGS if not
+# specified, depending on macro)
#
-# - $1 shell-variable-to-add-to : CFLAGS, CXXFLAGS, or FCFLAGS
-# - $2 add-value-if-not-found : nothing
-# - $3 action-if-found : add value to shellvariable
-# - $4 action-if-not-found : nothing
+# - $2 - default
+# value to use for flags if compiler vendor cannot be determined (by
+# default, "")
#
-# NOTE: These macros depend on AX_APPEND_FLAG.
+# - $3 - action-if-found
+# action to take if the compiler vendor has been successfully
+# determined (by default, add the appropriate compiler flags to
+# shellvar)
+#
+# - $4 - action-if-not-found
+# action to take if the compiler vendor has not been determined or
+# is unknown (by default, add the default flags, or "" if not
+# specified, to shellvar)
+#
+# These macros use AX_COMPILER_VENDOR to determine which flags should be
+# returned for a given compiler. Not all compilers currently have flags
+# defined for them; patches are welcome. If need be, compiler flags may
+# be made language-dependent: use a construct like the following:
+#
+# [vendor_name], [m4_if(_AC_LANG_PREFIX,[C], VAR="--relevant-c-flags",dnl
+# m4_if(_AC_LANG_PREFIX,[CXX], VAR="--relevant-c++-flags",dnl
+# m4_if(_AC_LANG_PREFIX,[FC], VAR="--relevant-fortran-flags",dnl
+# VAR="$2"; FOUND="no")))],
+#
+# Note: These macros also depend on AX_PREPEND_FLAG.
#
# LICENSE
#
# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
# Copyright (c) 2010 Rhys Ulerich <rhys.ulerich@gmail.com>
+# Copyright (c) 2018 John Zaitseff <J.Zaitseff@zap.org.au>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
@@ -58,65 +79,80 @@
# modified version of the Autoconf Macro, you may extend this special
# exception to the GPL to apply to your modified version as well.
-#serial 16
+#serial 25
+
+AC_DEFUN([AX_FLAGS_WARN_ALL], [
+ AX_REQUIRE_DEFINED([AX_PREPEND_FLAG])dnl
+ AC_REQUIRE([AX_COMPILER_VENDOR])dnl
+
+ AS_VAR_PUSHDEF([FLAGS], [m4_default($1,_AC_LANG_PREFIX[]FLAGS)])dnl
+ AS_VAR_PUSHDEF([VAR], [ac_cv_[]_AC_LANG_ABBREV[]flags_warn_all])dnl
+ AS_VAR_PUSHDEF([FOUND], [ac_save_[]_AC_LANG_ABBREV[]flags_warn_all_found])dnl
+
+ AC_CACHE_CHECK([FLAGS for most reasonable warnings], VAR, [
+ VAR=""
+ FOUND="yes"
+ dnl Cases are listed in the order found in ax_compiler_vendor.m4
+ AS_CASE("$ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor",
+ [intel], [VAR="-w2"],
+ [ibm], [VAR="-qsrcmsg -qinfo=all:noppt:noppc:noobs:nocnd"],
+ [pathscale], [],
+ [clang], [VAR="-Wall"],
+ [cray], [VAR="-h msglevel 2"],
+ [fujitsu], [],
+ [sdcc], [],
+ [sx], [VAR="-pvctl[,]fullmsg"],
+ [portland], [],
+ [gnu], [VAR="-Wall"],
+ [sun], [VAR="-v"],
+ [hp], [VAR="+w1"],
+ [dec], [VAR="-verbose -w0 -warnprotos"],
+ [borland], [],
+ [comeau], [],
+ [kai], [],
+ [lcc], [],
+ [sgi], [VAR="-fullwarn"],
+ [microsoft], [],
+ [metrowerks], [],
+ [watcom], [],
+ [tcc], [],
+ [unknown], [
+ VAR="$2"
+ FOUND="no"
+ ],
+ [
+ AC_MSG_WARN([Unknown compiler vendor returned by [AX_COMPILER_VENDOR]])
+ VAR="$2"
+ FOUND="no"
+ ]
+ )
+
+ AS_IF([test "x$FOUND" = "xyes"], [dnl
+ m4_default($3, [AS_IF([test "x$VAR" != "x"], [AX_PREPEND_FLAG([$VAR], [FLAGS])])])
+ ], [dnl
+ m4_default($4, [m4_ifval($2, [AX_PREPEND_FLAG([$VAR], [FLAGS])], [true])])
+ ])dnl
+ ])dnl
-AC_DEFUN([AX_FLAGS_WARN_ALL],[dnl
-AS_VAR_PUSHDEF([FLAGS],[_AC_LANG_PREFIX[]FLAGS])dnl
-AS_VAR_PUSHDEF([VAR],[ac_cv_[]_AC_LANG_ABBREV[]flags_warn_all])dnl
-AC_CACHE_CHECK([m4_ifval($1,$1,FLAGS) for maximum warnings],
-VAR,[VAR="no, unknown"
-ac_save_[]FLAGS="$[]FLAGS"
-for ac_arg dnl
-in "-warn all % -warn all" dnl Intel
- "-pedantic % -Wall" dnl GCC
- "-xstrconst % -v" dnl Solaris C
- "-std1 % -verbose -w0 -warnprotos" dnl Digital Unix
- "-qlanglvl=ansi % -qsrcmsg -qinfo=all:noppt:noppc:noobs:nocnd" dnl AIX
- "-ansi -ansiE % -fullwarn" dnl IRIX
- "+ESlit % +w1" dnl HP-UX C
- "-Xc % -pvctl[,]fullmsg" dnl NEC SX-5 (Super-UX 10)
- "-h conform % -h msglevel 2" dnl Cray C (Unicos)
- #
-do FLAGS="$ac_save_[]FLAGS "`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'`
- AC_COMPILE_IFELSE([AC_LANG_PROGRAM],
- [VAR=`echo $ac_arg | sed -e 's,.*% *,,'` ; break])
-done
-FLAGS="$ac_save_[]FLAGS"
-])
-AS_VAR_POPDEF([FLAGS])dnl
-AX_REQUIRE_DEFINED([AX_APPEND_FLAG])
-case ".$VAR" in
- .ok|.ok,*) m4_ifvaln($3,$3) ;;
- .|.no|.no,*) m4_default($4,[m4_ifval($2,[AX_APPEND_FLAG([$2], [$1])])]) ;;
- *) m4_default($3,[AX_APPEND_FLAG([$VAR], [$1])]) ;;
-esac
-AS_VAR_POPDEF([VAR])dnl
+ AS_VAR_POPDEF([FOUND])dnl
+ AS_VAR_POPDEF([VAR])dnl
+ AS_VAR_POPDEF([FLAGS])dnl
])dnl AX_FLAGS_WARN_ALL
-dnl implementation tactics:
-dnl the for-argument contains a list of options. The first part of
-dnl these does only exist to detect the compiler - usually it is
-dnl a global option to enable -ansi or -extrawarnings. All other
-dnl compilers will fail about it. That was needed since a lot of
-dnl compilers will give false positives for some option-syntax
-dnl like -Woption or -Xoption as they think of it is a pass-through
-dnl to later compile stages or something. The "%" is used as a
-dnl delimiter. A non-option comment can be given after "%%" marks
-dnl which will be shown but not added to the respective C/CXXFLAGS.
-AC_DEFUN([AX_CFLAGS_WARN_ALL],[dnl
-AC_LANG_PUSH([C])
-AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4])
-AC_LANG_POP([C])
-])
+AC_DEFUN([AX_CFLAGS_WARN_ALL], [dnl
+ AC_LANG_PUSH([C])
+ AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4])
+ AC_LANG_POP([C])
+])dnl
-AC_DEFUN([AX_CXXFLAGS_WARN_ALL],[dnl
-AC_LANG_PUSH([C++])
-AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4])
-AC_LANG_POP([C++])
-])
+AC_DEFUN([AX_CXXFLAGS_WARN_ALL], [dnl
+ AC_LANG_PUSH([C++])
+ AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4])
+ AC_LANG_POP([C++])
+])dnl
-AC_DEFUN([AX_FCFLAGS_WARN_ALL],[dnl
-AC_LANG_PUSH([Fortran])
-AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4])
-AC_LANG_POP([Fortran])
-])
+AC_DEFUN([AX_FCFLAGS_WARN_ALL], [dnl
+ AC_LANG_PUSH([Fortran])
+ AX_FLAGS_WARN_ALL([$1], [$2], [$3], [$4])
+ AC_LANG_POP([Fortran])
+])dnl
diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4
index dcabb92..bd753b3 100644
--- a/m4/ax_check_compile_flag.m4
+++ b/m4/ax_check_compile_flag.m4
@@ -29,33 +29,12 @@
# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
#
-# This program 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.
-#
-# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
-#
-# As a special exception, the respective Autoconf Macro's copyright owner
-# gives unlimited permission to copy, distribute and modify the configure
-# scripts that are the output of Autoconf when processing the Macro. You
-# need not follow the terms of the GNU General Public License when using
-# or distributing such scripts, even though portions of the text of the
-# Macro appear in them. The GNU General Public License (GPL) does govern
-# all other use of the material that constitutes the Autoconf Macro.
-#
-# This special exception to the GPL applies to versions of the Autoconf
-# Macro released by the Autoconf Archive. When you make and distribute a
-# modified version of the Autoconf Macro, you may extend this special
-# exception to the GPL to apply to your modified version as well.
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
-#serial 5
+#serial 6
AC_DEFUN([AX_CHECK_COMPILE_FLAG],
[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
diff --git a/m4/ax_compiler_vendor.m4 b/m4/ax_compiler_vendor.m4
new file mode 100644
index 0000000..039f99d
--- /dev/null
+++ b/m4/ax_compiler_vendor.m4
@@ -0,0 +1,119 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_compiler_vendor.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_COMPILER_VENDOR
+#
+# DESCRIPTION
+#
+# Determine the vendor of the C, C++ or Fortran compiler. The vendor is
+# returned in the cache variable $ax_cv_c_compiler_vendor for C,
+# $ax_cv_cxx_compiler_vendor for C++ or $ax_cv_fc_compiler_vendor for
+# (modern) Fortran. The value is one of "intel", "ibm", "pathscale",
+# "clang" (LLVM), "cray", "fujitsu", "sdcc", "sx", "nvhpc" (NVIDIA HPC
+# Compiler), "portland" (PGI), "gnu" (GCC), "sun" (Oracle Developer
+# Studio), "hp", "dec", "borland", "comeau", "kai", "lcc", "sgi",
+# "microsoft", "metrowerks", "watcom", "tcc" (Tiny CC) or "unknown" (if
+# the compiler cannot be determined).
+#
+# To check for a Fortran compiler, you must first call AC_FC_PP_SRCEXT
+# with an appropriate preprocessor-enabled extension. For example:
+#
+# AC_LANG_PUSH([Fortran])
+# AC_PROG_FC
+# AC_FC_PP_SRCEXT([F])
+# AX_COMPILER_VENDOR
+# AC_LANG_POP([Fortran])
+#
+# LICENSE
+#
+# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>
+# Copyright (c) 2008 Matteo Frigo
+# Copyright (c) 2018-19 John Zaitseff <J.Zaitseff@zap.org.au>
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 32
+
+AC_DEFUN([AX_COMPILER_VENDOR], [dnl
+ AC_CACHE_CHECK([for _AC_LANG compiler vendor], ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor, [dnl
+ dnl If you modify this list of vendors, please add similar support
+ dnl to ax_compiler_version.m4 if at all possible.
+ dnl
+ dnl Note: Do NOT check for GCC first since some other compilers
+ dnl define __GNUC__ to remain compatible with it. Compilers that
+ dnl are very slow to start (such as Intel) are listed first.
+
+ vendors="
+ intel: __ICC,__ECC,__INTEL_COMPILER
+ ibm: __xlc__,__xlC__,__IBMC__,__IBMCPP__,__ibmxl__
+ pathscale: __PATHCC__,__PATHSCALE__
+ clang: __clang__
+ cray: _CRAYC
+ fujitsu: __FUJITSU
+ sdcc: SDCC,__SDCC
+ sx: _SX
+ nvhpc: __NVCOMPILER
+ portland: __PGI
+ gnu: __GNUC__
+ sun: __SUNPRO_C,__SUNPRO_CC,__SUNPRO_F90,__SUNPRO_F95
+ hp: __HP_cc,__HP_aCC
+ dec: __DECC,__DECCXX,__DECC_VER,__DECCXX_VER
+ borland: __BORLANDC__,__CODEGEARC__,__TURBOC__
+ comeau: __COMO__
+ kai: __KCC
+ lcc: __LCC__
+ sgi: __sgi,sgi
+ microsoft: _MSC_VER
+ metrowerks: __MWERKS__
+ watcom: __WATCOMC__
+ tcc: __TINYC__
+ unknown: UNKNOWN
+ "
+ for ventest in $vendors; do
+ case $ventest in
+ *:)
+ vendor=$ventest
+ continue
+ ;;
+ *)
+ vencpp="defined("`echo $ventest | sed 's/,/) || defined(/g'`")"
+ ;;
+ esac
+
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [[
+#if !($vencpp)
+ thisisanerror;
+#endif
+ ]])], [break])
+ done
+
+ ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor=`echo $vendor | cut -d: -f1`
+ ])
+])dnl
diff --git a/m4/ax_prepend_flag.m4 b/m4/ax_prepend_flag.m4
new file mode 100644
index 0000000..adac8c5
--- /dev/null
+++ b/m4/ax_prepend_flag.m4
@@ -0,0 +1,51 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_prepend_flag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_PREPEND_FLAG(FLAG, [FLAGS-VARIABLE])
+#
+# DESCRIPTION
+#
+# FLAG is added to the front of the FLAGS-VARIABLE shell variable, with a
+# space added in between.
+#
+# If FLAGS-VARIABLE is not specified, the current language's flags (e.g.
+# CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains
+# FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly
+# FLAG.
+#
+# NOTE: Implementation based on AX_APPEND_FLAG.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
+# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
+# Copyright (c) 2018 John Zaitseff <J.Zaitseff@zap.org.au>
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 2
+
+AC_DEFUN([AX_PREPEND_FLAG],
+[dnl
+AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF
+AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])])
+AS_VAR_SET_IF(FLAGS,[
+ AS_CASE([" AS_VAR_GET(FLAGS) "],
+ [*" $1 "*], [AC_RUN_LOG([: FLAGS already contains $1])],
+ [
+ FLAGS="$1 $FLAGS"
+ AC_RUN_LOG([: FLAGS="$FLAGS"])
+ ])
+ ],
+ [
+ AS_VAR_SET(FLAGS,[$1])
+ AC_RUN_LOG([: FLAGS="$FLAGS"])
+ ])
+AS_VAR_POPDEF([FLAGS])dnl
+])dnl AX_PREPEND_FLAG
diff --git a/m4/gcc_version.m4 b/m4/gcc_version.m4
index 9e16450..703973c 100644
--- a/m4/gcc_version.m4
+++ b/m4/gcc_version.m4
@@ -1,22 +1,18 @@
-dnl Copied from GNU Coreutils configure.ac
-dnl
-dnl The 17 lines are probably too trivial to be copyrightable, but just in case:
-dnl
-dnl Copyright (C) 1991-2017 Free Software Foundation, Inc.
-dnl
-dnl This program is free software: you can redistribute it and/or modify
-dnl it under the terms of the GNU General Public License as published by
-dnl the Free Software Foundation, either version 3 of the License, or
-dnl (at your option) any later version.
-dnl
-dnl This program is distributed in the hope that it will be useful,
-dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
-dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-dnl GNU General Public License for more details.
-dnl
-dnl You should have received a copy of the GNU General Public License
-dnl along with this program. If not, see <https://www.gnu.org/licenses/>.
-dnl
+# Copyright (C) 1991-2023 Free Software Foundation, Inc.
+
+# This program 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.
+
+# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
+
dnl Written by Jim Meyering.
# gl_GCC_VERSION_IFELSE([major], [minor], [run-if-found], [run-if-not-found])
diff --git a/m4/gl_manywarnings.m4 b/m4/gl_manywarnings.m4
index d10bcd0..a06f26f 100644
--- a/m4/gl_manywarnings.m4
+++ b/m4/gl_manywarnings.m4
@@ -1,11 +1,13 @@
-# manywarnings.m4 serial 13
-dnl Copyright (C) 2008-2017 Free Software Foundation, Inc.
+# manywarnings.m4 serial 24
+dnl Copyright (C) 2008-2023 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
dnl with or without modifications, as long as this notice is preserved.
dnl From Simon Josefsson
+AC_PREREQ([2.64])
+
# gl_MANYWARN_COMPLEMENT(OUTVAR, LISTVAR, REMOVEVAR)
# --------------------------------------------------
# Copy LISTVAR to OUTVAR except for the entries in REMOVEVAR.
@@ -21,7 +23,7 @@ AC_DEFUN([gl_MANYWARN_COMPLEMENT],
*" $gl_warn_item "*)
;;
*)
- gl_warn_set="$gl_warn_set $gl_warn_item"
+ AS_VAR_APPEND([gl_warn_set], [" $gl_warn_item"])
;;
esac
done
@@ -39,291 +41,166 @@ AC_DEFUN([gl_MANYWARN_ALL_GCC],
[_AC_LANG_DISPATCH([$0], _AC_LANG, $@)])
# Specialization for _AC_LANG = C.
-# Use of m4_defun rather than AC_DEFUN works around a bug in autoconf < 2.63b.
-m4_defun([gl_MANYWARN_ALL_GCC(C)],
+AC_DEFUN([gl_MANYWARN_ALL_GCC(C)],
[
AC_LANG_PUSH([C])
dnl First, check for some issues that only occur when combining multiple
dnl gcc warning categories.
AC_REQUIRE([AC_PROG_CC])
- if test -n "$GCC"; then
-
- dnl Check if -W -Werror -Wno-missing-field-initializers is supported
- dnl with the current $CC $CFLAGS $CPPFLAGS.
- AC_MSG_CHECKING([whether -Wno-missing-field-initializers is supported])
- AC_CACHE_VAL([gl_cv_cc_nomfi_supported], [
- gl_save_CFLAGS="$CFLAGS"
- CFLAGS="$CFLAGS -W -Werror -Wno-missing-field-initializers"
- AC_COMPILE_IFELSE(
- [AC_LANG_PROGRAM([[]], [[]])],
- [gl_cv_cc_nomfi_supported=yes],
- [gl_cv_cc_nomfi_supported=no])
- CFLAGS="$gl_save_CFLAGS"])
- AC_MSG_RESULT([$gl_cv_cc_nomfi_supported])
-
- if test "$gl_cv_cc_nomfi_supported" = yes; then
- dnl Now check whether -Wno-missing-field-initializers is needed
- dnl for the { 0, } construct.
- AC_MSG_CHECKING([whether -Wno-missing-field-initializers is needed])
- AC_CACHE_VAL([gl_cv_cc_nomfi_needed], [
- gl_save_CFLAGS="$CFLAGS"
- CFLAGS="$CFLAGS -W -Werror"
- AC_COMPILE_IFELSE(
- [AC_LANG_PROGRAM(
- [[int f (void)
- {
- typedef struct { int a; int b; } s_t;
- s_t s1 = { 0, };
- return s1.b;
- }
- ]],
- [[]])],
- [gl_cv_cc_nomfi_needed=no],
- [gl_cv_cc_nomfi_needed=yes])
- CFLAGS="$gl_save_CFLAGS"
- ])
- AC_MSG_RESULT([$gl_cv_cc_nomfi_needed])
- fi
+ AS_IF([test -n "$GCC"], [
+ AC_CACHE_CHECK([whether -Wno-missing-field-initializers is needed],
+ [gl_cv_cc_nomfi_needed],
+ [gl_cv_cc_nomfi_needed=no
+ gl_save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS -Wextra -Werror"
+ AC_COMPILE_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[struct file_data { int desc, name; };
+ struct cmp { struct file_data file[1]; };
+ void f (struct cmp *r)
+ {
+ typedef struct { int a; int b; } s_t;
+ s_t s1 = { 0, };
+ struct cmp cmp = { .file[0].desc = r->file[0].desc + s1.a };
+ *r = cmp;
+ }
+ ]],
+ [[]])],
+ [],
+ [CFLAGS="$CFLAGS -Wno-missing-field-initializers"
+ AC_COMPILE_IFELSE([],
+ [gl_cv_cc_nomfi_needed=yes])])
+ CFLAGS="$gl_save_CFLAGS"
+ ])
dnl Next, check if -Werror -Wuninitialized is useful with the
dnl user's choice of $CFLAGS; some versions of gcc warn that it
dnl has no effect if -O is not also used
- AC_MSG_CHECKING([whether -Wuninitialized is supported])
- AC_CACHE_VAL([gl_cv_cc_uninitialized_supported], [
- gl_save_CFLAGS="$CFLAGS"
- CFLAGS="$CFLAGS -Werror -Wuninitialized"
- AC_COMPILE_IFELSE(
- [AC_LANG_PROGRAM([[]], [[]])],
- [gl_cv_cc_uninitialized_supported=yes],
- [gl_cv_cc_uninitialized_supported=no])
- CFLAGS="$gl_save_CFLAGS"])
- AC_MSG_RESULT([$gl_cv_cc_uninitialized_supported])
-
- fi
+ AC_CACHE_CHECK([whether -Wuninitialized is supported],
+ [gl_cv_cc_uninitialized_supported],
+ [gl_save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS -Werror -Wuninitialized"
+ AC_COMPILE_IFELSE(
+ [AC_LANG_PROGRAM([[]], [[]])],
+ [gl_cv_cc_uninitialized_supported=yes],
+ [gl_cv_cc_uninitialized_supported=no])
+ CFLAGS="$gl_save_CFLAGS"
+ ])
+ ])
# List all gcc warning categories.
# To compare this list to your installed GCC's, run this Bash command:
#
# comm -3 \
- # <(sed -n 's/^ *\(-[^ ]*\) .*/\1/p' manywarnings.m4 | sort) \
- # <(gcc --help=warnings | sed -n 's/^ \(-[^ ]*\) .*/\1/p' | sort |
- # grep -v -x -F -f <(
- # awk '/^[^#]/ {print $1}' ../build-aux/gcc-warning.spec))
+ # <((sed -n 's/^ *\(-[^ 0-9][^ ]*\).*/\1/p' manywarnings.m4; \
+ # awk '/^[^#]/ {print $1}' ../build-aux/gcc-warning.spec) | sort) \
+ # <(LC_ALL=C gcc --help=warnings | sed -n 's/^ \(-[^ ]*\) .*/\1/p' | sort)
- gl_manywarn_set=
- for gl_manywarn_item in -fno-common \
- -W \
- -Wabi \
- -Waddress \
- -Waggressive-loop-optimizations \
+ $1=
+ for gl_manywarn_item in -fanalyzer -fstrict-flex-arrays \
-Wall \
- -Wattributes \
+ -Warith-conversion \
-Wbad-function-cast \
- -Wbool-compare \
- -Wbool-operation \
- -Wbuiltin-declaration-mismatch \
- -Wbuiltin-macro-redefined \
- -Wcast-align \
- -Wchar-subscripts \
- -Wchkp \
- -Wclobbered \
- -Wcomment \
- -Wcomments \
- -Wcoverage-mismatch \
- -Wcpp \
- -Wdangling-else \
+ -Wcast-align=strict \
-Wdate-time \
- -Wdeprecated \
- -Wdeprecated-declarations \
- -Wdesignated-init \
-Wdisabled-optimization \
- -Wdiscarded-array-qualifiers \
- -Wdiscarded-qualifiers \
- -Wdiv-by-zero \
-Wdouble-promotion \
-Wduplicated-branches \
-Wduplicated-cond \
- -Wduplicate-decl-specifier \
- -Wempty-body \
- -Wendif-labels \
- -Wenum-compare \
- -Wexpansion-to-defined \
-Wextra \
- -Wformat-contains-nul \
- -Wformat-extra-args \
- -Wformat-nonliteral \
- -Wformat-security \
-Wformat-signedness \
- -Wformat-y2k \
- -Wformat-zero-length \
- -Wframe-address \
- -Wfree-nonheap-object \
- -Whsa \
- -Wignored-attributes \
- -Wignored-qualifiers \
- -Wimplicit \
- -Wimplicit-function-declaration \
- -Wimplicit-int \
- -Wincompatible-pointer-types \
-Winit-self \
-Winline \
- -Wint-conversion \
- -Wint-in-bool-context \
- -Wint-to-pointer-cast \
- -Winvalid-memory-model \
-Winvalid-pch \
- -Wjump-misses-init \
- -Wlogical-not-parentheses \
-Wlogical-op \
- -Wmain \
- -Wmaybe-uninitialized \
- -Wmemset-elt-size \
- -Wmemset-transposed-args \
- -Wmisleading-indentation \
- -Wmissing-braces \
-Wmissing-declarations \
- -Wmissing-field-initializers \
-Wmissing-include-dirs \
- -Wmissing-parameter-type \
-Wmissing-prototypes \
- -Wmultichar \
- -Wnarrowing \
-Wnested-externs \
- -Wnonnull \
- -Wnonnull-compare \
-Wnull-dereference \
- -Wodr \
- -Wold-style-declaration \
-Wold-style-definition \
-Wopenmp-simd \
- -Woverflow \
-Woverlength-strings \
- -Woverride-init \
-Wpacked \
- -Wpacked-bitfield-compat \
- -Wparentheses \
-Wpointer-arith \
- -Wpointer-compare \
- -Wpointer-sign \
- -Wpointer-to-int-cast \
- -Wpragmas \
- -Wpsabi \
- -Wrestrict \
- -Wreturn-local-addr \
- -Wreturn-type \
- -Wscalar-storage-order \
- -Wsequence-point \
-Wshadow \
- -Wshift-count-negative \
- -Wshift-count-overflow \
- -Wshift-negative-value \
- -Wsizeof-array-argument \
- -Wsizeof-pointer-memaccess \
-Wstack-protector \
- -Wstrict-aliasing \
+ -Wstrict-flex-arrays \
-Wstrict-overflow \
-Wstrict-prototypes \
+ -Wsuggest-attribute=cold \
-Wsuggest-attribute=const \
-Wsuggest-attribute=format \
+ -Wsuggest-attribute=malloc \
-Wsuggest-attribute=noreturn \
-Wsuggest-attribute=pure \
-Wsuggest-final-methods \
-Wsuggest-final-types \
- -Wswitch \
- -Wswitch-bool \
- -Wswitch-default \
- -Wswitch-unreachable \
-Wsync-nand \
-Wsystem-headers \
- -Wtautological-compare \
-Wtrampolines \
- -Wtrigraphs \
- -Wtype-limits \
-Wuninitialized \
-Wunknown-pragmas \
-Wunsafe-loop-optimizations \
- -Wunused \
- -Wunused-but-set-parameter \
- -Wunused-but-set-variable \
- -Wunused-function \
- -Wunused-label \
- -Wunused-local-typedefs \
-Wunused-macros \
- -Wunused-parameter \
- -Wunused-result \
- -Wunused-value \
- -Wunused-variable \
- -Wvarargs \
-Wvariadic-macros \
-Wvector-operation-performance \
-Wvla \
- -Wvolatile-register-var \
-Wwrite-strings \
\
; do
- gl_manywarn_set="$gl_manywarn_set $gl_manywarn_item"
+ AS_VAR_APPEND([$1], [" $gl_manywarn_item"])
done
# gcc --help=warnings outputs an unusual form for these options; list
# them here so that the above 'comm' command doesn't report a false match.
- # Would prefer "min (PTRDIFF_MAX, SIZE_MAX)", but it must be a literal.
- # Also, AC_COMPUTE_INT requires it to fit in a long; it is 2**63 on
- # the only platforms where it does not fit in a long, so make that
- # a special case.
- AC_MSG_CHECKING([max safe object size])
- AC_COMPUTE_INT([gl_alloc_max],
- [LONG_MAX < (PTRDIFF_MAX < (size_t) -1 ? PTRDIFF_MAX : (size_t) -1)
- ? -1
- : PTRDIFF_MAX < (size_t) -1 ? (long) PTRDIFF_MAX : (long) (size_t) -1],
- [[#include <limits.h>
- #include <stddef.h>
- #include <stdint.h>
- ]],
- [gl_alloc_max=2147483647])
- case $gl_alloc_max in
- -1) gl_alloc_max=9223372036854775807;;
- esac
- AC_MSG_RESULT([$gl_alloc_max])
- gl_manywarn_set="$gl_manywarn_set -Walloc-size-larger-than=$gl_alloc_max"
- gl_manywarn_set="$gl_manywarn_set -Warray-bounds=2"
- gl_manywarn_set="$gl_manywarn_set -Wformat-overflow=2"
- gl_manywarn_set="$gl_manywarn_set -Wformat-truncation=2"
- gl_manywarn_set="$gl_manywarn_set -Wimplicit-fallthrough=5"
- gl_manywarn_set="$gl_manywarn_set -Wnormalized=nfc"
- gl_manywarn_set="$gl_manywarn_set -Wshift-overflow=2"
- gl_manywarn_set="$gl_manywarn_set -Wstringop-overflow=2"
- gl_manywarn_set="$gl_manywarn_set -Wunused-const-variable=2"
- gl_manywarn_set="$gl_manywarn_set -Wvla-larger-than=4031"
+ AS_VAR_APPEND([$1], [' -Warray-bounds=2'])
+ AS_VAR_APPEND([$1], [' -Wattribute-alias=2'])
+ AS_VAR_APPEND([$1], [' -Wbidi-chars=any,ucn'])
+ AS_VAR_APPEND([$1], [' -Wformat-overflow=2'])
+ AS_VAR_APPEND([$1], [' -Wformat=2'])
+ AS_VAR_APPEND([$1], [' -Wformat-truncation=2'])
+ AS_VAR_APPEND([$1], [' -Wimplicit-fallthrough=5'])
+ AS_VAR_APPEND([$1], [' -Wshift-overflow=2'])
+ AS_VAR_APPEND([$1], [' -Wuse-after-free=3'])
+ AS_VAR_APPEND([$1], [' -Wunused-const-variable=2'])
+ AS_VAR_APPEND([$1], [' -Wvla-larger-than=4031'])
# These are needed for older GCC versions.
- if test -n "$GCC"; then
- case `($CC --version) 2>/dev/null` in
+ if test -n "$GCC" && gl_gcc_version=`($CC --version) 2>/dev/null`; then
+ case $gl_gcc_version in
'gcc (GCC) '[[0-3]].* | \
'gcc (GCC) '4.[[0-7]].*)
- gl_manywarn_set="$gl_manywarn_set -fdiagnostics-show-option"
- gl_manywarn_set="$gl_manywarn_set -funit-at-a-time"
+ AS_VAR_APPEND([$1], [' -fdiagnostics-show-option'])
+ AS_VAR_APPEND([$1], [' -funit-at-a-time'])
+ ;;
+ esac
+ case $gl_gcc_version in
+ 'gcc (GCC) '[[0-9]].*)
+ AS_VAR_APPEND([$1], [' -fno-common'])
;;
esac
fi
# Disable specific options as needed.
if test "$gl_cv_cc_nomfi_needed" = yes; then
- gl_manywarn_set="$gl_manywarn_set -Wno-missing-field-initializers"
+ AS_VAR_APPEND([$1], [' -Wno-missing-field-initializers'])
fi
if test "$gl_cv_cc_uninitialized_supported" = no; then
- gl_manywarn_set="$gl_manywarn_set -Wno-uninitialized"
+ AS_VAR_APPEND([$1], [' -Wno-uninitialized'])
fi
- $1=$gl_manywarn_set
+ # This warning have too many false alarms in GCC 11.2.1.
+ # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101713
+ AS_VAR_APPEND([$1], [' -Wno-analyzer-malloc-leak'])
AC_LANG_POP([C])
])
# Specialization for _AC_LANG = C++.
-# Use of m4_defun rather than AC_DEFUN works around a bug in autoconf < 2.63b.
-m4_defun([gl_MANYWARN_ALL_GCC(C++)],
+AC_DEFUN([gl_MANYWARN_ALL_GCC(C++)],
[
gl_MANYWARN_ALL_GCC_CXX_IMPL([$1])
])
diff --git a/m4/gl_warnings.m4 b/m4/gl_warnings.m4
index 870472b..9433cb5 100644
--- a/m4/gl_warnings.m4
+++ b/m4/gl_warnings.m4
@@ -1,19 +1,12 @@
-# warnings.m4 serial 13
-dnl Copyright (C) 2008-2017 Free Software Foundation, Inc.
+# warnings.m4 serial 19
+dnl Copyright (C) 2008-2023 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
dnl with or without modifications, as long as this notice is preserved.
dnl From Simon Josefsson
-# gl_AS_VAR_APPEND(VAR, VALUE)
-# ----------------------------
-# Provide the functionality of AS_VAR_APPEND if Autoconf does not have it.
-m4_ifdef([AS_VAR_APPEND],
-[m4_copy([AS_VAR_APPEND], [gl_AS_VAR_APPEND])],
-[m4_define([gl_AS_VAR_APPEND],
-[AS_VAR_SET([$1], [AS_VAR_GET([$1])$2])])])
-
+AC_PREREQ([2.64])
# gl_COMPILER_OPTION_IF(OPTION, [IF-SUPPORTED], [IF-NOT-SUPPORTED],
# [PROGRAM = AC_LANG_PROGRAM()])
@@ -23,8 +16,6 @@ m4_ifdef([AS_VAR_APPEND],
# The effects of this macro depend on the current language (_AC_LANG).
AC_DEFUN([gl_COMPILER_OPTION_IF],
[
-dnl FIXME: gl_Warn must be used unquoted until we can assume Autoconf
-dnl 2.64 or newer.
AS_VAR_PUSHDEF([gl_Warn], [gl_cv_warn_[]_AC_LANG_ABBREV[]_$1])dnl
AS_VAR_PUSHDEF([gl_Flags], [_AC_LANG_PREFIX[]FLAGS])dnl
AS_LITERAL_IF([$1],
@@ -34,13 +25,13 @@ case $gl_positive in
-Wno-*) gl_positive=-W`expr "X$gl_positive" : 'X-Wno-\(.*\)'` ;;
esac
m4_pushdef([gl_Positive], [$gl_positive])])dnl
-AC_CACHE_CHECK([whether _AC_LANG compiler handles $1], m4_defn([gl_Warn]), [
+AC_CACHE_CHECK([whether _AC_LANG compiler handles $1], [gl_Warn], [
gl_save_compiler_FLAGS="$gl_Flags"
- gl_AS_VAR_APPEND(m4_defn([gl_Flags]),
+ AS_VAR_APPEND(m4_defn([gl_Flags]),
[" $gl_unknown_warnings_are_errors ]m4_defn([gl_Positive])["])
- AC_LINK_IFELSE([m4_default([$4], [AC_LANG_PROGRAM([])])],
- [AS_VAR_SET(gl_Warn, [yes])],
- [AS_VAR_SET(gl_Warn, [no])])
+ AC_LINK_IFELSE([m4_default([$4], [AC_LANG_PROGRAM([[]])])],
+ [AS_VAR_SET([gl_Warn], [yes])],
+ [AS_VAR_SET([gl_Warn], [no])])
gl_Flags="$gl_save_compiler_FLAGS"
])
AS_VAR_IF(gl_Warn, [yes], [$2], [$3])
@@ -59,8 +50,7 @@ AC_DEFUN([gl_UNKNOWN_WARNINGS_ARE_ERRORS],
[_AC_LANG_DISPATCH([$0], _AC_LANG, $@)])
# Specialization for _AC_LANG = C. This macro can be AC_REQUIREd.
-# Use of m4_defun rather than AC_DEFUN works around a bug in autoconf < 2.63b.
-m4_defun([gl_UNKNOWN_WARNINGS_ARE_ERRORS(C)],
+AC_DEFUN([gl_UNKNOWN_WARNINGS_ARE_ERRORS(C)],
[
AC_LANG_PUSH([C])
gl_UNKNOWN_WARNINGS_ARE_ERRORS_IMPL
@@ -68,14 +58,21 @@ m4_defun([gl_UNKNOWN_WARNINGS_ARE_ERRORS(C)],
])
# Specialization for _AC_LANG = C++. This macro can be AC_REQUIREd.
-# Use of m4_defun rather than AC_DEFUN works around a bug in autoconf < 2.63b.
-m4_defun([gl_UNKNOWN_WARNINGS_ARE_ERRORS(C++)],
+AC_DEFUN([gl_UNKNOWN_WARNINGS_ARE_ERRORS(C++)],
[
AC_LANG_PUSH([C++])
gl_UNKNOWN_WARNINGS_ARE_ERRORS_IMPL
AC_LANG_POP([C++])
])
+# Specialization for _AC_LANG = Objective C. This macro can be AC_REQUIREd.
+AC_DEFUN([gl_UNKNOWN_WARNINGS_ARE_ERRORS(Objective C)],
+[
+ AC_LANG_PUSH([Objective C])
+ gl_UNKNOWN_WARNINGS_ARE_ERRORS_IMPL
+ AC_LANG_POP([Objective C])
+])
+
AC_DEFUN([gl_UNKNOWN_WARNINGS_ARE_ERRORS_IMPL],
[gl_COMPILER_OPTION_IF([-Werror -Wunknown-warning-option],
[gl_unknown_warnings_are_errors='-Wunknown-warning-option -Werror'],
@@ -84,16 +81,18 @@ AC_DEFUN([gl_UNKNOWN_WARNINGS_ARE_ERRORS_IMPL],
# gl_WARN_ADD(OPTION, [VARIABLE = WARN_CFLAGS/WARN_CXXFLAGS],
# [PROGRAM = AC_LANG_PROGRAM()])
# -----------------------------------------------------------
-# Adds parameter to WARN_CFLAGS/WARN_CXXFLAGS if the compiler supports it
-# when compiling PROGRAM. For example, gl_WARN_ADD([-Wparentheses]).
+# Adds OPTION to VARIABLE (which defaults to WARN_CFLAGS or WARN_CXXFLAGS)
+# if the compiler supports it when compiling PROGRAM.
#
# If VARIABLE is a variable name, AC_SUBST it.
#
# The effects of this macro depend on the current language (_AC_LANG).
+#
+# Example: gl_WARN_ADD([-Wparentheses]).
AC_DEFUN([gl_WARN_ADD],
[AC_REQUIRE([gl_UNKNOWN_WARNINGS_ARE_ERRORS(]_AC_LANG[)])
gl_COMPILER_OPTION_IF([$1],
- [gl_AS_VAR_APPEND(m4_if([$2], [], [[WARN_]_AC_LANG_PREFIX[FLAGS]], [[$2]]), [" $1"])],
+ [AS_VAR_APPEND(m4_if([$2], [], [[WARN_]_AC_LANG_PREFIX[FLAGS]], [[$2]]), [" $1"])],
[],
[$3])
m4_ifval([$2],
@@ -101,6 +100,77 @@ m4_ifval([$2],
[AC_SUBST([WARN_]_AC_LANG_PREFIX[FLAGS])])dnl
])
+
+# gl_CC_INHIBIT_WARNINGS
+# sets and substitutes a variable GL_CFLAG_INHIBIT_WARNINGS, to a $(CC) option
+# that reverts all preceding -W* options, if available.
+# This is expected to be '-w' at least on gcc, clang, AIX xlc, xlclang, Sun cc,
+# "compile cl" (MSVC), "compile clang-cl" (MSVC-compatible clang). Or it can be
+# empty.
+AC_DEFUN([gl_CC_INHIBIT_WARNINGS],
+[
+ AC_REQUIRE([AC_PROG_CC])
+ AC_CACHE_CHECK([for C compiler option to inhibit all warnings],
+ [gl_cv_cc_winhibit],
+ [rm -f conftest*
+ echo 'int dummy;' > conftest.c
+ AC_TRY_COMMAND([${CC-cc} $CFLAGS $CPPFLAGS -c conftest.c 2>conftest1.err]) >/dev/null
+ AC_TRY_COMMAND([${CC-cc} $CFLAGS $CPPFLAGS -w -c conftest.c 2>conftest2.err]) >/dev/null
+ if test $? = 0 && test `wc -l < conftest1.err` = `wc -l < conftest2.err`; then
+ gl_cv_cc_winhibit='-w'
+ else
+ gl_cv_cc_winhibit=none
+ fi
+ rm -f conftest*
+ ])
+ case "$gl_cv_cc_winhibit" in
+ none) GL_CFLAG_INHIBIT_WARNINGS='' ;;
+ *)
+ GL_CFLAG_INHIBIT_WARNINGS="$gl_cv_cc_winhibit"
+ dnl If all warnings are inhibited, there's no point in having the GCC
+ dnl analyzer enabled. This saves RAM requirements and CPU consumption.
+ gl_WARN_ADD([-fno-analyzer], [GL_CFLAG_INHIBIT_WARNINGS])
+ ;;
+ esac
+ AC_SUBST([GL_CFLAG_INHIBIT_WARNINGS])
+])
+
+# gl_CXX_INHIBIT_WARNINGS
+# sets and substitutes a variable GL_CXXFLAG_INHIBIT_WARNINGS, to a $(CC) option
+# that reverts all preceding -W* options, if available.
+AC_DEFUN([gl_CXX_INHIBIT_WARNINGS],
+[
+ dnl Requires AC_PROG_CXX or gl_PROG_ANSI_CXX.
+ if test -n "$CXX" && test "$CXX" != no; then
+ AC_CACHE_CHECK([for C++ compiler option to inhibit all warnings],
+ [gl_cv_cxx_winhibit],
+ [rm -f conftest*
+ echo 'int dummy;' > conftest.cc
+ AC_TRY_COMMAND([${CXX-c++} $CXXFLAGS $CPPFLAGS -c conftest.cc 2>conftest1.err]) >/dev/null
+ AC_TRY_COMMAND([${CXX-c++} $CXXFLAGS $CPPFLAGS -w -c conftest.cc 2>conftest2.err]) >/dev/null
+ if test $? = 0 && test `wc -l < conftest1.err` = `wc -l < conftest2.err`; then
+ gl_cv_cxx_winhibit='-w'
+ else
+ gl_cv_cxx_winhibit=none
+ fi
+ rm -f conftest*
+ ])
+ case "$gl_cv_cxx_winhibit" in
+ none) GL_CXXFLAG_INHIBIT_WARNINGS='' ;;
+ *)
+ GL_CXXFLAG_INHIBIT_WARNINGS="$gl_cv_cxx_winhibit"
+ dnl If all warnings are inhibited, there's no point in having the GCC
+ dnl analyzer enabled. This saves RAM requirements and CPU consumption.
+ gl_WARN_ADD([-fno-analyzer], [GL_CXXFLAG_INHIBIT_WARNINGS])
+ ;;
+ esac
+ else
+ GL_CXXFLAG_INHIBIT_WARNINGS=''
+ fi
+ AC_SUBST([GL_CXXFLAG_INHIBIT_WARNINGS])
+])
+
+
# Local Variables:
# mode: autoconf
# End:
diff --git a/man/.gitignore b/man/.gitignore
new file mode 100644
index 0000000..ba0d22a
--- /dev/null
+++ b/man/.gitignore
@@ -0,0 +1 @@
+*.?
diff --git a/man/atsign.1in b/man/atsign.1in
new file mode 100644
index 0000000..6cc1123
--- /dev/null
+++ b/man/atsign.1in
@@ -0,0 +1,41 @@
+.TH ATSIGN "1" "2023-10-14" "@PACKAGE_STRING@"
+
+.SH NAME
+@ATSIGN@ \- delay until a specified time
+
+.SH SYNOPSIS
+.B @ATSIGN@
+[\fI\,date\/\fR]\fI\,time\/\fR
+
+.SH OPTIONS
+
+.TP
+\fB\-F\fR, \fB\-\-list\-formats\fR
+List all supported date and time formats
+
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Show this help information
+
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Show version information
+
+.SH "REPORTING BUGS"
+Report bugs to <@PACKAGE_BUGREPORT@>.
+
+.SH COPYRIGHT
+Copyright \(co 2023 P. J. McDermott
+.PP
+@ 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.
+.PP
+@ 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.
+.PP
+You should have received a copy of the GNU General Public License
+along with @. If not, see <http://www.gnu.org/licenses/>.
diff --git a/man/local.mk b/man/local.mk
new file mode 100644
index 0000000..65814fe
--- /dev/null
+++ b/man/local.mk
@@ -0,0 +1,43 @@
+# Process this file with automake to produce an input makefile.
+#
+# Copyright (C) 2018, 2023 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/>.
+
+man1_sources = \
+ %reldir%/atsign.1in
+man1_MANS = $(man1_sources:.1in=.1)
+
+CLEANFILES += \
+ $(man1_MANS)
+EXTRA_DIST += \
+ $(man1_sources)
+
+if ENABLE_SYMLINK
+atsign = @$(EXEEXT)
+else !ENABLE_SYMLINK
+atsign = $$(printf 'atsign' | sed '$(transform); s/$$/$(EXEEXT)/')
+endif
+man_subst = sed \
+ -e "s|[@]PACKAGE_STRING[@]|$(PACKAGE_STRING)|g" \
+ -e "s|[@]PACKAGE_BUGREPORT[@]|$(PACKAGE_BUGREPORT)|g" \
+ -e "s|[@]ATSIGN[@]|$(atsign)|g"
+
+SUFFIXES += .1in .1
+
+.1in.1:
+ $(AM_V_GEN)$(MKDIR_P) "$$(dirname $@)"
+ $(AM_V_at)$(man_subst) $< >$@
diff --git a/po/.gitignore b/po/.gitignore
new file mode 100644
index 0000000..532f823
--- /dev/null
+++ b/po/.gitignore
@@ -0,0 +1,11 @@
+/Makefile.in.in
+/Makevars.template
+/Rules-quot
+/*.sed
+/POTFILES
+/*.pot
+/*.po
+/*.mo
+/*@boldquot.header
+/*@quot.header
+/stamp-po
diff --git a/po/Makevars b/po/Makevars
new file mode 100644
index 0000000..1753e43
--- /dev/null
+++ b/po/Makevars
@@ -0,0 +1,26 @@
+# Variables to be inserted into Makefile
+
+DOMAIN = $(PACKAGE)
+
+subdir = po
+top_builddir = ..
+
+XGETTEXT_OPTIONS = --add-comments=TRANSLATORS --keyword=_
+
+COPYRIGHT_HOLDER = P. J. McDermott
+
+PACKAGE_GNU = no
+
+MSGID_BUGS_ADDRESS = mailto:pj@pehjota.net
+
+EXTRA_LOCALE_CATEGORIES =
+
+USE_MSGCTXT = no
+
+MSGMERGE_OPTIONS =
+
+MSGINIT_OPTIONS =
+
+PO_DEPENDS_ON_POT = yes
+
+DIST_DEPENDS_ON_UPDATE_PO = yes
diff --git a/po/POTFILES.in b/po/POTFILES.in
new file mode 100644
index 0000000..5472064
--- /dev/null
+++ b/po/POTFILES.in
@@ -0,0 +1,2 @@
+src/datetime.c
+src/main.c
diff --git a/scripts/update-m4.sh b/scripts/update-m4.sh
new file mode 100755
index 0000000..e0d500d
--- /dev/null
+++ b/scripts/update-m4.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+#
+# Script to update M4 files from Gnulib, GNU Autoconf Archive, and GNU Coreutils
+#
+# Copyright (C) 2023 P. J. McDermott
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved. This file is offered as-is,
+# without any warranty.
+
+set -eu
+
+SV_CGIT='https://git.savannah.gnu.org/cgit'
+
+for f in m4/gl_*.m4; do
+ wget -O "${f}" "${SV_CGIT}/gnulib.git/plain/m4/${f#m4/gl_}"
+done
+
+for f in m4/ax_*.m4; do
+ wget -O "${f}" "${SV_CGIT}/autoconf-archive.git/plain/${f}"
+done
+
+wget -O - "${SV_CGIT}/coreutils.git/plain/configure.ac" | sed -n '
+ /^# Copyright /,/^dnl Written /{
+ p;
+ s/^dnl Written .*$//p;
+ };
+ /^# gl_GCC_VERSION_IFELSE/,/^$/p;
+ ' | sed '$d' 1>'m4/gcc_version.m4'
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");
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 0000000..7e563b8
--- /dev/null
+++ b/tests/.gitignore
@@ -0,0 +1,2 @@
+*.log
+*.trs
diff --git a/tests/aux/tap-functions.sh b/tests/aux/tap-functions.sh
new file mode 100644
index 0000000..e2414b7
--- /dev/null
+++ b/tests/aux/tap-functions.sh
@@ -0,0 +1,229 @@
+# -*- shell-script -*-
+#
+# Copyright (C) 2011-2022 Free Software Foundation, Inc.
+#
+# This program 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 2, or (at your option)
+# any later version.
+#
+# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
+
+# Helper functions used by TAP-producing tests of the Automake testsuite.
+
+#
+# IMPORTANT: All the functions defined in this file can *not* be used
+# from within a subshell, unless explicitly noted otherwise.
+#
+
+# The counts of the TAP test results seen so far: total count and
+# per-result counts.
+tap_count_=0
+tap_pass_count_=0
+tap_skip_count_=0
+tap_fail_count_=0
+tap_xfail_count_=0
+tap_xpass_count_=0
+
+# not COMMAND [ARGS...]
+# ---------------------
+# Run the given command and invert its exit status.
+not () { ! "$@"; }
+
+# plan_ [unknown|later|lazy|now|NUMBER-OF-PLANNED-TESTS]
+# ------------------------------------------------------
+# Print a TAP plan for the given number of tests. This must be called
+# before reporting any test result. If called with the special argument
+# "unknown" or "later", it will do nothing, expecting the calling script
+# to declare the plan later. If called with the special argument "lazy"
+# or "now", it will print a TAP plan that accounts for the number of tests
+# seen so far.
+plan_ ()
+{
+ if test $# -eq 0; then
+ bailout_ "plan_: missing argument"
+ elif test $# -ge 2; then
+ bailout_ "plan_: too many arguments"
+ elif test x"$planned_" != x"none" && test x"$planned_" != x"later"; then
+ bailout_ "plan_: called to many times"
+ elif test x"$1" = x"unknown" || test x"$1" = x"later"; then
+ # This means we want to get back later to declaring the TAP plan.
+ planned_=later
+ return 0
+ elif test x"$1" = x"lazy" || test x"$1" = x"now"; then
+ planned_=$tap_count_ # Number of test results seen so far.
+ elif test $1 -ge 0; then
+ planned_=$1
+ else
+ bailout_ "plan_: invalid argument '$1'"
+ fi
+ echo "1..$planned_"
+}
+planned_=none
+
+# diag_ [EXPLANATION]
+# ------------------
+# Report the given text as TAP diagnostic. Assumes the string denoting
+# TAP diagnostic lines is stored in the '$diag_string_' variable; this is
+# done to allow better interplay with TAP drivers that allow such a string
+# to be configured.
+diag_ ()
+{
+ test $# -eq 0 || echo "$diag_string_ $*"
+}
+
+# Used by the 'diag_' function above. User-overridable.
+diag_string_="#"
+
+# warn_ [EXPLANATION]
+# ------------------
+# Give a warning (using TAP diagnostic).
+warn_ ()
+{
+ case $# in
+ 0) diag_ "WARNING: (unknown warning)";;
+ *) diag_ "WARNING: $*";;
+ esac
+}
+
+# result_ RESULT [-D DIRECTIVE] [-r REASON] [--] [DESCRIPTION...]
+# ---------------------------------------------------------------
+# Report a test case with the given RESULT (valid values are "ok" and
+# "not ok") and the given DESCRIPTION (if any). If DIRECTIVE is given
+# and non-empty (valid values being "TODO" and "SKIP"), it will be
+# reported too, with the REASON (if given) appended.
+result_ ()
+{
+ test $# -gt 0 || bailout_ "result_: missing argument"
+ tap_result_=$1; shift
+ case $tap_result_ in
+ "ok"|"not ok") ;;
+ *) bailout_ "result_: invalid result '$tap_result'" ;;
+ esac
+ tap_directive_= tap_reason_=
+ while test $# -gt 0; do
+ case $1 in
+ -D|--directive) tap_directive_=$2; shift;;
+ -r|--reason) tap_reason_=$2; shift;;
+ --) shift; break;;
+ -*) bailout_ "result_: invalid option '$1'";;
+ *) break;;
+ esac
+ shift
+ done
+ case $tap_directive_ in
+ ""|TODO|SKIP) ;;
+ *) bailout_ "result_: invalid directive '$directive_'" ;;
+ esac
+ tap_count_=$(($tap_count_ + 1))
+ case $tap_result_,$tap_directive_ in
+ ok,) # Passed.
+ tap_pass_count_=$(($tap_pass_count_ + 1)) ;;
+ not\ ok,TODO) # Expected failure.
+ tap_xfail_count_=$(($tap_xfail_count_ + 1)) ;;
+ not\ ok,*) # Failed.
+ tap_fail_count_=$(($tap_fail_count_ + 1)) ;;
+ ok,TODO) # Unexpected pass.
+ tap_xpass_count_=$(($tap_xpass_count_ + 1)) ;;
+ ok,SKIP) # Skipped.
+ tap_skip_count_=$(($tap_skip_count_ + 1)) ;;
+ *) # Can't happen.
+ bailout_ "internal error in 'result_'" ;;
+ esac
+ tap_text_="$tap_result_ $tap_count_"
+ if test x"$*" != x; then
+ tap_text_="$tap_text_ - $*"
+ fi
+ if test x"$tap_directive_" != x; then
+ tap_text_="$tap_text_ # $tap_directive_"${tap_reason_:+" $tap_reason_"}
+ fi
+ printf '%s\n' "$tap_text_"
+}
+
+# Shorthands for common usages of 'result_'.
+ok_ () { result_ 'ok' ${1+"$@"}; }
+not_ok_ () { result_ 'not ok' ${1+"$@"}; }
+skip_ () { result_ 'ok' -D SKIP ${1+"$@"}; }
+
+# skip_row_ COUNT [-r REASON] [--] [DESCRIPTION...]
+# -------------------------------------------------
+# Report a COUNT of skipped test, with the given reason and descriptions
+# (if any). Useful to avoid cascade failures in case a fair number of
+# tests depend on an earlier one that failed.
+skip_row_ ()
+{
+ skip_count_=$1; shift
+ for i_ in $(seq_ $skip_count_); do skip_ ${1+"$@"}; done
+}
+
+# skip_all_ [REASON ...]
+# ----------------------
+# Skip all the tests in a test script. Must be used before calling 'plan_'
+# or reporting any test result. Can't be used from within a subshell.
+skip_all_ ()
+{
+ echo "1..0 # SKIP" ${1+"$@"}
+ planned_=0
+ exit 0
+}
+
+# bailout_ [REASON ...]
+# ---------------------
+# Stop the execution of the current test suite right now, due to an
+# unrecoverable error. Can be called at any point, but cannot be used
+# from within a subshell.
+bailout_ ()
+{
+ echo 'Bail out!' ${1+"$@"}
+ exit 99
+}
+
+# fatal_ [REASON ...]
+# -------------------
+# Same as 'bailout_'; for compatibility with 'plain-functions.sh'.
+fatal_ ()
+{
+ bailout_ ${1+"$@"}
+}
+
+# framework_failure_ [REASON ...]
+# -------------------------------
+# Stop the execution of the current test suite right now, due to an
+# unrecoverable error in the set-up of the test case. Can be called
+# at any point, but cannot be used from within a subshell.
+framework_failure_ ()
+{
+ bailout_ "set-up failure"${1+": $*"}
+}
+
+# command_ok_ TEST-DESCRIPTION [OPTIONS..] [--] CMD [ARGS...]
+# -----------------------------------------------------------
+# Helper subroutine for when a TAP result must be determined by the
+# outcome of a command.
+command_ok_ ()
+{
+ tap_directive_= tap_reason_=
+ test $# -gt 0 || bailout_ "command_ok_: missing argument"
+ tap_description_=$1; shift
+ while test $# -gt 0; do
+ case $1 in
+ -D|--directive) tap_directive_=$2; shift;;
+ -r|--reason) tap_reason_=$2; shift;;
+ --) shift; break;;
+ -*) bailout_ "command_ok_: invalid option '$1'";;
+ *) break;;
+ esac
+ shift
+ done
+ tap_result_="ok"; "$@" || tap_result_="not ok"
+ result_ "$tap_result_" -D "$tap_directive_" -r "$tap_reason_" \
+ -- "$tap_description_"
+}
+
+:
diff --git a/tests/disabled.sh b/tests/disabled.sh
new file mode 100755
index 0000000..53caf82
--- /dev/null
+++ b/tests/disabled.sh
@@ -0,0 +1,2 @@
+printf 'Test suite support disabled\n' 1>&2
+exit 1
diff --git a/tests/formats.exp b/tests/formats.exp
new file mode 100644
index 0000000..20b49c9
--- /dev/null
+++ b/tests/formats.exp
@@ -0,0 +1,204 @@
+Time formats:
+ * 03:04:05 AM
+ * 03:04:05
+ * 03:04 AM
+ * 03:04
+ * 03.04.05 AM
+ * 03.04.05
+ * 03.04 AM
+ * 03.04
+ * 030405 AM
+ * 030405
+ * 0304 AM
+ * 0304
+Date formats:
+ * Fri
+ * Fri.
+ * 1970-01-02
+ * 1970/01/02
+ * 1970.01.02
+ * 01-02-1970
+ * 01-02
+ * 01/02/1970
+ * 01/02
+ * 01.02.1970
+ * 01.02
+ * 2 Jan 1970
+ * 2 Jan. 1970
+ * 2 Jan
+ * 2 Jan.
+ * 02-Jan-1970
+ * 02-Jan
+ * 02/Jan/1970
+ * 02/Jan
+ * Fri 2 Jan 1970
+ * Fri 2 Jan. 1970
+ * Fri 2 Jan
+ * Fri 2 Jan.
+ * Fri 02-Jan-1970
+ * Fri 02-Jan
+ * Fri 02/Jan/1970
+ * Fri 02/Jan
+ * Fri. 2 Jan 1970
+ * Fri. 2 Jan. 1970
+ * Fri. 2 Jan
+ * Fri. 2 Jan.
+ * Fri. 02-Jan-1970
+ * Fri. 02-Jan
+ * Fri. 02/Jan/1970
+ * Fri. 02/Jan
+ * Fri, 2 Jan 1970
+ * Fri, 2 Jan. 1970
+ * Fri, 2 Jan
+ * Fri, 2 Jan.
+ * Fri, 02-Jan-1970
+ * Fri, 02-Jan
+ * Fri, 02/Jan/1970
+ * Fri, 02/Jan
+ * Jan 2, 1970
+ * Jan. 2, 1970
+ * Jan 2
+ * Jan. 2
+ * Jan-02-1970
+ * Jan-02
+ * Jan/02/1970
+ * Jan/02
+ * Fri Jan 2, 1970
+ * Fri Jan. 2, 1970
+ * Fri Jan 2
+ * Fri Jan. 2
+ * Fri Jan-02-1970
+ * Fri Jan-02
+ * Fri Jan/02/1970
+ * Fri Jan/02
+ * Fri. Jan 2, 1970
+ * Fri. Jan. 2, 1970
+ * Fri. Jan 2
+ * Fri. Jan. 2
+ * Fri. Jan-02-1970
+ * Fri. Jan-02
+ * Fri. Jan/02/1970
+ * Fri. Jan/02
+ * Fri, Jan 2, 1970
+ * Fri, Jan. 2, 1970
+ * Fri, Jan 2
+ * Fri, Jan. 2
+ * Fri, Jan-02-1970
+ * Fri, Jan-02
+ * Fri, Jan/02/1970
+ * Fri, Jan/02
+ * Friday
+ * Friday 2 Jan 1970
+ * Friday 2 Jan. 1970
+ * Friday 2 Jan
+ * Friday 2 Jan.
+ * Friday 02-Jan-1970
+ * Friday 02-Jan
+ * Friday 02/Jan/1970
+ * Friday 02/Jan
+ * Friday, 2 Jan 1970
+ * Friday, 2 Jan. 1970
+ * Friday, 2 Jan
+ * Friday, 2 Jan.
+ * Friday, 02-Jan-1970
+ * Friday, 02-Jan
+ * Friday, 02/Jan/1970
+ * Friday, 02/Jan
+ * Friday Jan 2, 1970
+ * Friday Jan. 2, 1970
+ * Friday Jan 2
+ * Friday Jan. 2
+ * Friday Jan-02-1970
+ * Friday Jan-02
+ * Friday Jan/02/1970
+ * Friday Jan/02
+ * Friday, Jan 2, 1970
+ * Friday, Jan. 2, 1970
+ * Friday, Jan 2
+ * Friday, Jan. 2
+ * Friday, Jan-02-1970
+ * Friday, Jan-02
+ * Friday, Jan/02/1970
+ * Friday, Jan/02
+ * 2 January 1970
+ * 2 January
+ * 02-January-1970
+ * 02-January
+ * 02/January/1970
+ * 02/January
+ * Fri 2 January 1970
+ * Fri 2 January
+ * Fri 02-January-1970
+ * Fri 02-January
+ * Fri 02/January/1970
+ * Fri 02/January
+ * Fri. 2 January 1970
+ * Fri. 2 January
+ * Fri. 02-January-1970
+ * Fri. 02-January
+ * Fri. 02/January/1970
+ * Fri. 02/January
+ * Fri, 2 January 1970
+ * Fri, 2 January
+ * Fri, 02-January-1970
+ * Fri, 02-January
+ * Fri, 02/January/1970
+ * Fri, 02/January
+ * January 2, 1970
+ * January 2
+ * January-02-1970
+ * January-02
+ * January/02/1970
+ * January/02
+ * Fri January 2, 1970
+ * Fri January 2
+ * Fri January-02-1970
+ * Fri January-02
+ * Fri January/02/1970
+ * Fri January/02
+ * Fri. January 2, 1970
+ * Fri. January 2
+ * Fri. January-02-1970
+ * Fri. January-02
+ * Fri. January/02/1970
+ * Fri. January/02
+ * Fri, January 2, 1970
+ * Fri, January 2
+ * Fri, January-02-1970
+ * Fri, January-02
+ * Fri, January/02/1970
+ * Fri, January/02
+ * Friday 2 January 1970
+ * Friday 2 January
+ * Friday 02-January-1970
+ * Friday 02-January
+ * Friday 02/January/1970
+ * Friday 02/January
+ * Friday, 2 January 1970
+ * Friday, 2 January
+ * Friday, 02-January-1970
+ * Friday, 02-January
+ * Friday, 02/January/1970
+ * Friday, 02/January
+ * Friday January 2, 1970
+ * Friday January 2
+ * Friday January-02-1970
+ * Friday January-02
+ * Friday January/02/1970
+ * Friday January/02
+ * Friday, January 2, 1970
+ * Friday, January 2
+ * Friday, January-02-1970
+ * Friday, January-02
+ * Friday, January/02/1970
+ * Friday, January/02
+Additional formats:
+ * 1970-01-02T03:04:05
+ * 1970-01-02T03:04
+ * 19700102T030405
+ * 19700102T0304
+ * 19700102 030405 AM
+ * 19700102 030405
+ * 19700102 0304 AM
+ * 19700102 0304
+ * Fri Jan 02 03:04:05 1970
diff --git a/tests/formats.sh b/tests/formats.sh
new file mode 100755
index 0000000..576b96a
--- /dev/null
+++ b/tests/formats.sh
@@ -0,0 +1,43 @@
+# Tests `@ -F` output
+#
+# Copyright (C) 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/>.
+
+set -eu
+
+. "${TOP_SRCDIR}/tests/aux/tap-functions.sh"
+
+plan_ 1
+
+exp="$(cat "${0%.sh}.exp")"
+got="$("${TOP_BUILDDIR}/atsign" -d '1970-01-02 03:04:05' -F)"
+
+if [ x"${got}" = x"${exp}" ]; then
+ ok_ -- 'formats list'
+ exit 0
+fi
+not_ok_ -- 'formats list'
+
+IFS='
+'
+
+for line in $(printf '%s\n' "${got}" | diff -u "${0%.sh}.exp" '-' | sed '1,2d')
+do
+ diag_ "${line}"
+done
+
+exit 0
diff --git a/tests/local.mk b/tests/local.mk
new file mode 100644
index 0000000..84eb79e
--- /dev/null
+++ b/tests/local.mk
@@ -0,0 +1,21 @@
+tests = \
+ %reldir%/formats.sh \
+ %reldir%/parse.sh
+if ENABLE_TESTS
+TESTS = $(tests)
+else
+TESTS = %reldir%/disabled.sh
+endif
+
+TEST_EXTENSIONS = .sh
+SH_LOG_DRIVER = \
+ AM_TAP_AWK='$(AWK)' \
+ TOP_SRCDIR="$(abs_top_srcdir)" \
+ TOP_BUILDDIR="$(abs_top_builddir)" \
+ $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh
+EXTRA_DIST += \
+ $(tests) \
+ %reldir%/disabled.sh \
+ %reldir%/formats.exp \
+ $(top_srcdir)/build-aux/tap-driver.sh \
+ %reldir%/aux/tap-functions.sh
diff --git a/tests/parse.sh b/tests/parse.sh
new file mode 100755
index 0000000..eadd2cf
--- /dev/null
+++ b/tests/parse.sh
@@ -0,0 +1,77 @@
+# Tests @'s date/time parsing
+#
+# Copyright (C) 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/>.
+
+set -eu
+
+. "${TOP_SRCDIR}/tests/aux/tap-functions.sh"
+
+now='1969-12-31 23:59:30'
+tgt='1970-01-01 00:00:00'
+
+fmts="$(TZ=UTC0 "${TOP_BUILDDIR}/atsign" -d "${tgt}" -F)"
+time_fmts="$(printf '%s\n' "${fmts}" | \
+ sed -n '/^Time formats:$/,/^Date formats:$/s/^ \* //p')"
+date_fmts="$(printf '%s\n' "${fmts}" | \
+ sed -n '/^Date formats:$/,/^Additional formats:$/s/^ \* //p')"
+misc_fmts="$(printf '%s\n' "${fmts}" | \
+ sed -n '/^Additional formats:$/,$s/^ \* //p')"
+
+plan_ $((\
+ $(printf '%s\n' "${time_fmts}" | wc -l) + \
+ $(printf '%s\n' "${time_fmts}" | wc -l) * \
+ $(printf '%s\n' "${date_fmts}" | wc -l) * \
+ 2 + \
+ $(printf '%s\n' "${misc_fmts}" | wc -l) \
+ ))
+
+IFS='
+'
+
+for fmt in $(printf '%s\n' "${fmts}"); do
+ diag_ "${fmt}"
+done
+
+test_fmt()
+{
+ now="${1}"
+ tgt="${2}"
+ fmt="${3}"
+ shift 3
+
+ got="$(TZ=UTC0 "${TOP_BUILDDIR}/atsign" -d "${now}" "${fmt}" 2>&1)" || :
+ if [ x"${got}" = x"${tgt}" ]; then
+ ok_ -- "${fmt}"
+ else
+ not_ok_ -- "${fmt}"
+ diag_ " Failed test '${fmt}'"
+ diag_ " got: '${got}'"
+ diag_ " expected: '${tgt}'"
+ fi
+}
+
+for time_fmt in ${time_fmts}; do
+ test_fmt "${now}" "${tgt}" "${time_fmt}"
+ for date_fmt in ${date_fmts}; do
+ test_fmt "${now}" "${tgt}" "${date_fmt} ${time_fmt}"
+ test_fmt "${now}" "${tgt}" "${time_fmt} ${date_fmt}"
+ done
+done
+for misc_fmt in ${misc_fmts}; do
+ test_fmt "${now}" "${tgt}" "${misc_fmt}"
+done