notmuch/test/parse-time.c
Daniel Kahn Gillmor 6a833a6e83 Use https instead of http where possible
Many of the external links found in the notmuch source can be resolved
using https instead of http.  This changeset addresses as many as i
could find, without touching the e-mail corpus or expected outputs
found in tests.
2016-06-05 08:32:17 -03:00

314 lines
7.1 KiB
C

/*
* 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;
}