]> git.notmuchmail.org Git - notmuch/blob - test/parse-time.c
version: bump to 0.15
[notmuch] / test / parse-time.c
1 /*
2  * parse time string - user friendly date and time parser
3  * Copyright © 2012 Jani Nikula
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Author: Jani Nikula <jani@nikula.org>
19  */
20
21 #include <assert.h>
22 #include <ctype.h>
23 #include <getopt.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "parse-time-string.h"
29
30 #define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0]))
31
32 static const char *parse_time_error_strings[] = {
33     [PARSE_TIME_OK]                     = "OK",
34     [PARSE_TIME_ERR]                    = "ERR",
35     [PARSE_TIME_ERR_LIB]                = "LIB",
36     [PARSE_TIME_ERR_ALREADYSET]         = "ALREADYSET",
37     [PARSE_TIME_ERR_FORMAT]             = "FORMAT",
38     [PARSE_TIME_ERR_DATEFORMAT]         = "DATEFORMAT",
39     [PARSE_TIME_ERR_TIMEFORMAT]         = "TIMEFORMAT",
40     [PARSE_TIME_ERR_INVALIDDATE]        = "INVALIDDATE",
41     [PARSE_TIME_ERR_INVALIDTIME]        = "INVALIDTIME",
42     [PARSE_TIME_ERR_KEYWORD]            = "KEYWORD",
43 };
44
45 static const char *
46 parse_time_strerror (unsigned int errnum)
47 {
48     if (errnum < ARRAY_SIZE (parse_time_error_strings))
49         return parse_time_error_strings[errnum];
50     else
51         return NULL;
52 }
53
54 /*
55  * concat argv[start]...argv[end - 1], separating them by a single
56  * space, to a malloced string
57  */
58 static char *
59 concat_args (int start, int end, char *argv[])
60 {
61     int i;
62     size_t len = 1;
63     char *p;
64
65     for (i = start; i < end; i++)
66         len += strlen (argv[i]) + 1;
67
68     p = malloc (len);
69     if (!p)
70         return NULL;
71
72     *p = 0;
73
74     for (i = start; i < end; i++) {
75         if (i != start)
76             strcat (p, " ");
77         strcat (p, argv[i]);
78     }
79
80     return p;
81 }
82
83 #define DEFAULT_FORMAT "%a %b %d %T %z %Y"
84
85 static void
86 usage (const char *name)
87 {
88     printf ("Usage: %s [options ...] [<date/time>]\n\n", name);
89     printf (
90         "Parse <date/time> and display it in given format. If <date/time> is\n"
91         "not given, parse each line in stdin according to:\n\n"
92         "  <date/time> [(==>|==_>|==^>|==^^>)<ignored>] [#<comment>]\n\n"
93         "and produce output:\n\n"
94         "  <date/time> (==>|==_>|==^>|==^^>) <time in --format=FMT> [#<comment>]\n\n"
95         "preserving whitespace and comment in input. The operators ==>, ==_>,\n"
96         "==^>, and ==^^> define rounding as no rounding, round down, round up\n"
97         "inclusive, and round up, respectively.\n\n"
98
99         "  -f, --format=FMT output format, FMT according to strftime(3)\n"
100         "                   (default: \"%s\")\n"
101         "  -r, --ref=N      use N seconds since epoch as reference time\n"
102         "                   (default: now)\n"
103         "  -u, --^          round result up inclusive (default: no rounding)\n"
104         "  -U, --^^         round result up (default: no rounding)\n"
105         "  -d, --_          round result down (default: no rounding)\n"
106         "  -h, --help       print this help\n",
107         DEFAULT_FORMAT);
108 }
109
110 struct {
111     const char *operator;
112     int round;
113 } operators[] = {
114     { "==>",    PARSE_TIME_NO_ROUND },
115     { "==_>",   PARSE_TIME_ROUND_DOWN },
116     { "==^>",   PARSE_TIME_ROUND_UP_INCLUSIVE },
117     { "==^^>",  PARSE_TIME_ROUND_UP },
118 };
119
120 static const char *
121 find_operator_in_string (char *str, char **ptr, int *round)
122 {
123     const char *oper = NULL;
124     unsigned int i;
125
126     for (i = 0; i < ARRAY_SIZE (operators); i++) {
127         char *p = strstr (str, operators[i].operator);
128         if (p) {
129             if (round)
130                 *round = operators[i].round;
131             if (ptr)
132                 *ptr = p;
133
134             oper = operators[i].operator;
135             break;
136         }
137     }
138
139     return oper;
140 }
141
142 static const char *
143 get_operator (int round)
144 {
145     const char *oper = NULL;
146     unsigned int i;
147
148     for (i = 0; i < ARRAY_SIZE(operators); i++) {
149         if (round == operators[i].round) {
150             oper = operators[i].operator;
151             break;
152         }
153     }
154
155     return oper;
156 }
157
158 static int
159 parse_stdin (FILE *infile, time_t *ref, int round, const char *format)
160 {
161     char *input = NULL;
162     char result[1024];
163     size_t inputsize;
164     ssize_t len;
165     struct tm tm;
166     time_t t;
167     int r;
168
169     while ((len = getline (&input, &inputsize, infile)) != -1) {
170         const char *oper;
171         char *trail, *tmp;
172
173         /* trail is trailing whitespace and (optional) comment */
174         trail = strchr (input, '#');
175         if (!trail)
176             trail = input + len;
177
178         while (trail > input && isspace ((unsigned char) *(trail-1)))
179             trail--;
180
181         if (trail == input) {
182             printf ("%s", input);
183             continue;
184         }
185
186         tmp = strdup (trail);
187         if (!tmp) {
188             fprintf (stderr, "strdup() failed\n");
189             continue;
190         }
191         *trail = '\0';
192         trail = tmp;
193
194         /* operator */
195         oper = find_operator_in_string (input, &tmp, &round);
196         if (oper) {
197             *tmp = '\0';
198         } else {
199             oper = get_operator (round);
200             assert (oper);
201         }
202
203         r = parse_time_string (input, &t, ref, round);
204         if (!r) {
205             if (!localtime_r (&t, &tm)) {
206                 fprintf (stderr, "localtime_r() failed\n");
207                 free (trail);
208                 continue;
209             }
210
211             strftime (result, sizeof (result), format, &tm);
212         } else {
213             const char *errstr = parse_time_strerror (r);
214             if (errstr)
215                 snprintf (result, sizeof (result), "ERROR: %s", errstr);
216             else
217                 snprintf (result, sizeof (result), "ERROR: %d", r);
218         }
219
220         printf ("%s%s %s%s", input, oper, result, trail);
221         free (trail);
222     }
223
224     free (input);
225
226     return 0;
227 }
228
229 int
230 main (int argc, char *argv[])
231 {
232     int r;
233     struct tm tm;
234     time_t result;
235     time_t now;
236     time_t *nowp = NULL;
237     char *argstr;
238     int round = PARSE_TIME_NO_ROUND;
239     char buf[1024];
240     const char *format = DEFAULT_FORMAT;
241     struct option options[] = {
242         { "help",       no_argument,            NULL,   'h' },
243         { "^",          no_argument,            NULL,   'u' },
244         { "^^",         no_argument,            NULL,   'U' },
245         { "_",          no_argument,            NULL,   'd' },
246         { "format",     required_argument,      NULL,   'f' },
247         { "ref",        required_argument,      NULL,   'r' },
248         { NULL, 0, NULL, 0 },
249     };
250
251     for (;;) {
252         int c;
253
254         c = getopt_long (argc, argv, "huUdf:r:", options, NULL);
255         if (c == -1)
256             break;
257
258         switch (c) {
259         case 'f':
260             /* output format */
261             format = optarg;
262             break;
263         case 'u':
264             round = PARSE_TIME_ROUND_UP_INCLUSIVE;
265             break;
266         case 'U':
267             round = PARSE_TIME_ROUND_UP;
268             break;
269         case 'd':
270             round = PARSE_TIME_ROUND_DOWN;
271             break;
272         case 'r':
273             /* specify now in seconds since epoch */
274             now = (time_t) strtol (optarg, NULL, 10);
275             if (now >= (time_t) 0)
276                 nowp = &now;
277             break;
278         case 'h':
279         case '?':
280         default:
281             usage (argv[0]);
282             return 1;
283         }
284     }
285
286     if (optind == argc)
287         return parse_stdin (stdin, nowp, round, format);
288
289     argstr = concat_args (optind, argc, argv);
290     if (!argstr)
291         return 1;
292
293     r = parse_time_string (argstr, &result, nowp, round);
294
295     free (argstr);
296
297     if (r) {
298         const char *errstr = parse_time_strerror (r);
299         if (errstr)
300             fprintf (stderr, "ERROR: %s\n", errstr);
301         else
302             fprintf (stderr, "ERROR: %d\n", r);
303
304         return r;
305     }
306
307     if (!localtime_r (&result, &tm))
308         return 1;
309
310     strftime (buf, sizeof (buf), format, &tm);
311     printf ("%s\n", buf);
312
313     return 0;
314 }