Re: [PATCH] test/README: have matching test script file names
[notmuch-archives.git] / ab / 8067f73304a6e5629614264071a2627902b9ec
1 Return-Path: <jani@nikula.org>\r
2 X-Original-To: notmuch@notmuchmail.org\r
3 Delivered-To: notmuch@notmuchmail.org\r
4 Received: from localhost (localhost [127.0.0.1])\r
5         by olra.theworths.org (Postfix) with ESMTP id E2F8A431FAF\r
6         for <notmuch@notmuchmail.org>; Sat,  4 Aug 2012 00:42:11 -0700 (PDT)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 X-Amavis-Alert: BAD HEADER SECTION, Duplicate header field: "References"\r
9 X-Spam-Flag: NO\r
10 X-Spam-Score: -0.7\r
11 X-Spam-Level: \r
12 X-Spam-Status: No, score=-0.7 tagged_above=-999 required=5\r
13         tests=[RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled\r
14 Received: from olra.theworths.org ([127.0.0.1])\r
15         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
16         with ESMTP id a0deIpJTI6mj for <notmuch@notmuchmail.org>;\r
17         Sat,  4 Aug 2012 00:42:04 -0700 (PDT)\r
18 Received: from mail-lb0-f181.google.com (mail-lb0-f181.google.com\r
19         [209.85.217.181]) (using TLSv1 with cipher RC4-SHA (128/128 bits))\r
20         (No client certificate requested)\r
21         by olra.theworths.org (Postfix) with ESMTPS id 26BC4431FC0\r
22         for <notmuch@notmuchmail.org>; Sat,  4 Aug 2012 00:42:03 -0700 (PDT)\r
23 Received: by lbbgk1 with SMTP id gk1so786230lbb.26\r
24         for <notmuch@notmuchmail.org>; Sat, 04 Aug 2012 00:42:02 -0700 (PDT)\r
25 X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;\r
26         d=google.com; s=20120113;\r
27         h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references\r
28         :in-reply-to:references:mime-version:content-type\r
29         :content-transfer-encoding:x-gm-message-state;\r
30         bh=Zn4xSNUB0JOswzcv1FR65oHsh2zpWZtJb9uVNauYiGE=;\r
31         b=VMo6ZZmG7p7P6pS7bjJohE8vKdqUv4VxZDi9ijl7fhLJ3eFxYg9clUDZdC/R26OoaC\r
32         9z/BzSvxmQa+ozpJap5W+OzD8kO1kaJChaKSOI405ksF56cBw00CiZpLveDsTE//IZ6w\r
33         7HaQT3YantVTA8YsS/Mzim7Q1LSk5OE6tO8qmORb5OOiugBD0zZDmVaQtzjSLs0xGdiX\r
34         y2y4HNvbt98sFDJsZpFfj5/mhJzXmtxt1TUtd37dL8armiTDonTYaERjyWBABIgwDThM\r
35         GbqBF37hxG5uWWjq41n3H3OX0yHecuTelIMEH+zRaloiWePr7YHzzGA/MRAZGoMoIEXa\r
36         yBsQ==\r
37 Received: by 10.112.99.71 with SMTP id eo7mr1746263lbb.84.1344066122291;\r
38         Sat, 04 Aug 2012 00:42:02 -0700 (PDT)\r
39 Received: from localhost (dsl-hkibrasgw4-fe51df00-27.dhcp.inet.fi.\r
40         [80.223.81.27])\r
41         by mx.google.com with ESMTPS id j3sm2441009lbh.0.2012.08.04.00.41.59\r
42         (version=SSLv3 cipher=OTHER); Sat, 04 Aug 2012 00:42:01 -0700 (PDT)\r
43 From: Jani Nikula <jani@nikula.org>\r
44 To: notmuch@notmuchmail.org\r
45 Subject: [PATCH v2 2/7] lib: add a date/time parser module\r
46 Date: Sat,  4 Aug 2012 10:41:40 +0300\r
47 Message-Id:\r
48  <133d16fa9b63e4cd91aa2b8816a6ad7285b3bd4c.1344065790.git.jani@nikula.org>\r
49 X-Mailer: git-send-email 1.7.9.5\r
50 In-Reply-To: <cover.1344065790.git.jani@nikula.org>\r
51 References: <cover.1344065790.git.jani@nikula.org>\r
52 In-Reply-To: <cover.1344065790.git.jani@nikula.org>\r
53 References: <cover.1344065790.git.jani@nikula.org>\r
54 MIME-Version: 1.0\r
55 Content-Type: text/plain; charset=UTF-8\r
56 Content-Transfer-Encoding: 8bit\r
57 X-Gm-Message-State:\r
58  ALoCoQnIk8En1tXzPInWNbMODXS4je/6PU+TGqkMMiG673tlGCA53EnoYd7xMZabykXVRSbZERFP\r
59 X-BeenThere: notmuch@notmuchmail.org\r
60 X-Mailman-Version: 2.1.13\r
61 Precedence: list\r
62 List-Id: "Use and development of the notmuch mail system."\r
63         <notmuch.notmuchmail.org>\r
64 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
65         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
66 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
67 List-Post: <mailto:notmuch@notmuchmail.org>\r
68 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
69 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
70         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
71 X-List-Received-Date: Sat, 04 Aug 2012 07:42:12 -0000\r
72 \r
73 Build a date/time parser as part of the notmuch lib, to be used for\r
74 adding date range query support later on.\r
75 \r
76 Signed-off-by: Jani Nikula <jani@nikula.org>\r
77 ---\r
78  lib/Makefile.local      |    1 +\r
79  lib/parse-time-string.c | 1384 +++++++++++++++++++++++++++++++++++++++++++++++\r
80  lib/parse-time-string.h |   95 ++++\r
81  3 files changed, 1480 insertions(+)\r
82  create mode 100644 lib/parse-time-string.c\r
83  create mode 100644 lib/parse-time-string.h\r
84 \r
85 diff --git a/lib/Makefile.local b/lib/Makefile.local\r
86 index 8a9aa28..e29c3a2 100644\r
87 --- a/lib/Makefile.local\r
88 +++ b/lib/Makefile.local\r
89 @@ -53,6 +53,7 @@ libnotmuch_c_srcs =           \\r
90         $(dir)/libsha1.c        \\r
91         $(dir)/message-file.c   \\r
92         $(dir)/messages.c       \\r
93 +       $(dir)/parse-time-string.c      \\r
94         $(dir)/sha1.c           \\r
95         $(dir)/tags.c\r
96  \r
97 diff --git a/lib/parse-time-string.c b/lib/parse-time-string.c\r
98 new file mode 100644\r
99 index 0000000..7c50f3e\r
100 --- /dev/null\r
101 +++ b/lib/parse-time-string.c\r
102 @@ -0,0 +1,1384 @@\r
103 +/*\r
104 + * parse time string - user friendly date and time parser\r
105 + * Copyright © 2012 Jani Nikula\r
106 + *\r
107 + * This program is free software: you can redistribute it and/or modify\r
108 + * it under the terms of the GNU General Public License as published by\r
109 + * the Free Software Foundation, either version 2 of the License, or\r
110 + * (at your option) any later version.\r
111 + *\r
112 + * This program is distributed in the hope that it will be useful,\r
113 + * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
114 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
115 + * GNU General Public License for more details.\r
116 + *\r
117 + * You should have received a copy of the GNU General Public License\r
118 + * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r
119 + *\r
120 + * Author: Jani Nikula <jani@nikula.org>\r
121 + */\r
122 +\r
123 +#include <assert.h>\r
124 +#include <ctype.h>\r
125 +#include <errno.h>\r
126 +#include <limits.h>\r
127 +#include <stdio.h>\r
128 +#include <stdarg.h>\r
129 +#include <stdbool.h>\r
130 +#include <stdlib.h>\r
131 +#include <string.h>\r
132 +#include <strings.h>\r
133 +#include <time.h>\r
134 +#include <sys/time.h>\r
135 +#include <sys/types.h>\r
136 +\r
137 +#include "parse-time-string.h"\r
138 +\r
139 +#define unused(x) x __attribute__ ((unused))\r
140 +\r
141 +/* REVISIT: Redefine these to add i18n support. The keyword table uses\r
142 + * N_() to mark strings to be translated; they are accessed\r
143 + * dynamically using _(). */\r
144 +#define _(s) (s)       /* i18n: define as gettext (s) */\r
145 +#define N_(s) (s)      /* i18n: define as gettext_noop (s) */\r
146 +\r
147 +#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0]))\r
148 +\r
149 +/* field indices in struct state tm, and set fields */\r
150 +enum field {\r
151 +    /* keep SEC...YEAR in this order */\r
152 +    TM_ABS_SEC,                /* seconds */\r
153 +    TM_ABS_MIN,                /* minutes */\r
154 +    TM_ABS_HOUR,       /* hours */\r
155 +    TM_ABS_MDAY,       /* day of the month */\r
156 +    TM_ABS_MON,                /* month */\r
157 +    TM_ABS_YEAR,       /* year */\r
158 +\r
159 +    TM_ABS_WDAY,       /* day of the week. special: may be relative */\r
160 +    TM_ABS_ISDST,      /* daylight saving time */\r
161 +\r
162 +    TM_AMPM,           /* am vs. pm */\r
163 +    TM_TZ,             /* timezone in minutes */\r
164 +\r
165 +    /* keep SEC...YEAR in this order */\r
166 +    TM_REL_SEC,                /* seconds relative to now */\r
167 +    TM_REL_MIN,                /* minutes ... */\r
168 +    TM_REL_HOUR,       /* hours ... */\r
169 +    TM_REL_DAY,                /* days ... */\r
170 +    TM_REL_MON,                /* months ... */\r
171 +    TM_REL_YEAR,       /* years ... */\r
172 +    TM_REL_WEEK,       /* weeks ... */\r
173 +\r
174 +    TM_NONE,           /* not a field */\r
175 +\r
176 +    TM_SIZE = TM_NONE,\r
177 +};\r
178 +\r
179 +enum field_set {\r
180 +    FIELD_UNSET,\r
181 +    FIELD_SET,\r
182 +    FIELD_NOW,\r
183 +};\r
184 +\r
185 +static enum field\r
186 +next_field (enum field field)\r
187 +{\r
188 +    /* note: depends on the enum ordering */\r
189 +    return field < TM_ABS_YEAR ? field + 1 : TM_NONE;\r
190 +}\r
191 +\r
192 +static enum field\r
193 +abs_to_rel_field (enum field field)\r
194 +{\r
195 +    assert (field <= TM_ABS_YEAR);\r
196 +\r
197 +    /* note: depends on the enum ordering */\r
198 +    return field + (TM_REL_SEC - TM_ABS_SEC);\r
199 +}\r
200 +\r
201 +/* get zero value for field */\r
202 +static int\r
203 +field_zero (enum field field)\r
204 +{\r
205 +    if (field == TM_ABS_MDAY || field == TM_ABS_MON)\r
206 +       return 1;\r
207 +    else if (field == TM_ABS_YEAR)\r
208 +       return 1970;\r
209 +    else\r
210 +       return 0;\r
211 +}\r
212 +\r
213 +struct state {\r
214 +    int tm[TM_SIZE];                   /* parsed date and time */\r
215 +    enum field_set set[TM_SIZE];       /* set status of tm */\r
216 +\r
217 +    enum field last_field;\r
218 +    char delim;\r
219 +\r
220 +    int postponed_length;      /* number of digits in postponed value */\r
221 +    int postponed_value;\r
222 +    char postponed_delim;\r
223 +};\r
224 +\r
225 +/*\r
226 + * Helpers for postponed numbers.\r
227 + *\r
228 + * postponed_length is the number of digits in postponed value. 0\r
229 + * means there is no postponed number. -1 means there is a postponed\r
230 + * number, but it comes from a keyword, and it doesn't have digits.\r
231 + */\r
232 +static int\r
233 +get_postponed_length (struct state *state)\r
234 +{\r
235 +    return state->postponed_length;\r
236 +}\r
237 +\r
238 +static bool\r
239 +get_postponed_number (struct state *state, int *v, int *n, char *d)\r
240 +{\r
241 +    if (!state->postponed_length)\r
242 +       return false;\r
243 +\r
244 +    if (n)\r
245 +       *n = state->postponed_length;\r
246 +\r
247 +    if (v)\r
248 +       *v = state->postponed_value;\r
249 +\r
250 +    if (d)\r
251 +       *d = state->postponed_delim;\r
252 +\r
253 +    state->postponed_length = 0;\r
254 +    state->postponed_value = 0;\r
255 +    state->postponed_delim = 0;\r
256 +\r
257 +    return true;\r
258 +}\r
259 +\r
260 +/* parse postponed number if one exists */\r
261 +static int parse_postponed_number (struct state *state, int v, int n, char d);\r
262 +static int\r
263 +handle_postponed_number (struct state *state)\r
264 +{\r
265 +    int v = state->postponed_value;\r
266 +    int n = state->postponed_length;\r
267 +    char d = state->postponed_delim;\r
268 +\r
269 +    if (!n)\r
270 +       return 0;\r
271 +\r
272 +    state->postponed_value = 0;\r
273 +    state->postponed_length = 0;\r
274 +    state->postponed_delim = 0;\r
275 +\r
276 +    return parse_postponed_number (state, v, n, d);\r
277 +}\r
278 +\r
279 +/*\r
280 + * set new postponed number to be handled later. if one exists\r
281 + * already, handle it first. n may be -1 to indicate a keyword that\r
282 + * has no number length.\r
283 + */\r
284 +static int\r
285 +set_postponed_number (struct state *state, int v, int n)\r
286 +{\r
287 +    int r;\r
288 +    char d = state->delim;\r
289 +\r
290 +    /* parse previous postponed number, if any */\r
291 +    r = handle_postponed_number (state);\r
292 +    if (r)\r
293 +       return r;\r
294 +\r
295 +    state->postponed_length = n;\r
296 +    state->postponed_value = v;\r
297 +    state->postponed_delim = d;\r
298 +\r
299 +    return 0;\r
300 +}\r
301 +\r
302 +static void\r
303 +set_delim (struct state *state, char delim)\r
304 +{\r
305 +    state->delim = delim;\r
306 +}\r
307 +\r
308 +static void\r
309 +unset_delim (struct state *state)\r
310 +{\r
311 +    state->delim = 0;\r
312 +}\r
313 +\r
314 +/*\r
315 + * Field set/get/mod helpers.\r
316 + */\r
317 +\r
318 +/* returns unset for non-tracked fields */\r
319 +static bool\r
320 +is_field_set (struct state *state, enum field field)\r
321 +{\r
322 +    assert (field < ARRAY_SIZE (state->tm));\r
323 +\r
324 +    return field < ARRAY_SIZE (state->set) &&\r
325 +          state->set[field] != FIELD_UNSET;\r
326 +}\r
327 +\r
328 +static void\r
329 +unset_field (struct state *state, enum field field)\r
330 +{\r
331 +    assert (field < ARRAY_SIZE (state->tm));\r
332 +\r
333 +    state->set[field] = FIELD_UNSET;\r
334 +    state->tm[field] = 0;\r
335 +}\r
336 +\r
337 +/* Set field to value. */\r
338 +static int\r
339 +set_field (struct state *state, enum field field, int value)\r
340 +{\r
341 +    int r;\r
342 +\r
343 +    assert (field < ARRAY_SIZE (state->tm));\r
344 +\r
345 +    /* some fields can only be set once */\r
346 +    if (field < ARRAY_SIZE (state->set) && state->set[field] != FIELD_UNSET)\r
347 +       return -PARSE_TIME_ERR_ALREADYSET;\r
348 +\r
349 +    state->set[field] = FIELD_SET;\r
350 +\r
351 +    /*\r
352 +     * REVISIT: There could be a "next_field" that would be set from\r
353 +     * "field" for the duration of the handle_postponed_number() call,\r
354 +     * so it has more information to work with.\r
355 +     */\r
356 +\r
357 +    /* parse postponed number, if any */\r
358 +    r = handle_postponed_number (state);\r
359 +    if (r)\r
360 +       return r;\r
361 +\r
362 +    unset_delim (state);\r
363 +\r
364 +    state->tm[field] = value;\r
365 +    state->last_field = field;\r
366 +\r
367 +    return 0;\r
368 +}\r
369 +\r
370 +/*\r
371 + * Mark n fields in fields to be set to current date/time in the\r
372 + * specified time zone, or local timezone if not specified. The fields\r
373 + * will be initialized after parsing is complete and timezone is\r
374 + * known.\r
375 + */\r
376 +static int\r
377 +set_fields_to_now (struct state *state, enum field *fields, size_t n)\r
378 +{\r
379 +    size_t i;\r
380 +    int r;\r
381 +\r
382 +    for (i = 0; i < n; i++) {\r
383 +       r = set_field (state, fields[i], 0);\r
384 +       if (r)\r
385 +           return r;\r
386 +       state->set[fields[i]] = FIELD_NOW;\r
387 +    }\r
388 +\r
389 +    return 0;\r
390 +}\r
391 +\r
392 +/* Modify field by adding value to it. To be used on relative fields. */\r
393 +static int\r
394 +mod_field (struct state *state, enum field field, int value)\r
395 +{\r
396 +    int r;\r
397 +\r
398 +    assert (field < ARRAY_SIZE (state->tm));   /* assert relative??? */\r
399 +\r
400 +    if (field < ARRAY_SIZE (state->set))\r
401 +       state->set[field] = FIELD_SET;\r
402 +\r
403 +    /* parse postponed number, if any */\r
404 +    r = handle_postponed_number (state);\r
405 +    if (r)\r
406 +       return r;\r
407 +\r
408 +    unset_delim (state);\r
409 +\r
410 +    state->tm[field] += value;\r
411 +    state->last_field = field;\r
412 +\r
413 +    return 0;\r
414 +}\r
415 +\r
416 +/*\r
417 + * Get field value. Make sure the field is set before query. It's most\r
418 + * likely an error to call this while parsing (for example fields set\r
419 + * as FIELD_NOW will only be set to some value after parsing).\r
420 + */\r
421 +static int\r
422 +get_field (struct state *state, enum field field)\r
423 +{\r
424 +    assert (field < ARRAY_SIZE (state->tm));\r
425 +\r
426 +    return state->tm[field];\r
427 +}\r
428 +\r
429 +/*\r
430 + * Validity checkers.\r
431 + */\r
432 +static bool is_valid_12hour (int h)\r
433 +{\r
434 +    return h >= 0 && h <= 12;\r
435 +}\r
436 +\r
437 +static bool is_valid_time (int h, int m, int s)\r
438 +{\r
439 +    /* allow 24:00:00 to denote end of day */\r
440 +    if (h == 24 && m == 0 && s == 0)\r
441 +       return true;\r
442 +\r
443 +    return h >= 0 && h <= 23 && m >= 0 && m <= 59 && s >= 0 && s <= 59;\r
444 +}\r
445 +\r
446 +static bool is_valid_mday (int mday)\r
447 +{\r
448 +    return mday >= 1 && mday <= 31;\r
449 +}\r
450 +\r
451 +static bool is_valid_mon (int mon)\r
452 +{\r
453 +    return mon >= 1 && mon <= 12;\r
454 +}\r
455 +\r
456 +static bool is_valid_year (int year)\r
457 +{\r
458 +    return year >= 1970;\r
459 +}\r
460 +\r
461 +static bool is_valid_date (int year, int mon, int mday)\r
462 +{\r
463 +    return is_valid_year (year) && is_valid_mon (mon) && is_valid_mday (mday);\r
464 +}\r
465 +\r
466 +/* Unset indicator for time and date set helpers. */\r
467 +#define UNSET -1\r
468 +\r
469 +/* Time set helper. No input checking. Use UNSET (-1) to leave unset. */\r
470 +static int\r
471 +set_abs_time (struct state *state, int hour, int min, int sec)\r
472 +{\r
473 +    int r;\r
474 +\r
475 +    if (hour != UNSET) {\r
476 +       if ((r = set_field (state, TM_ABS_HOUR, hour)))\r
477 +           return r;\r
478 +    }\r
479 +\r
480 +    if (min != UNSET) {\r
481 +       if ((r = set_field (state, TM_ABS_MIN, min)))\r
482 +           return r;\r
483 +    }\r
484 +\r
485 +    if (sec != UNSET) {\r
486 +       if ((r = set_field (state, TM_ABS_SEC, sec)))\r
487 +           return r;\r
488 +    }\r
489 +\r
490 +    return 0;\r
491 +}\r
492 +\r
493 +/* Date set helper. No input checking. Use UNSET (-1) to leave unset. */\r
494 +static int\r
495 +set_abs_date (struct state *state, int year, int mon, int mday)\r
496 +{\r
497 +    int r;\r
498 +\r
499 +    if (year != UNSET) {\r
500 +       if ((r = set_field (state, TM_ABS_YEAR, year)))\r
501 +           return r;\r
502 +    }\r
503 +\r
504 +    if (mon != UNSET) {\r
505 +       if ((r = set_field (state, TM_ABS_MON, mon)))\r
506 +           return r;\r
507 +    }\r
508 +\r
509 +    if (mday != UNSET) {\r
510 +       if ((r = set_field (state, TM_ABS_MDAY, mday)))\r
511 +           return r;\r
512 +    }\r
513 +\r
514 +    return 0;\r
515 +}\r
516 +\r
517 +/*\r
518 + * Keyword parsing and handling.\r
519 + */\r
520 +struct keyword;\r
521 +typedef int (*setter_t)(struct state *state, struct keyword *kw);\r
522 +\r
523 +struct keyword {\r
524 +    const char *name;  /* keyword */\r
525 +    enum field field;  /* field to set, or FIELD_NONE if N/A */\r
526 +    int value;         /* value to set, or 0 if N/A */\r
527 +    setter_t set;      /* function to use for setting, if non-NULL */\r
528 +};\r
529 +\r
530 +/*\r
531 + * Setter callback functions for keywords.\r
532 + */\r
533 +static int\r
534 +kw_set_default (struct state *state, struct keyword *kw)\r
535 +{\r
536 +    return set_field (state, kw->field, kw->value);\r
537 +}\r
538 +\r
539 +static int\r
540 +kw_set_rel (struct state *state, struct keyword *kw)\r
541 +{\r
542 +    int multiplier = 1;\r
543 +\r
544 +    /* get a previously set multiplier, if any */\r
545 +    get_postponed_number (state, &multiplier, NULL, NULL);\r
546 +\r
547 +    /* accumulate relative field values */\r
548 +    return mod_field (state, kw->field, multiplier * kw->value);\r
549 +}\r
550 +\r
551 +static int\r
552 +kw_set_number (struct state *state, struct keyword *kw)\r
553 +{\r
554 +    /* -1 = no length, from keyword */\r
555 +    return set_postponed_number (state, kw->value, -1);\r
556 +}\r
557 +\r
558 +static int\r
559 +kw_set_month (struct state *state, struct keyword *kw)\r
560 +{\r
561 +    int n = get_postponed_length (state);\r
562 +\r
563 +    /* consume postponed number if it could be mday */\r
564 +    if (n == 1 || n == 2) {\r
565 +       int r, v;\r
566 +\r
567 +       get_postponed_number (state, &v, NULL, NULL);\r
568 +\r
569 +       if (!is_valid_mday (v))\r
570 +           return -PARSE_TIME_ERR_INVALIDDATE;\r
571 +\r
572 +       r = set_field (state, TM_ABS_MDAY, v);\r
573 +       if (r)\r
574 +           return r;\r
575 +    }\r
576 +\r
577 +    return set_field (state, kw->field, kw->value);\r
578 +}\r
579 +\r
580 +static int\r
581 +kw_set_ampm (struct state *state, struct keyword *kw)\r
582 +{\r
583 +    int n = get_postponed_length (state);\r
584 +\r
585 +    /* consume postponed number if it could be hour */\r
586 +    if (n == 1 || n == 2) {\r
587 +       int r, v;\r
588 +\r
589 +       get_postponed_number (state, &v, NULL, NULL);\r
590 +\r
591 +       if (!is_valid_12hour (v))\r
592 +           return -PARSE_TIME_ERR_INVALIDTIME;\r
593 +\r
594 +       r = set_abs_time (state, v, 0, 0);\r
595 +       if (r)\r
596 +           return r;\r
597 +    }\r
598 +\r
599 +    return set_field (state, kw->field, kw->value);\r
600 +}\r
601 +\r
602 +static int\r
603 +kw_set_timeofday (struct state *state, struct keyword *kw)\r
604 +{\r
605 +    return set_abs_time (state, kw->value, 0, 0);\r
606 +}\r
607 +\r
608 +static int\r
609 +kw_set_today (struct state *state, unused (struct keyword *kw))\r
610 +{\r
611 +    enum field fields[] = { TM_ABS_YEAR, TM_ABS_MON, TM_ABS_MDAY };\r
612 +\r
613 +    return set_fields_to_now (state, fields, ARRAY_SIZE (fields));\r
614 +}\r
615 +\r
616 +static int\r
617 +kw_set_now (struct state *state, unused (struct keyword *kw))\r
618 +{\r
619 +    enum field fields[] = { TM_ABS_HOUR, TM_ABS_MIN, TM_ABS_SEC };\r
620 +\r
621 +    return set_fields_to_now (state, fields, ARRAY_SIZE (fields));\r
622 +}\r
623 +\r
624 +static int\r
625 +kw_set_ordinal (struct state *state, struct keyword *kw)\r
626 +{\r
627 +    int n, v;\r
628 +\r
629 +    /* require a postponed number */\r
630 +    if (!get_postponed_number (state, &v, &n, NULL))\r
631 +       return -PARSE_TIME_ERR_DATEFORMAT;\r
632 +\r
633 +    /* ordinals are mday */\r
634 +    if (n != 1 && n != 2)\r
635 +       return -PARSE_TIME_ERR_DATEFORMAT;\r
636 +\r
637 +    /* be strict about st, nd, rd, and lax about th */\r
638 +    if (strcasecmp (kw->name, "st") == 0 && v != 1 && v != 21 && v != 31)\r
639 +       return -PARSE_TIME_ERR_INVALIDDATE;\r
640 +    else if (strcasecmp (kw->name, "nd") == 0 && v != 2 && v != 22)\r
641 +       return -PARSE_TIME_ERR_INVALIDDATE;\r
642 +    else if (strcasecmp (kw->name, "rd") == 0 && v != 3 && v != 23)\r
643 +       return -PARSE_TIME_ERR_INVALIDDATE;\r
644 +    else if (strcasecmp (kw->name, "th") == 0 && !is_valid_mday (v))\r
645 +       return -PARSE_TIME_ERR_INVALIDDATE;\r
646 +\r
647 +    return set_field (state, TM_ABS_MDAY, v);\r
648 +}\r
649 +\r
650 +/*\r
651 + * Accepted keywords.\r
652 + *\r
653 + * A keyword may optionally contain a '|' to indicate the minimum\r
654 + * match length. Without one, full match is required. It's advisable\r
655 + * to keep the minimum match parts unique across all keywords.\r
656 + *\r
657 + * If keyword begins with upper case letter, then the matching will be\r
658 + * case sensitive. Otherwise the matching is case insensitive.\r
659 + *\r
660 + * If setter is NULL, set_default will be used.\r
661 + *\r
662 + * Note: Order matters. Matching is greedy, longest match is used, but\r
663 + * of equal length matches the first one is used.\r
664 + */\r
665 +static struct keyword keywords[] = {\r
666 +    /* weekdays */\r
667 +    { N_("sun|day"),   TM_ABS_WDAY,    0,      NULL },\r
668 +    { N_("mon|day"),   TM_ABS_WDAY,    1,      NULL },\r
669 +    { N_("tue|sday"),  TM_ABS_WDAY,    2,      NULL },\r
670 +    { N_("wed|nesday"),        TM_ABS_WDAY,    3,      NULL },\r
671 +    { N_("thu|rsday"), TM_ABS_WDAY,    4,      NULL },\r
672 +    { N_("fri|day"),   TM_ABS_WDAY,    5,      NULL },\r
673 +    { N_("sat|urday"), TM_ABS_WDAY,    6,      NULL },\r
674 +\r
675 +    /* months */\r
676 +    { N_("jan|uary"),  TM_ABS_MON,     1,      kw_set_month },\r
677 +    { N_("feb|ruary"), TM_ABS_MON,     2,      kw_set_month },\r
678 +    { N_("mar|ch"),    TM_ABS_MON,     3,      kw_set_month },\r
679 +    { N_("apr|il"),    TM_ABS_MON,     4,      kw_set_month },\r
680 +    { N_("may"),       TM_ABS_MON,     5,      kw_set_month },\r
681 +    { N_("jun|e"),     TM_ABS_MON,     6,      kw_set_month },\r
682 +    { N_("jul|y"),     TM_ABS_MON,     7,      kw_set_month },\r
683 +    { N_("aug|ust"),   TM_ABS_MON,     8,      kw_set_month },\r
684 +    { N_("sep|tember"),        TM_ABS_MON,     9,      kw_set_month },\r
685 +    { N_("oct|ober"),  TM_ABS_MON,     10,     kw_set_month },\r
686 +    { N_("nov|ember"), TM_ABS_MON,     11,     kw_set_month },\r
687 +    { N_("dec|ember"), TM_ABS_MON,     12,     kw_set_month },\r
688 +\r
689 +    /* durations */\r
690 +    { N_("y|ears"),    TM_REL_YEAR,    1,      kw_set_rel },\r
691 +    { N_("w|eeks"),    TM_REL_WEEK,    1,      kw_set_rel },\r
692 +    { N_("d|ays"),     TM_REL_DAY,     1,      kw_set_rel },\r
693 +    { N_("h|ours"),    TM_REL_HOUR,    1,      kw_set_rel },\r
694 +    { N_("hr|s"),      TM_REL_HOUR,    1,      kw_set_rel },\r
695 +    { N_("m|inutes"),  TM_REL_MIN,     1,      kw_set_rel },\r
696 +    /* M=months, m=minutes */\r
697 +    { N_("M"),         TM_REL_MON,     1,      kw_set_rel },\r
698 +    { N_("mins"),      TM_REL_MIN,     1,      kw_set_rel },\r
699 +    { N_("mo|nths"),   TM_REL_MON,     1,      kw_set_rel },\r
700 +    { N_("s|econds"),  TM_REL_SEC,     1,      kw_set_rel },\r
701 +    { N_("secs"),      TM_REL_SEC,     1,      kw_set_rel },\r
702 +\r
703 +    /* numbers */\r
704 +    { N_("one"),       TM_NONE,        1,      kw_set_number },\r
705 +    { N_("two"),       TM_NONE,        2,      kw_set_number },\r
706 +    { N_("three"),     TM_NONE,        3,      kw_set_number },\r
707 +    { N_("four"),      TM_NONE,        4,      kw_set_number },\r
708 +    { N_("five"),      TM_NONE,        5,      kw_set_number },\r
709 +    { N_("six"),       TM_NONE,        6,      kw_set_number },\r
710 +    { N_("seven"),     TM_NONE,        7,      kw_set_number },\r
711 +    { N_("eight"),     TM_NONE,        8,      kw_set_number },\r
712 +    { N_("nine"),      TM_NONE,        9,      kw_set_number },\r
713 +    { N_("ten"),       TM_NONE,        10,     kw_set_number },\r
714 +    { N_("dozen"),     TM_NONE,        12,     kw_set_number },\r
715 +    { N_("hundred"),   TM_NONE,        100,    kw_set_number },\r
716 +\r
717 +    /* special number forms */\r
718 +    { N_("this"),      TM_NONE,        0,      kw_set_number },\r
719 +    { N_("last"),      TM_NONE,        1,      kw_set_number },\r
720 +\r
721 +    /* specials */\r
722 +    { N_("yesterday"), TM_REL_DAY,     1,      kw_set_rel },\r
723 +    { N_("today"),     TM_NONE,        0,      kw_set_today },\r
724 +    { N_("now"),       TM_NONE,        0,      kw_set_now },\r
725 +    { N_("noon"),      TM_NONE,        12,     kw_set_timeofday },\r
726 +    { N_("midnight"),  TM_NONE,        0,      kw_set_timeofday },\r
727 +    { N_("am"),                TM_AMPM,        0,      kw_set_ampm },\r
728 +    { N_("a.m."),      TM_AMPM,        0,      kw_set_ampm },\r
729 +    { N_("pm"),                TM_AMPM,        1,      kw_set_ampm },\r
730 +    { N_("p.m."),      TM_AMPM,        1,      kw_set_ampm },\r
731 +    { N_("st"),                TM_NONE,        0,      kw_set_ordinal },\r
732 +    { N_("nd"),                TM_NONE,        0,      kw_set_ordinal },\r
733 +    { N_("rd"),                TM_NONE,        0,      kw_set_ordinal },\r
734 +    { N_("th"),                TM_NONE,        0,      kw_set_ordinal },\r
735 +\r
736 +    /* timezone codes: offset in minutes. FIXME: add more codes. */\r
737 +    { N_("pst"),       TM_TZ,          -8*60,  NULL },\r
738 +    { N_("mst"),       TM_TZ,          -7*60,  NULL },\r
739 +    { N_("cst"),       TM_TZ,          -6*60,  NULL },\r
740 +    { N_("est"),       TM_TZ,          -5*60,  NULL },\r
741 +    { N_("ast"),       TM_TZ,          -4*60,  NULL },\r
742 +    { N_("nst"),       TM_TZ,          -(3*60+30),     NULL },\r
743 +\r
744 +    { N_("gmt"),       TM_TZ,          0,      NULL },\r
745 +    { N_("utc"),       TM_TZ,          0,      NULL },\r
746 +\r
747 +    { N_("wet"),       TM_TZ,          0,      NULL },\r
748 +    { N_("cet"),       TM_TZ,          1*60,   NULL },\r
749 +    { N_("eet"),       TM_TZ,          2*60,   NULL },\r
750 +    { N_("fet"),       TM_TZ,          3*60,   NULL },\r
751 +\r
752 +    { N_("wat"),       TM_TZ,          1*60,   NULL },\r
753 +    { N_("cat"),       TM_TZ,          2*60,   NULL },\r
754 +    { N_("eat"),       TM_TZ,          3*60,   NULL },\r
755 +};\r
756 +\r
757 +/*\r
758 + * Compare strings s and keyword. Return number of matching chars on\r
759 + * match, 0 for no match. Match must be at least n chars (n == 0 all\r
760 + * of keyword), otherwise it's not a match. Use match_case for case\r
761 + * sensitive matching.\r
762 + */\r
763 +static size_t\r
764 +stringcmp (const char *s, const char *keyword, size_t n, bool match_case)\r
765 +{\r
766 +    size_t i;\r
767 +\r
768 +    for (i = 0; *s && *keyword; i++, s++, keyword++) {\r
769 +       if (match_case) {\r
770 +           if (*s != *keyword)\r
771 +               break;\r
772 +       } else {\r
773 +           if (tolower ((unsigned char) *s) !=\r
774 +               tolower ((unsigned char) *keyword))\r
775 +               break;\r
776 +       }\r
777 +    }\r
778 +\r
779 +    if (n)\r
780 +       return i < n ? 0 : i;\r
781 +    else\r
782 +       return *keyword ? 0 : i;\r
783 +}\r
784 +\r
785 +/*\r
786 + * Parse a keyword. Return < 0 on error, number of parsed chars on\r
787 + * success.\r
788 + */\r
789 +static ssize_t\r
790 +parse_keyword (struct state *state, const char *s)\r
791 +{\r
792 +    unsigned int i;\r
793 +    size_t n, max_n = 0;\r
794 +    struct keyword *kw = NULL;\r
795 +    int r;\r
796 +\r
797 +    /* Match longest keyword */\r
798 +    for (i = 0; i < ARRAY_SIZE (keywords); i++) {\r
799 +       /* Match case if keyword begins with upper case letter. */\r
800 +       bool mcase = isupper ((unsigned char) keywords[i].name[0]);\r
801 +       size_t minlen = 0;\r
802 +       char keyword[128];\r
803 +       char *p;\r
804 +\r
805 +       strncpy (keyword, _(keywords[i].name), sizeof (keyword));\r
806 +\r
807 +       /* Truncate too long keywords. REVISIT: Make this dynamic? */\r
808 +       keyword[sizeof (keyword) - 1] = '\0';\r
809 +\r
810 +       /* Minimum match length. */\r
811 +       p = strchr (keyword, '|');\r
812 +       if (p) {\r
813 +           minlen = p - keyword;\r
814 +           memmove (p, p + 1, strlen (p + 1) + 1);\r
815 +       }\r
816 +\r
817 +       n = stringcmp (s, keyword, minlen, mcase);\r
818 +       if (n > max_n || (n == max_n && mcase)) {\r
819 +           max_n = n;\r
820 +           kw = &keywords[i];\r
821 +       }\r
822 +    }\r
823 +\r
824 +    if (!kw)\r
825 +       return -PARSE_TIME_ERR_KEYWORD;\r
826 +\r
827 +    if (kw->set)\r
828 +       r = kw->set (state, kw);\r
829 +    else\r
830 +       r = kw_set_default (state, kw);\r
831 +\r
832 +    if (r < 0)\r
833 +       return r;\r
834 +\r
835 +    return max_n;\r
836 +}\r
837 +\r
838 +/*\r
839 + * Non-keyword parsers and their helpers.\r
840 + */\r
841 +\r
842 +static int\r
843 +set_user_tz (struct state *state, char sign, int hour, int min)\r
844 +{\r
845 +    int tz = hour * 60 + min;\r
846 +\r
847 +    assert (sign == '+' || sign == '-');\r
848 +\r
849 +    if (hour < 0 || hour > 14 || min < 0 || min > 59 || min % 15)\r
850 +       return -PARSE_TIME_ERR_INVALIDTIME;\r
851 +\r
852 +    if (sign == '-')\r
853 +       tz = -tz;\r
854 +\r
855 +    return set_field (state, TM_TZ, tz);\r
856 +}\r
857 +\r
858 +/*\r
859 + * Independent parsing of a postponed number when it wasn't consumed\r
860 + * during parsing of the following token.\r
861 + *\r
862 + * This should be able to trust that last_field and next_field are\r
863 + * right.\r
864 + */\r
865 +static int\r
866 +parse_postponed_number (struct state *state, int v, int n, char d)\r
867 +{\r
868 +    /*\r
869 +     * alright, these are really lone, won't affect parsing of\r
870 +     * following items... it's not a multiplier, those have been eaten\r
871 +     * away.\r
872 +     *\r
873 +     * also note numbers eaten away by parse_single_number.\r
874 +     */\r
875 +\r
876 +    assert (n < 8);\r
877 +\r
878 +    if (n == 1 || n == 2) {\r
879 +       if (state->last_field == TM_ABS_MON) {\r
880 +           /* D[D] */\r
881 +           if (!is_valid_mday (v))\r
882 +               return -PARSE_TIME_ERR_INVALIDDATE;\r
883 +\r
884 +           return set_field (state, TM_ABS_MDAY, v);\r
885 +       } else if (n == 2) {\r
886 +           /* REVISIT: only allow if last field is hour, min, or sec? */\r
887 +           if (d == '+' || d == '-') {\r
888 +               /* +/-HH */\r
889 +               return set_user_tz (state, d, v, 0);\r
890 +           }\r
891 +       }\r
892 +    } else if (n == 4) {\r
893 +       /* Notable exception: Value affects parsing. */\r
894 +       if (!is_valid_year (v)) {\r
895 +           if (d == '+' || d == '-') {\r
896 +               /* +/-HHMM */\r
897 +               return set_user_tz (state, d, v / 100, v % 100);\r
898 +           }\r
899 +       } else {\r
900 +           /* YYYY */\r
901 +           return set_field (state, TM_ABS_YEAR, v);\r
902 +       }\r
903 +    } else if (n == 6) {\r
904 +       /* HHMMSS */\r
905 +       int hour = v / 10000;\r
906 +       int min = (v / 100) % 100;\r
907 +       int sec = v % 100;\r
908 +\r
909 +       if (!is_valid_time (hour, min, sec))\r
910 +           return -PARSE_TIME_ERR_INVALIDTIME;\r
911 +\r
912 +       return set_abs_time (state, hour, min, sec);\r
913 +    }\r
914 +\r
915 +    /* else n is one of {-1, 3, 5, 7 } */\r
916 +\r
917 +    return -PARSE_TIME_ERR_FORMAT;\r
918 +}\r
919 +\r
920 +/* Parse a single number. Typically postpone parsing until later. */\r
921 +static int\r
922 +parse_single_number (struct state *state, unsigned long v,\r
923 +                    unsigned long n)\r
924 +{\r
925 +    assert (n);\r
926 +\r
927 +    /* parse things that can be parsed immediately */\r
928 +    if (n == 8) {\r
929 +       /* YYYYMMDD */\r
930 +       int year = v / 10000;\r
931 +       int mon = (v / 100) % 100;\r
932 +       int mday = v % 100;\r
933 +\r
934 +       if (!is_valid_date (year, mon, mday))\r
935 +           return -PARSE_TIME_ERR_INVALIDDATE;\r
936 +\r
937 +       return set_abs_date (state, year, mon, mday);\r
938 +    } else if (n > 8) {\r
939 +       /* FIXME: seconds since epoch */\r
940 +       return -PARSE_TIME_ERR_FORMAT;\r
941 +    }\r
942 +\r
943 +    if (v > INT_MAX)\r
944 +       return -PARSE_TIME_ERR_FORMAT;\r
945 +\r
946 +    return set_postponed_number (state, v, n);\r
947 +}\r
948 +\r
949 +static bool\r
950 +is_time_sep (char c)\r
951 +{\r
952 +    return c == ':';\r
953 +}\r
954 +\r
955 +static bool\r
956 +is_date_sep (char c)\r
957 +{\r
958 +    return c == '/' || c == '-' || c == '.';\r
959 +}\r
960 +\r
961 +static bool\r
962 +is_sep (char c)\r
963 +{\r
964 +    return is_time_sep (c) || is_date_sep (c);\r
965 +}\r
966 +\r
967 +/* two-digit year: 00...69 is 2000s, 70...99 1900s, if n == 0 keep unset */\r
968 +static int\r
969 +expand_year (unsigned long year, size_t n)\r
970 +{\r
971 +    if (n == 2) {\r
972 +       return (year < 70 ? 2000 : 1900) + year;\r
973 +    } else if (n == 4) {\r
974 +       return year;\r
975 +    } else {\r
976 +       return UNSET;\r
977 +    }\r
978 +}\r
979 +\r
980 +static int\r
981 +parse_date (struct state *state, char sep,\r
982 +           unsigned long v1, unsigned long v2, unsigned long v3,\r
983 +           size_t n1, size_t n2, size_t n3)\r
984 +{\r
985 +    int year = UNSET, mon = UNSET, mday = UNSET;\r
986 +\r
987 +    assert (is_date_sep (sep));\r
988 +\r
989 +    switch (sep) {\r
990 +    case '/': /* Date: M[M]/D[D][/YY[YY]] or M[M]/YYYY */\r
991 +       if (n1 != 1 && n1 != 2)\r
992 +           return -PARSE_TIME_ERR_DATEFORMAT;\r
993 +\r
994 +       if ((n2 == 1 || n2 == 2) && (n3 == 0 || n3 == 2 || n3 == 4)) {\r
995 +           /* M[M]/D[D][/YY[YY]] */\r
996 +           year = expand_year (v3, n3);\r
997 +           mon = v1;\r
998 +           mday = v2;\r
999 +       } else if (n2 == 4 && n3 == 0) {\r
1000 +           /* M[M]/YYYY */\r
1001 +           year = v2;\r
1002 +           mon = v1;\r
1003 +       } else {\r
1004 +           return -PARSE_TIME_ERR_DATEFORMAT;\r
1005 +       }\r
1006 +       break;\r
1007 +\r
1008 +    case '-': /* Date: YYYY-MM[-DD] or DD-MM[-YY[YY]] or MM-YYYY */\r
1009 +       if (n1 == 4 && n2 == 2 && (n3 == 0 || n3 == 2)) {\r
1010 +           /* YYYY-MM[-DD] */\r
1011 +           year = v1;\r
1012 +           mon = v2;\r
1013 +           if (n3)\r
1014 +               mday = v3;\r
1015 +       } else if (n1 == 2 && n2 == 2 && (n3 == 0 || n3 == 2 || n3 == 4)) {\r
1016 +           /* DD-MM[-YY[YY]] */\r
1017 +           year = expand_year (v3, n3);\r
1018 +           mon = v2;\r
1019 +           mday = v1;\r
1020 +       } else if (n1 == 2 && n2 == 4 && n3 == 0) {\r
1021 +           /* MM-YYYY */\r
1022 +           year = v2;\r
1023 +           mon = v1;\r
1024 +       } else {\r
1025 +           return -PARSE_TIME_ERR_DATEFORMAT;\r
1026 +       }\r
1027 +       break;\r
1028 +\r
1029 +    case '.': /* Date: D[D].M[M][.[YY[YY]]] */\r
1030 +       if ((n1 != 1 && n1 != 2) || (n2 != 1 && n2 != 2) ||\r
1031 +           (n3 != 0 && n3 != 2 && n3 != 4))\r
1032 +           return -PARSE_TIME_ERR_DATEFORMAT;\r
1033 +\r
1034 +       year = expand_year (v3, n3);\r
1035 +       mon = v2;\r
1036 +       mday = v1;\r
1037 +       break;\r
1038 +    }\r
1039 +\r
1040 +    if (year != UNSET && !is_valid_year (year))\r
1041 +       return -PARSE_TIME_ERR_INVALIDDATE;\r
1042 +\r
1043 +    if (mon != UNSET && !is_valid_mon (mon))\r
1044 +       return -PARSE_TIME_ERR_INVALIDDATE;\r
1045 +\r
1046 +    if (mday != UNSET && !is_valid_mday (mday))\r
1047 +       return -PARSE_TIME_ERR_INVALIDDATE;\r
1048 +\r
1049 +    return set_abs_date (state, year, mon, mday);\r
1050 +}\r
1051 +\r
1052 +static int\r
1053 +parse_time (struct state *state, char sep,\r
1054 +           unsigned long v1, unsigned long v2, unsigned long v3,\r
1055 +           size_t n1, size_t n2, size_t n3)\r
1056 +{\r
1057 +    assert (is_time_sep (sep));\r
1058 +\r
1059 +    if ((n1 != 1 && n1 != 2) || n2 != 2 || (n3 != 0 && n3 != 2))\r
1060 +       return -PARSE_TIME_ERR_TIMEFORMAT;\r
1061 +\r
1062 +    /*\r
1063 +     * REVISIT: this means it's required to set time *before* being\r
1064 +     * able to set timezone\r
1065 +     */\r
1066 +    if (is_field_set (state, TM_ABS_HOUR) &&\r
1067 +       is_field_set (state, TM_ABS_MIN) &&\r
1068 +       n1 == 2 && n2 == 2 && n3 == 0 &&\r
1069 +       (state->delim == '+' || state->delim == '-')) {\r
1070 +       return set_user_tz (state, state->delim, v1, v2);\r
1071 +    }\r
1072 +\r
1073 +    if (!is_valid_time (v1, v2, v3))\r
1074 +       return -PARSE_TIME_ERR_INVALIDTIME;\r
1075 +\r
1076 +    return set_abs_time (state, v1, v2, n3 ? v3 : 0);\r
1077 +}\r
1078 +\r
1079 +/* strtoul helper that assigns length */\r
1080 +static unsigned long\r
1081 +strtoul_len (const char *s, const char **endp, size_t *len)\r
1082 +{\r
1083 +    unsigned long val = strtoul (s, (char **) endp, 10);\r
1084 +\r
1085 +    *len = *endp - s;\r
1086 +    return val;\r
1087 +}\r
1088 +\r
1089 +/*\r
1090 + * Parse a (group of) number(s). Return < 0 on error, number of parsed\r
1091 + * chars on success.\r
1092 + */\r
1093 +static ssize_t\r
1094 +parse_number (struct state *state, const char *s)\r
1095 +{\r
1096 +    int r;\r
1097 +    unsigned long v1, v2, v3 = 0;\r
1098 +    size_t n1, n2, n3 = 0;\r
1099 +    const char *p = s;\r
1100 +    char sep;\r
1101 +\r
1102 +    v1 = strtoul_len (p, &p, &n1);\r
1103 +\r
1104 +    if (is_sep (*p) && isdigit ((unsigned char) *(p + 1))) {\r
1105 +       sep = *p;\r
1106 +       v2 = strtoul_len (p + 1, &p, &n2);\r
1107 +    } else {\r
1108 +       /* a single number */\r
1109 +       r = parse_single_number (state, v1, n1);\r
1110 +       if (r)\r
1111 +           return r;\r
1112 +\r
1113 +       return p - s;\r
1114 +    }\r
1115 +\r
1116 +    /* a group of two or three numbers? */\r
1117 +    if (*p == sep && isdigit ((unsigned char) *(p + 1)))\r
1118 +       v3 = strtoul_len (p + 1, &p, &n3);\r
1119 +\r
1120 +    if (is_time_sep (sep))\r
1121 +       r = parse_time (state, sep, v1, v2, v3, n1, n2, n3);\r
1122 +    else\r
1123 +       r = parse_date (state, sep, v1, v2, v3, n1, n2, n3);\r
1124 +\r
1125 +    if (r)\r
1126 +       return r;\r
1127 +\r
1128 +    return p - s;\r
1129 +}\r
1130 +\r
1131 +/*\r
1132 + * Parse delimiter(s). Return < 0 on error, number of parsed chars on\r
1133 + * success.\r
1134 + */\r
1135 +static ssize_t\r
1136 +parse_delim (struct state *state, const char *s)\r
1137 +{\r
1138 +    const char *p = s;\r
1139 +\r
1140 +    /*\r
1141 +     * REVISIT: any actions depending on the first delim after last\r
1142 +     * field? what could it be?\r
1143 +     */\r
1144 +\r
1145 +    /*\r
1146 +     * skip non-alpha and non-digit, and store the last for further\r
1147 +     * processing\r
1148 +     */\r
1149 +    while (*p && !isalnum ((unsigned char) *p)) {\r
1150 +       set_delim (state, *p);\r
1151 +       p++;\r
1152 +    }\r
1153 +\r
1154 +    return p - s;\r
1155 +}\r
1156 +\r
1157 +/*\r
1158 + * Parse a date/time string. Return < 0 on error, number of parsed\r
1159 + * chars on success.\r
1160 + */\r
1161 +static ssize_t\r
1162 +parse_input (struct state *state, const char *s)\r
1163 +{\r
1164 +    const char *p = s;\r
1165 +    ssize_t n;\r
1166 +    int r;\r
1167 +\r
1168 +    while (*p) {\r
1169 +       if (isalpha ((unsigned char) *p)) {\r
1170 +           n = parse_keyword (state, p);\r
1171 +       } else if (isdigit ((unsigned char) *p)) {\r
1172 +           n = parse_number (state, p);\r
1173 +       } else {\r
1174 +           n = parse_delim (state, p);\r
1175 +       }\r
1176 +\r
1177 +       if (n <= 0) {\r
1178 +           if (n == 0)\r
1179 +               n = -PARSE_TIME_ERR;\r
1180 +\r
1181 +           return n;\r
1182 +       }\r
1183 +\r
1184 +       p += n;\r
1185 +    }\r
1186 +\r
1187 +    /* parse postponed number, if any */\r
1188 +    r = handle_postponed_number (state);\r
1189 +    if (r < 0)\r
1190 +       return r;\r
1191 +\r
1192 +    return p - s;\r
1193 +}\r
1194 +\r
1195 +/*\r
1196 + * Processing the parsed input.\r
1197 + */\r
1198 +\r
1199 +/*\r
1200 + * Initialize reference time to tm. Use time zone in state if\r
1201 + * specified, otherwise local time. Use now for reference time if\r
1202 + * non-NULL, otherwise current time.\r
1203 + */\r
1204 +static int\r
1205 +initialize_now (struct state *state, struct tm *tm, const time_t *now)\r
1206 +{\r
1207 +    time_t t;\r
1208 +\r
1209 +    if (now) {\r
1210 +       t = *now;\r
1211 +    } else {\r
1212 +       if (time (&t) == (time_t) -1)\r
1213 +           return -PARSE_TIME_ERR_LIB;\r
1214 +    }\r
1215 +\r
1216 +    if (is_field_set (state, TM_TZ)) {\r
1217 +       /* some other time zone */\r
1218 +\r
1219 +       /* adjust now according to the TZ */\r
1220 +       t += get_field (state, TM_TZ) * 60;\r
1221 +\r
1222 +       /* it's not gm, but this doesn't mess with the tz */\r
1223 +       if (gmtime_r (&t, tm) == NULL)\r
1224 +           return -PARSE_TIME_ERR_LIB;\r
1225 +    } else {\r
1226 +       /* local time */\r
1227 +       if (localtime_r (&t, tm) == NULL)\r
1228 +           return -PARSE_TIME_ERR_LIB;\r
1229 +    }\r
1230 +\r
1231 +    return 0;\r
1232 +}\r
1233 +\r
1234 +/*\r
1235 + * Normalize tm according to mktime(3). Both mktime(3) and\r
1236 + * localtime_r(3) use local time, but they cancel each other out here,\r
1237 + * making this function agnostic to time zone.\r
1238 + */\r
1239 +static int\r
1240 +normalize_tm (struct tm *tm)\r
1241 +{\r
1242 +    time_t t = mktime (tm);\r
1243 +\r
1244 +    if (t == (time_t) -1)\r
1245 +       return -PARSE_TIME_ERR_LIB;\r
1246 +\r
1247 +    if (!localtime_r (&t, tm))\r
1248 +       return -PARSE_TIME_ERR_LIB;\r
1249 +\r
1250 +    return 0;\r
1251 +}\r
1252 +\r
1253 +/* Get field out of a struct tm. */\r
1254 +static int\r
1255 +tm_get_field (const struct tm *tm, enum field field)\r
1256 +{\r
1257 +    switch (field) {\r
1258 +    case TM_ABS_SEC:   return tm->tm_sec;\r
1259 +    case TM_ABS_MIN:   return tm->tm_min;\r
1260 +    case TM_ABS_HOUR:  return tm->tm_hour;\r
1261 +    case TM_ABS_MDAY:  return tm->tm_mday;\r
1262 +    case TM_ABS_MON:   return tm->tm_mon + 1; /* 0- to 1-based */\r
1263 +    case TM_ABS_YEAR:  return 1900 + tm->tm_year;\r
1264 +    case TM_ABS_WDAY:  return tm->tm_wday;\r
1265 +    case TM_ABS_ISDST: return tm->tm_isdst;\r
1266 +    default:\r
1267 +       assert (false);\r
1268 +       break;\r
1269 +    }\r
1270 +\r
1271 +    return 0;\r
1272 +}\r
1273 +\r
1274 +/* Modify hour according to am/pm setting. */\r
1275 +static int\r
1276 +fixup_ampm (struct state *state)\r
1277 +{\r
1278 +    int hour, hdiff = 0;\r
1279 +\r
1280 +    if (!is_field_set (state, TM_AMPM))\r
1281 +       return 0;\r
1282 +\r
1283 +    if (!is_field_set (state, TM_ABS_HOUR))\r
1284 +       return -PARSE_TIME_ERR_TIMEFORMAT;\r
1285 +\r
1286 +    hour = get_field (state, TM_ABS_HOUR);\r
1287 +    if (!is_valid_12hour (hour))\r
1288 +       return -PARSE_TIME_ERR_INVALIDTIME;\r
1289 +\r
1290 +    if (get_field (state, TM_AMPM)) {\r
1291 +       /* 12pm is noon */\r
1292 +       if (hour != 12)\r
1293 +           hdiff = 12;\r
1294 +    } else {\r
1295 +       /* 12am is midnight, beginning of day */\r
1296 +       if (hour == 12)\r
1297 +           hdiff = -12;\r
1298 +    }\r
1299 +\r
1300 +    mod_field (state, TM_REL_HOUR, -hdiff);\r
1301 +\r
1302 +    return 0;\r
1303 +}\r
1304 +\r
1305 +/* Combine absolute and relative fields, and round. */\r
1306 +static int\r
1307 +create_output (struct state *state, time_t *t_out, const time_t *tnow,\r
1308 +              int round)\r
1309 +{\r
1310 +    struct tm tm = { .tm_isdst = -1 };\r
1311 +    struct tm now;\r
1312 +    time_t t;\r
1313 +    enum field f;\r
1314 +    int r;\r
1315 +    int week_round = PARSE_TIME_NO_ROUND;\r
1316 +\r
1317 +    r = initialize_now (state, &now, tnow);\r
1318 +    if (r)\r
1319 +       return r;\r
1320 +\r
1321 +    /* initialize uninitialized fields to now */\r
1322 +    for (f = TM_ABS_SEC; f != TM_NONE; f = next_field (f)) {\r
1323 +       if (state->set[f] == FIELD_NOW) {\r
1324 +           state->tm[f] = tm_get_field (&now, f);\r
1325 +           state->set[f] = FIELD_SET;\r
1326 +       }\r
1327 +    }\r
1328 +\r
1329 +    /*\r
1330 +     * If MON is set but YEAR is not, refer to past month.\r
1331 +     *\r
1332 +     * REVISIT: Why are month/week special in this regard? What about\r
1333 +     * mday, or time. Should refer to past.\r
1334 +     */\r
1335 +    if (is_field_set (state, TM_ABS_MON) &&\r
1336 +       !is_field_set (state, TM_ABS_YEAR)) {\r
1337 +       if (get_field (state, TM_ABS_MON) >= tm_get_field (&now, TM_ABS_MON))\r
1338 +           mod_field (state, TM_REL_YEAR, 1);\r
1339 +    }\r
1340 +\r
1341 +    /*\r
1342 +     * If WDAY is set but MDAY is not, we consider WDAY relative\r
1343 +     *\r
1344 +     * REVISIT: This fails on stuff like "two months ago monday"\r
1345 +     * because two months ago wasn't the same day as today. Postpone\r
1346 +     * until we know date?\r
1347 +     */\r
1348 +    if (is_field_set (state, TM_ABS_WDAY) &&\r
1349 +       !is_field_set (state, TM_ABS_MDAY)) {\r
1350 +       int wday = get_field (state, TM_ABS_WDAY);\r
1351 +       int today = tm_get_field (&now, TM_ABS_WDAY);\r
1352 +       int rel_days;\r
1353 +\r
1354 +       if (today > wday)\r
1355 +           rel_days = today - wday;\r
1356 +       else\r
1357 +           rel_days = today + 7 - wday;\r
1358 +\r
1359 +       /* this also prevents special week rounding from happening */\r
1360 +       mod_field (state, TM_REL_DAY, rel_days);\r
1361 +\r
1362 +       unset_field (state, TM_ABS_WDAY);\r
1363 +    }\r
1364 +\r
1365 +    r = fixup_ampm (state);\r
1366 +    if (r)\r
1367 +       return r;\r
1368 +\r
1369 +    /*\r
1370 +     * Iterate fields from most accurate to least accurate, and set\r
1371 +     * unset fields according to requested rounding.\r
1372 +     */\r
1373 +    for (f = TM_ABS_SEC; f != TM_NONE; f = next_field (f)) {\r
1374 +       if (round != PARSE_TIME_NO_ROUND) {\r
1375 +           enum field r = abs_to_rel_field (f);\r
1376 +\r
1377 +           if (is_field_set (state, f) || is_field_set (state, r)) {\r
1378 +               if (round >= PARSE_TIME_ROUND_UP)\r
1379 +                   mod_field (state, r, -1);\r
1380 +               round = PARSE_TIME_NO_ROUND; /* no more rounding */\r
1381 +           } else {\r
1382 +               if (f == TM_ABS_MDAY &&\r
1383 +                   is_field_set (state, TM_REL_WEEK)) {\r
1384 +                   /* week is most accurate */\r
1385 +                   week_round = round;\r
1386 +                   round = PARSE_TIME_NO_ROUND;\r
1387 +               } else {\r
1388 +                   set_field (state, f, field_zero (f));\r
1389 +               }\r
1390 +           }\r
1391 +       }\r
1392 +\r
1393 +       if (!is_field_set (state, f))\r
1394 +           set_field (state, f, tm_get_field (&now, f));\r
1395 +    }\r
1396 +\r
1397 +    /* special case: rounding with week accuracy */\r
1398 +    if (week_round != PARSE_TIME_NO_ROUND) {\r
1399 +       /* temporarily set more accurate fields to now */\r
1400 +       set_field (state, TM_ABS_SEC, tm_get_field (&now, TM_ABS_SEC));\r
1401 +       set_field (state, TM_ABS_MIN, tm_get_field (&now, TM_ABS_MIN));\r
1402 +       set_field (state, TM_ABS_HOUR, tm_get_field (&now, TM_ABS_HOUR));\r
1403 +       set_field (state, TM_ABS_MDAY, tm_get_field (&now, TM_ABS_MDAY));\r
1404 +    }\r
1405 +\r
1406 +    /*\r
1407 +     * set all fields. they may contain out of range values before\r
1408 +     * normalization by mktime(3).\r
1409 +     */\r
1410 +    tm.tm_sec = get_field (state, TM_ABS_SEC) - get_field (state, TM_REL_SEC);\r
1411 +    tm.tm_min = get_field (state, TM_ABS_MIN) - get_field (state, TM_REL_MIN);\r
1412 +    tm.tm_hour = get_field (state, TM_ABS_HOUR) - get_field (state, TM_REL_HOUR);\r
1413 +    tm.tm_mday = get_field (state, TM_ABS_MDAY) -\r
1414 +                get_field (state, TM_REL_DAY) - 7 * get_field (state, TM_REL_WEEK);\r
1415 +    tm.tm_mon = get_field (state, TM_ABS_MON) - get_field (state, TM_REL_MON);\r
1416 +    tm.tm_mon--; /* 1- to 0-based */\r
1417 +    tm.tm_year = get_field (state, TM_ABS_YEAR) - get_field (state, TM_REL_YEAR) - 1900;\r
1418 +\r
1419 +    /*\r
1420 +     * It's always normal time.\r
1421 +     *\r
1422 +     * REVISIT: This is probably not a solution that universally\r
1423 +     * works. Just make sure DST is not taken into account. We don't\r
1424 +     * want rounding to be affected by DST.\r
1425 +     */\r
1426 +    tm.tm_isdst = -1;\r
1427 +\r
1428 +    /* special case: rounding with week accuracy */\r
1429 +    if (week_round != PARSE_TIME_NO_ROUND) {\r
1430 +       /* normalize to get proper tm.wday */\r
1431 +       r = normalize_tm (&tm);\r
1432 +       if (r < 0)\r
1433 +           return r;\r
1434 +\r
1435 +       /* set more accurate fields back to zero */\r
1436 +       tm.tm_sec = 0;\r
1437 +       tm.tm_min = 0;\r
1438 +       tm.tm_hour = 0;\r
1439 +       tm.tm_isdst = -1;\r
1440 +\r
1441 +       /* monday is the true 1st day of week, but this is easier */\r
1442 +       if (week_round <= PARSE_TIME_ROUND_DOWN)\r
1443 +           tm.tm_mday -= tm.tm_wday;\r
1444 +       else\r
1445 +           tm.tm_mday += 7 - tm.tm_wday;\r
1446 +    }\r
1447 +\r
1448 +    if (is_field_set (state, TM_TZ)) {\r
1449 +       /* tm is in specified TZ, convert to UTC for timegm(3) */\r
1450 +       tm.tm_min -= get_field (state, TM_TZ);\r
1451 +       t = timegm (&tm);\r
1452 +    } else {\r
1453 +       /* tm is in local time */\r
1454 +       t = mktime (&tm);\r
1455 +    }\r
1456 +\r
1457 +    if (t == (time_t) -1)\r
1458 +       return -PARSE_TIME_ERR_LIB;\r
1459 +\r
1460 +    *t_out = t;\r
1461 +\r
1462 +    return 0;\r
1463 +}\r
1464 +\r
1465 +/* internally, all errors are < 0. parse_time_string() returns errors > 0. */\r
1466 +#define EXTERNAL_ERR(r) (-r)\r
1467 +\r
1468 +int\r
1469 +parse_time_string (const char *s, time_t *t, const time_t *now, int round)\r
1470 +{\r
1471 +    struct state state = { .last_field = TM_NONE };\r
1472 +    int r;\r
1473 +\r
1474 +    if (!s || !t)\r
1475 +       return EXTERNAL_ERR (-PARSE_TIME_ERR);\r
1476 +\r
1477 +    r = parse_input (&state, s);\r
1478 +    if (r < 0)\r
1479 +       return EXTERNAL_ERR (r);\r
1480 +\r
1481 +    r = create_output (&state, t, now, round);\r
1482 +    if (r < 0)\r
1483 +       return EXTERNAL_ERR (r);\r
1484 +\r
1485 +    return 0;\r
1486 +}\r
1487 diff --git a/lib/parse-time-string.h b/lib/parse-time-string.h\r
1488 new file mode 100644\r
1489 index 0000000..50b7c6f\r
1490 --- /dev/null\r
1491 +++ b/lib/parse-time-string.h\r
1492 @@ -0,0 +1,95 @@\r
1493 +/*\r
1494 + * parse time string - user friendly date and time parser\r
1495 + * Copyright © 2012 Jani Nikula\r
1496 + *\r
1497 + * This program is free software: you can redistribute it and/or modify\r
1498 + * it under the terms of the GNU General Public License as published by\r
1499 + * the Free Software Foundation, either version 2 of the License, or\r
1500 + * (at your option) any later version.\r
1501 + *\r
1502 + * This program is distributed in the hope that it will be useful,\r
1503 + * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
1504 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
1505 + * GNU General Public License for more details.\r
1506 + *\r
1507 + * You should have received a copy of the GNU General Public License\r
1508 + * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r
1509 + *\r
1510 + * Author: Jani Nikula <jani@nikula.org>\r
1511 + */\r
1512 +\r
1513 +#ifndef PARSE_TIME_STRING_H\r
1514 +#define PARSE_TIME_STRING_H\r
1515 +\r
1516 +#ifdef __cplusplus\r
1517 +extern "C" {\r
1518 +#endif\r
1519 +\r
1520 +#include <time.h>\r
1521 +\r
1522 +/* return values for parse_time_string() */\r
1523 +enum {\r
1524 +    PARSE_TIME_OK = 0,\r
1525 +    PARSE_TIME_ERR,            /* unspecified error */\r
1526 +    PARSE_TIME_ERR_LIB,                /* library call failed */\r
1527 +    PARSE_TIME_ERR_ALREADYSET, /* attempt to set unit twice */\r
1528 +    PARSE_TIME_ERR_FORMAT,     /* generic date/time format error */\r
1529 +    PARSE_TIME_ERR_DATEFORMAT, /* date format error */\r
1530 +    PARSE_TIME_ERR_TIMEFORMAT, /* time format error */\r
1531 +    PARSE_TIME_ERR_INVALIDDATE,        /* date value error */\r
1532 +    PARSE_TIME_ERR_INVALIDTIME,        /* time value error */\r
1533 +    PARSE_TIME_ERR_KEYWORD,    /* unknown keyword */\r
1534 +};\r
1535 +\r
1536 +/* round values for parse_time_string() */\r
1537 +enum {\r
1538 +    PARSE_TIME_ROUND_DOWN = -1,\r
1539 +    PARSE_TIME_NO_ROUND = 0,\r
1540 +    PARSE_TIME_ROUND_UP = 1,\r
1541 +};\r
1542 +\r
1543 +/**\r
1544 + * parse_time_string() - user friendly date and time parser\r
1545 + * @s:         string to parse\r
1546 + * @t:         pointer to time_t to store parsed time in\r
1547 + * @now:       pointer to time_t containing reference date/time, or NULL\r
1548 + * @round:     PARSE_TIME_NO_ROUND, PARSE_TIME_ROUND_DOWN, or\r
1549 + *             PARSE_TIME_ROUND_UP\r
1550 + *\r
1551 + * Parse a date/time string 's' and store the parsed date/time result\r
1552 + * in 't'.\r
1553 + *\r
1554 + * A reference date/time is used for determining the "date/time units"\r
1555 + * (roughly equivalent to struct tm members) not specified by 's'. If\r
1556 + * 'now' is non-NULL, it must contain a pointer to a time_t to be used\r
1557 + * as reference date/time. Otherwise, the current time is used.\r
1558 + *\r
1559 + * If 's' does not specify a full date/time, the 'round' parameter\r
1560 + * specifies if and how the result should be rounded as follows:\r
1561 + *\r
1562 + *   PARSE_TIME_NO_ROUND: All date/time units that are not specified\r
1563 + *   by 's' are set to the corresponding unit derived from the\r
1564 + *   reference date/time.\r
1565 + *\r
1566 + *   PARSE_TIME_ROUND_DOWN: All date/time units that are more accurate\r
1567 + *   than the most accurate unit specified by 's' are set to the\r
1568 + *   smallest valid value for that unit. Rest of the unspecified units\r
1569 + *   are set as in PARSE_TIME_NO_ROUND.\r
1570 + *\r
1571 + *   PARSE_TIME_ROUND_UP: All date/time units that are more accurate\r
1572 + *   than the most accurate unit specified by 's' are set to the\r
1573 + *   smallest valid value for that unit. The most accurate unit\r
1574 + *   specified by 's' is incremented by one (and this is rolled over\r
1575 + *   to the less accurate units as necessary). Rest of the unspecified\r
1576 + *   units are set as in PARSE_TIME_NO_ROUND.\r
1577 + *\r
1578 + * Return 0 (PARSE_TIME_OK) for succesfully parsed date/time, or one\r
1579 + * of PARSE_TIME_ERR_* on error. 't' is not modified on error.\r
1580 + */\r
1581 +int parse_time_string (const char *s, time_t *t, const time_t *now, int round);\r
1582 +\r
1583 +#ifdef __cplusplus\r
1584 +}\r
1585 +#endif\r
1586 +\r
1587 +#endif /* PARSE_TIME_STRING_H */\r
1588 -- \r
1589 1.7.9.5\r
1590 \r