/* * parse time string - user friendly date and time parser * Copyright © 2012 Jani Nikula * * 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 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/>. * * Author: Jani Nikula <jani@nikula.org> */ #include <assert.h> #include <ctype.h> #include <getopt.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "parse-time-string.h" #define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0])) static const char *parse_time_error_strings[] = { [PARSE_TIME_OK] = "OK", [PARSE_TIME_ERR] = "ERR", [PARSE_TIME_ERR_LIB] = "LIB", [PARSE_TIME_ERR_ALREADYSET] = "ALREADYSET", [PARSE_TIME_ERR_FORMAT] = "FORMAT", [PARSE_TIME_ERR_DATEFORMAT] = "DATEFORMAT", [PARSE_TIME_ERR_TIMEFORMAT] = "TIMEFORMAT", [PARSE_TIME_ERR_INVALIDDATE] = "INVALIDDATE", [PARSE_TIME_ERR_INVALIDTIME] = "INVALIDTIME", [PARSE_TIME_ERR_KEYWORD] = "KEYWORD", }; static const char * parse_time_strerror (unsigned int errnum) { if (errnum < ARRAY_SIZE (parse_time_error_strings)) return parse_time_error_strings[errnum]; else return NULL; } /* * concat argv[start]...argv[end - 1], separating them by a single * space, to a malloced string */ static char * concat_args (int start, int end, char *argv[]) { int i; size_t len = 1; char *p; for (i = start; i < end; i++) len += strlen (argv[i]) + 1; p = malloc (len); if (! p) return NULL; *p = 0; for (i = start; i < end; i++) { if (i != start) strcat (p, " "); strcat (p, argv[i]); } return p; } #define DEFAULT_FORMAT "%a %b %d %T %z %Y" static void usage (const char *name) { printf ("Usage: %s [options ...] [<date/time>]\n\n", name); printf ( "Parse <date/time> and display it in given format. If <date/time> is\n" "not given, parse each line in stdin according to:\n\n" " <date/time> [(==>|==_>|==^>|==^^>)<ignored>] [#<comment>]\n\n" "and produce output:\n\n" " <date/time> (==>|==_>|==^>|==^^>) <time in --format=FMT> [#<comment>]\n\n" "preserving whitespace and comment in input. The operators ==>, ==_>,\n" "==^>, and ==^^> define rounding as no rounding, round down, round up\n" "inclusive, and round up, respectively.\n\n" " -f, --format=FMT output format, FMT according to strftime(3)\n" " (default: \"%s\")\n" " -r, --ref=N use N seconds since epoch as reference time\n" " (default: now)\n" " -u, --^ round result up inclusive (default: no rounding)\n" " -U, --^^ round result up (default: no rounding)\n" " -d, --_ round result down (default: no rounding)\n" " -h, --help print this help\n", DEFAULT_FORMAT); } struct { const char *operator; int round; } operators[] = { { "==>", PARSE_TIME_NO_ROUND }, { "==_>", PARSE_TIME_ROUND_DOWN }, { "==^>", PARSE_TIME_ROUND_UP_INCLUSIVE }, { "==^^>", PARSE_TIME_ROUND_UP }, }; static const char * find_operator_in_string (char *str, char **ptr, int *round) { const char *oper = NULL; unsigned int i; for (i = 0; i < ARRAY_SIZE (operators); i++) { char *p = strstr (str, operators[i].operator); if (p) { if (round) *round = operators[i].round; if (ptr) *ptr = p; oper = operators[i].operator; break; } } return oper; } static const char * get_operator (int round) { const char *oper = NULL; unsigned int i; for (i = 0; i < ARRAY_SIZE (operators); i++) { if (round == operators[i].round) { oper = operators[i].operator; break; } } return oper; } static int parse_stdin (FILE *infile, time_t *ref, int round, const char *format) { char *input = NULL; char result[1024]; size_t inputsize; ssize_t len; struct tm tm; time_t t; int r; while ((len = getline (&input, &inputsize, infile)) != -1) { const char *oper; char *trail, *tmp; /* trail is trailing whitespace and (optional) comment */ trail = strchr (input, '#'); if (! trail) trail = input + len; while (trail > input && isspace ((unsigned char) *(trail - 1))) trail--; if (trail == input) { printf ("%s", input); continue; } tmp = strdup (trail); if (! tmp) { fprintf (stderr, "strdup() failed\n"); continue; } *trail = '\0'; trail = tmp; /* operator */ oper = find_operator_in_string (input, &tmp, &round); if (oper) { *tmp = '\0'; } else { oper = get_operator (round); assert (oper); } r = parse_time_string (input, &t, ref, round); if (! r) { if (! localtime_r (&t, &tm)) { fprintf (stderr, "localtime_r() failed\n"); free (trail); continue; } strftime (result, sizeof (result), format, &tm); } else { const char *errstr = parse_time_strerror (r); if (errstr) snprintf (result, sizeof (result), "ERROR: %s", errstr); else snprintf (result, sizeof (result), "ERROR: %d", r); } printf ("%s%s %s%s", input, oper, result, trail); free (trail); } free (input); return 0; } int main (int argc, char *argv[]) { int r; struct tm tm; time_t result; time_t now; time_t *nowp = NULL; char *argstr; int round = PARSE_TIME_NO_ROUND; char buf[1024]; const char *format = DEFAULT_FORMAT; struct option options[] = { { "help", no_argument, NULL, 'h' }, { "^", no_argument, NULL, 'u' }, { "^^", no_argument, NULL, 'U' }, { "_", no_argument, NULL, 'd' }, { "format", required_argument, NULL, 'f' }, { "ref", required_argument, NULL, 'r' }, { NULL, 0, NULL, 0 }, }; for (;;) { int c; c = getopt_long (argc, argv, "huUdf:r:", options, NULL); if (c == -1) break; switch (c) { case 'f': /* output format */ format = optarg; break; case 'u': round = PARSE_TIME_ROUND_UP_INCLUSIVE; break; case 'U': round = PARSE_TIME_ROUND_UP; break; case 'd': round = PARSE_TIME_ROUND_DOWN; break; case 'r': /* specify now in seconds since epoch */ now = (time_t) strtol (optarg, NULL, 10); if (now >= (time_t) 0) nowp = &now; break; case 'h': case '?': default: usage (argv[0]); return 1; } } if (optind == argc) return parse_stdin (stdin, nowp, round, format); argstr = concat_args (optind, argc, argv); if (! argstr) return 1; r = parse_time_string (argstr, &result, nowp, round); free (argstr); if (r) { const char *errstr = parse_time_strerror (r); if (errstr) fprintf (stderr, "ERROR: %s\n", errstr); else fprintf (stderr, "ERROR: %d\n", r); return r; } if (! localtime_r (&result, &tm)) return 1; strftime (buf, sizeof (buf), format, &tm); printf ("%s\n", buf); return 0; }