Re: [PATCH] emacs: wash: make word-wrap bound message width
[notmuch-archives.git] / 25 / 37df1cb14f481e7fdbf1344ee5bb75ee3d4b41
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 68515431FC0\r
6         for <notmuch@notmuchmail.org>; Sat, 13 Oct 2012 15:10:21 -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 1wZidJaSezi4 for <notmuch@notmuchmail.org>;\r
17         Sat, 13 Oct 2012 15:10:09 -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 5FB3B431FBC\r
22         for <notmuch@notmuchmail.org>; Sat, 13 Oct 2012 15:10:07 -0700 (PDT)\r
23 Received: by mail-lb0-f181.google.com with SMTP id gg6so2988019lbb.26\r
24         for <notmuch@notmuchmail.org>; Sat, 13 Oct 2012 15:10:07 -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=JD9TmvPD3UT0UgJpIHYdE34nXm/RPdNkrJyI6QONQSI=;\r
31         b=XtXv8HoWcXWlO/FAqYLz5RVItQZMv1tS6HY3i6eVlXOX39tZkrAAQmYiLX17AAwlHA\r
32         rZIaQ2rd7/8NzRs0HTb7w7+2JLrNiHyvVm8RA0G8VaHvTIpQmZu/3p+0aaCRMW+LXUt6\r
33         04r5GYMOkSzUw5BVNYAOQNIjSSxCnFJ6hMHW5uBmqKEoubLkz1FpT6+F5jiGDAt9hkPT\r
34         Dd9dqKImCJZ0AvoNDg6tediKXYLDD4Gwy3nk6BLayzJl3M2RVQ7sOEgSuJaVRMciDLiy\r
35         DjlJLIS2khMZFSyGJkR4N3FjeCJ/DCqr46OiozJna5TJ75thezqDORapRucr27/2xdPA\r
36         sjKQ==\r
37 Received: by 10.112.23.6 with SMTP id i6mr2918317lbf.7.1350166206918;\r
38         Sat, 13 Oct 2012 15:10:06 -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 ps11sm843127lab.12.2012.10.13.15.10.04\r
42         (version=SSLv3 cipher=OTHER); Sat, 13 Oct 2012 15:10:05 -0700 (PDT)\r
43 From: Jani Nikula <jani@nikula.org>\r
44 To: notmuch@notmuchmail.org\r
45 Subject: [PATCH v4 2/9] parse-time-string: add a date/time parser to notmuch\r
46 Date: Sun, 14 Oct 2012 01:09:48 +0300\r
47 Message-Id:\r
48  <0296be2a3899653549b3f95e1aa1a4a0632e92e7.1350164594.git.jani@nikula.org>\r
49 X-Mailer: git-send-email 1.7.9.5\r
50 In-Reply-To: <cover.1350164594.git.jani@nikula.org>\r
51 References: <cover.1350164594.git.jani@nikula.org>\r
52 In-Reply-To: <cover.1350164594.git.jani@nikula.org>\r
53 References: <cover.1350164594.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  ALoCoQnQLxZm/uM1aN8MkPUXXLgqj3CK6dyPn1atHscGR70RrX8Ok5uQks7Kvhf6IaojQG352cB0\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, 13 Oct 2012 22:10:21 -0000\r
72 \r
73 Add a date/time parser to notmuch, to be used for adding date range\r
74 query support for notmuch lib later on. Add the parser to a directory\r
75 of its own to make it independent of the rest of the notmuch code\r
76 base.\r
77 \r
78 Signed-off-by: Jani Nikula <jani@nikula.org>\r
79 ---\r
80  Makefile                              |    2 +-\r
81  parse-time-string/Makefile            |    5 +\r
82  parse-time-string/Makefile.local      |   12 +\r
83  parse-time-string/README              |    9 +\r
84  parse-time-string/parse-time-string.c | 1492 +++++++++++++++++++++++++++++++++\r
85  parse-time-string/parse-time-string.h |  102 +++\r
86  6 files changed, 1621 insertions(+), 1 deletion(-)\r
87  create mode 100644 parse-time-string/Makefile\r
88  create mode 100644 parse-time-string/Makefile.local\r
89  create mode 100644 parse-time-string/README\r
90  create mode 100644 parse-time-string/parse-time-string.c\r
91  create mode 100644 parse-time-string/parse-time-string.h\r
92 \r
93 diff --git a/Makefile b/Makefile\r
94 index e5e2e3a..bb9c316 100644\r
95 --- a/Makefile\r
96 +++ b/Makefile\r
97 @@ -3,7 +3,7 @@\r
98  all:\r
99  \r
100  # List all subdirectories here. Each contains its own Makefile.local\r
101 -subdirs = compat completion emacs lib man util test\r
102 +subdirs = compat completion emacs lib man parse-time-string util test\r
103  \r
104  # We make all targets depend on the Makefiles themselves.\r
105  global_deps = Makefile Makefile.config Makefile.local \\r
106 diff --git a/parse-time-string/Makefile b/parse-time-string/Makefile\r
107 new file mode 100644\r
108 index 0000000..fa25832\r
109 --- /dev/null\r
110 +++ b/parse-time-string/Makefile\r
111 @@ -0,0 +1,5 @@\r
112 +all:\r
113 +       $(MAKE) -C .. all\r
114 +\r
115 +.DEFAULT:\r
116 +       $(MAKE) -C .. $@\r
117 diff --git a/parse-time-string/Makefile.local b/parse-time-string/Makefile.local\r
118 new file mode 100644\r
119 index 0000000..53534f3\r
120 --- /dev/null\r
121 +++ b/parse-time-string/Makefile.local\r
122 @@ -0,0 +1,12 @@\r
123 +dir := parse-time-string\r
124 +extra_cflags += -I$(srcdir)/$(dir)\r
125 +\r
126 +libparse-time-string_c_srcs := $(dir)/parse-time-string.c\r
127 +\r
128 +libparse-time-string_modules := $(libparse-time-string_c_srcs:.c=.o)\r
129 +\r
130 +$(dir)/libparse-time-string.a: $(libparse-time-string_modules)\r
131 +       $(call quiet,AR) rcs $@ $^\r
132 +\r
133 +SRCS := $(SRCS) $(libparse-time-string_c_srcs)\r
134 +CLEAN := $(CLEAN) $(libparse-time-string_modules) $(dir)/libparse-time-string.a\r
135 diff --git a/parse-time-string/README b/parse-time-string/README\r
136 new file mode 100644\r
137 index 0000000..300ff1f\r
138 --- /dev/null\r
139 +++ b/parse-time-string/README\r
140 @@ -0,0 +1,9 @@\r
141 +PARSE TIME STRING\r
142 +=================\r
143 +\r
144 +parse_time_string() is a date/time parser originally written for\r
145 +notmuch by Jani Nikula <jani@nikula.org>. However, there is nothing\r
146 +notmuch specific in it, and it should be kept reusable for other\r
147 +projects, and ready to be packaged on its own as needed. Please do not\r
148 +add dependencies on or references to anything notmuch specific. The\r
149 +parser should only depend on the C library.\r
150 diff --git a/parse-time-string/parse-time-string.c b/parse-time-string/parse-time-string.c\r
151 new file mode 100644\r
152 index 0000000..861a705\r
153 --- /dev/null\r
154 +++ b/parse-time-string/parse-time-string.c\r
155 @@ -0,0 +1,1492 @@\r
156 +/*\r
157 + * parse time string - user friendly date and time parser\r
158 + * Copyright © 2012 Jani Nikula\r
159 + *\r
160 + * This program is free software: you can redistribute it and/or modify\r
161 + * it under the terms of the GNU General Public License as published by\r
162 + * the Free Software Foundation, either version 2 of the License, or\r
163 + * (at your option) any later version.\r
164 + *\r
165 + * This program is distributed in the hope that it will be useful,\r
166 + * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
167 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
168 + * GNU General Public License for more details.\r
169 + *\r
170 + * You should have received a copy of the GNU General Public License\r
171 + * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r
172 + *\r
173 + * Author: Jani Nikula <jani@nikula.org>\r
174 + */\r
175 +\r
176 +#include <assert.h>\r
177 +#include <ctype.h>\r
178 +#include <errno.h>\r
179 +#include <limits.h>\r
180 +#include <stdio.h>\r
181 +#include <stdarg.h>\r
182 +#include <stdbool.h>\r
183 +#include <stdlib.h>\r
184 +#include <string.h>\r
185 +#include <strings.h>\r
186 +#include <time.h>\r
187 +#include <sys/time.h>\r
188 +#include <sys/types.h>\r
189 +\r
190 +#include "parse-time-string.h"\r
191 +\r
192 +/*\r
193 + * IMPLEMENTATION DETAILS\r
194 + *\r
195 + * At a high level, the parsing is done in two phases: 1) actual\r
196 + * parsing of the input string and storing the parsed data into\r
197 + * 'struct state', and 2) processing of the data in 'struct state'\r
198 + * according to current time (or provided reference time) and\r
199 + * rounding. This is evident in the main entry point function\r
200 + * parse_time_string().\r
201 + *\r
202 + * 1) The parsing phase - parse_input()\r
203 + *\r
204 + * Parsing is greedy and happens from left to right. The parsing is as\r
205 + * unambiguous as possible; only unambiguous date/time formats are\r
206 + * accepted. Redundant or contradictory absolute date/time in the\r
207 + * input (e.g. date specified multiple times/ways) is not\r
208 + * accepted. Relative date/time on the other hand just accumulates if\r
209 + * present multiple times (e.g. "5 days 5 days" just turns into 10\r
210 + * days).\r
211 + *\r
212 + * Parsing decisions are made on the input format, not value. For\r
213 + * example, "20/5/2005" fails because the recognized format here is\r
214 + * MM/D/YYYY, even though the values would suggest DD/M/YYYY.\r
215 + *\r
216 + * Parsing is mostly stateless in the sense that parsing decisions are\r
217 + * not made based on the values of previously parsed data, or whether\r
218 + * certain data is present in the first place. (There are a few\r
219 + * exceptions to the latter part, though, such as parsing of time zone\r
220 + * that would otherwise look like plain time.)\r
221 + *\r
222 + * When the parser encounters a number that is not greedily parsed as\r
223 + * part of a format, the interpretation is postponed until the next\r
224 + * token is parsed. The parser for the next token may consume the\r
225 + * previously postponed number. For example, when parsing "20 May" the\r
226 + * meaning of "20" is not known until "May" is parsed. If the parser\r
227 + * for the next token does not consume the postponed number, the\r
228 + * number is handled as a "lone" number before parser for the next\r
229 + * token finishes.\r
230 + *\r
231 + * 2) The processing phase - create_output()\r
232 + *\r
233 + * Once the parser in phase 1 has finished, 'struct state' contains\r
234 + * all the information from the input string, and it's no longer\r
235 + * needed. Since the parser does not even handle the concept of "now",\r
236 + * the processing initializes the fields referring to the current\r
237 + * date/time.\r
238 + *\r
239 + * If requested, the result is rounded towards past or future. The\r
240 + * idea behind rounding is to support parsing date/time ranges in an\r
241 + * obvious way. For example, for a range defined as two dates (without\r
242 + * time), one would typically want to have an inclusive range from the\r
243 + * beginning of start date to the end of the end date. The caller\r
244 + * would use rounding towards past in the start date, and towards\r
245 + * future in the end date.\r
246 + *\r
247 + * The absolute date and time is shifted by the relative date and\r
248 + * time, and time zone adjustments are made. Daylight saving time\r
249 + * (DST) is specifically *not* handled at all.\r
250 + *\r
251 + * Finally, the result is stored to time_t.\r
252 + */\r
253 +\r
254 +#define unused(x) x __attribute__ ((unused))\r
255 +\r
256 +/* XXX: Redefine these to add i18n support. The keyword table uses\r
257 + * N_() to mark strings to be translated; they are accessed\r
258 + * dynamically using _(). */\r
259 +#define _(s) (s)       /* i18n: define as gettext (s) */\r
260 +#define N_(s) (s)      /* i18n: define as gettext_noop (s) */\r
261 +\r
262 +#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0]))\r
263 +\r
264 +/*\r
265 + * Field indices in the tm and set arrays of struct state.\r
266 + *\r
267 + * NOTE: There's some code that depends on the ordering of this enum.\r
268 + */\r
269 +enum field {\r
270 +    /* Keep SEC...YEAR in this order. */\r
271 +    TM_ABS_SEC,                /* seconds */\r
272 +    TM_ABS_MIN,                /* minutes */\r
273 +    TM_ABS_HOUR,       /* hours */\r
274 +    TM_ABS_MDAY,       /* day of the month */\r
275 +    TM_ABS_MON,                /* month */\r
276 +    TM_ABS_YEAR,       /* year */\r
277 +\r
278 +    TM_ABS_WDAY,       /* day of the week. special: may be relative */\r
279 +    TM_ABS_ISDST,      /* daylight saving time */\r
280 +\r
281 +    TM_AMPM,           /* am vs. pm */\r
282 +    TM_TZ,             /* timezone in minutes */\r
283 +\r
284 +    /* Keep SEC...YEAR in this order. */\r
285 +    TM_REL_SEC,                /* seconds relative to absolute or reference time */\r
286 +    TM_REL_MIN,                /* minutes ... */\r
287 +    TM_REL_HOUR,       /* hours ... */\r
288 +    TM_REL_DAY,                /* days ... */\r
289 +    TM_REL_MON,                /* months ... */\r
290 +    TM_REL_YEAR,       /* years ... */\r
291 +    TM_REL_WEEK,       /* weeks ... */\r
292 +\r
293 +    TM_NONE,           /* not a field */\r
294 +\r
295 +    TM_SIZE = TM_NONE,\r
296 +    TM_FIRST_ABS = TM_ABS_SEC,\r
297 +    TM_FIRST_REL = TM_REL_SEC,\r
298 +};\r
299 +\r
300 +/* Values for the set array of struct state. */\r
301 +enum field_set {\r
302 +    FIELD_UNSET,       /* The field has not been touched by parser. */\r
303 +    FIELD_SET,         /* The field has been set by parser. */\r
304 +    FIELD_NOW,         /* The field will be set to reference time. */\r
305 +};\r
306 +\r
307 +static enum field\r
308 +next_abs_field (enum field field)\r
309 +{\r
310 +    /* NOTE: Depends on the enum ordering. */\r
311 +    return field < TM_ABS_YEAR ? field + 1 : TM_NONE;\r
312 +}\r
313 +\r
314 +static enum field\r
315 +abs_to_rel_field (enum field field)\r
316 +{\r
317 +    assert (field <= TM_ABS_YEAR);\r
318 +\r
319 +    /* NOTE: Depends on the enum ordering. */\r
320 +    return field + (TM_FIRST_REL - TM_FIRST_ABS);\r
321 +}\r
322 +\r
323 +/* Get epoch value for field. */\r
324 +static int\r
325 +field_epoch (enum field field)\r
326 +{\r
327 +    if (field == TM_ABS_MDAY || field == TM_ABS_MON)\r
328 +       return 1;\r
329 +    else if (field == TM_ABS_YEAR)\r
330 +       return 1970;\r
331 +    else\r
332 +       return 0;\r
333 +}\r
334 +\r
335 +/* The parsing state. */\r
336 +struct state {\r
337 +    int tm[TM_SIZE];                   /* parsed date and time */\r
338 +    enum field_set set[TM_SIZE];       /* set status of tm */\r
339 +\r
340 +    enum field last_field;     /* Previously set field. */\r
341 +    enum field next_field;     /* Next field for parse_postponed_number() */\r
342 +    char delim;\r
343 +\r
344 +    int postponed_length;      /* Number of digits in postponed value. */\r
345 +    int postponed_value;\r
346 +    char postponed_delim;      /* The delimiter preceding postponed number. */\r
347 +};\r
348 +\r
349 +/*\r
350 + * Helpers for postponed numbers.\r
351 + *\r
352 + * postponed_length is the number of digits in postponed value. 0\r
353 + * means there is no postponed number. -1 means there is a postponed\r
354 + * number, but it comes from a keyword, and it doesn't have digits.\r
355 + */\r
356 +static int\r
357 +get_postponed_length (struct state *state)\r
358 +{\r
359 +    return state->postponed_length;\r
360 +}\r
361 +\r
362 +/*\r
363 + * Consume a previously postponed number. Return true if a number was\r
364 + * in fact postponed, false otherwise. Store the postponed number's\r
365 + * value in *v, length in the input string in *n (or -1 if the number\r
366 + * was written out and parsed as a keyword), and the preceding\r
367 + * delimiter to *d.\r
368 + */\r
369 +static bool\r
370 +get_postponed_number (struct state *state, int *v, int *n, char *d)\r
371 +{\r
372 +    if (!state->postponed_length)\r
373 +       return false;\r
374 +\r
375 +    if (n)\r
376 +       *n = state->postponed_length;\r
377 +\r
378 +    if (v)\r
379 +       *v = state->postponed_value;\r
380 +\r
381 +    if (d)\r
382 +       *d = state->postponed_delim;\r
383 +\r
384 +    state->postponed_length = 0;\r
385 +    state->postponed_value = 0;\r
386 +    state->postponed_delim = 0;\r
387 +\r
388 +    return true;\r
389 +}\r
390 +\r
391 +/* Parse a previously postponed number if one exists. */\r
392 +static int parse_postponed_number (struct state *state, int v, int n, char d);\r
393 +static int\r
394 +handle_postponed_number (struct state *state, enum field next_field)\r
395 +{\r
396 +    int v = state->postponed_value;\r
397 +    int n = state->postponed_length;\r
398 +    char d = state->postponed_delim;\r
399 +    int r;\r
400 +\r
401 +    if (!n)\r
402 +       return 0;\r
403 +\r
404 +    state->postponed_value = 0;\r
405 +    state->postponed_length = 0;\r
406 +    state->postponed_delim = 0;\r
407 +\r
408 +    state->next_field = next_field;\r
409 +    r = parse_postponed_number (state, v, n, d);\r
410 +    state->next_field = TM_NONE;\r
411 +\r
412 +    return r;\r
413 +}\r
414 +\r
415 +/*\r
416 + * Postpone a number to be handled later. If one exists already,\r
417 + * handle it first. n may be -1 to indicate a keyword that has no\r
418 + * number length.\r
419 + */\r
420 +static int\r
421 +set_postponed_number (struct state *state, int v, int n)\r
422 +{\r
423 +    int r;\r
424 +    char d = state->delim;\r
425 +\r
426 +    /* Parse a previously postponed number, if any. */\r
427 +    r = handle_postponed_number (state, TM_NONE);\r
428 +    if (r)\r
429 +       return r;\r
430 +\r
431 +    state->postponed_length = n;\r
432 +    state->postponed_value = v;\r
433 +    state->postponed_delim = d;\r
434 +\r
435 +    return 0;\r
436 +}\r
437 +\r
438 +static void\r
439 +set_delim (struct state *state, char delim)\r
440 +{\r
441 +    state->delim = delim;\r
442 +}\r
443 +\r
444 +static void\r
445 +unset_delim (struct state *state)\r
446 +{\r
447 +    state->delim = 0;\r
448 +}\r
449 +\r
450 +/*\r
451 + * Field set/get/mod helpers.\r
452 + */\r
453 +\r
454 +/* Return true if field has been set. */\r
455 +static bool\r
456 +is_field_set (struct state *state, enum field field)\r
457 +{\r
458 +    assert (field < ARRAY_SIZE (state->tm));\r
459 +\r
460 +    return field < ARRAY_SIZE (state->set) &&\r
461 +          state->set[field] != FIELD_UNSET;\r
462 +}\r
463 +\r
464 +static void\r
465 +unset_field (struct state *state, enum field field)\r
466 +{\r
467 +    assert (field < ARRAY_SIZE (state->tm));\r
468 +\r
469 +    state->set[field] = FIELD_UNSET;\r
470 +    state->tm[field] = 0;\r
471 +}\r
472 +\r
473 +/*\r
474 + * Set field to value. A field can only be set once to ensure the\r
475 + * input does not contain redundant and potentially conflicting data.\r
476 + */\r
477 +static int\r
478 +set_field (struct state *state, enum field field, int value)\r
479 +{\r
480 +    int r;\r
481 +\r
482 +    assert (field < ARRAY_SIZE (state->tm));\r
483 +\r
484 +    /* Fields can only be set once. */\r
485 +    if (field < ARRAY_SIZE (state->set) && state->set[field] != FIELD_UNSET)\r
486 +       return -PARSE_TIME_ERR_ALREADYSET;\r
487 +\r
488 +    state->set[field] = FIELD_SET;\r
489 +\r
490 +    /* Parse postponed number, if any. */\r
491 +    r = handle_postponed_number (state, field);\r
492 +    if (r)\r
493 +       return r;\r
494 +\r
495 +    unset_delim (state);\r
496 +\r
497 +    state->tm[field] = value;\r
498 +    state->last_field = field;\r
499 +\r
500 +    return 0;\r
501 +}\r
502 +\r
503 +/*\r
504 + * Mark n fields in fields to be set to the reference date/time in the\r
505 + * specified time zone, or local timezone if not specified. The fields\r
506 + * will be initialized after parsing is complete and timezone is\r
507 + * known.\r
508 + */\r
509 +static int\r
510 +set_fields_to_now (struct state *state, enum field *fields, size_t n)\r
511 +{\r
512 +    size_t i;\r
513 +    int r;\r
514 +\r
515 +    for (i = 0; i < n; i++) {\r
516 +       r = set_field (state, fields[i], 0);\r
517 +       if (r)\r
518 +           return r;\r
519 +       state->set[fields[i]] = FIELD_NOW;\r
520 +    }\r
521 +\r
522 +    return 0;\r
523 +}\r
524 +\r
525 +/* Modify field by adding value to it. To be used on relative fields,\r
526 + * which can be modified multiple times (to accumulate). */\r
527 +static int\r
528 +mod_field (struct state *state, enum field field, int value)\r
529 +{\r
530 +    int r;\r
531 +\r
532 +    assert (field < ARRAY_SIZE (state->tm));   /* assert relative??? */\r
533 +\r
534 +    if (field < ARRAY_SIZE (state->set))\r
535 +       state->set[field] = FIELD_SET;\r
536 +\r
537 +    /* Parse postponed number, if any. */\r
538 +    r = handle_postponed_number (state, field);\r
539 +    if (r)\r
540 +       return r;\r
541 +\r
542 +    unset_delim (state);\r
543 +\r
544 +    state->tm[field] += value;\r
545 +    state->last_field = field;\r
546 +\r
547 +    return 0;\r
548 +}\r
549 +\r
550 +/*\r
551 + * Get field value. Make sure the field is set before query. It's most\r
552 + * likely an error to call this while parsing (for example fields set\r
553 + * as FIELD_NOW will only be set to some value after parsing).\r
554 + */\r
555 +static int\r
556 +get_field (struct state *state, enum field field)\r
557 +{\r
558 +    assert (field < ARRAY_SIZE (state->tm));\r
559 +\r
560 +    return state->tm[field];\r
561 +}\r
562 +\r
563 +/*\r
564 + * Validity checkers.\r
565 + */\r
566 +static bool is_valid_12hour (int h)\r
567 +{\r
568 +    return h >= 0 && h <= 12;\r
569 +}\r
570 +\r
571 +static bool is_valid_time (int h, int m, int s)\r
572 +{\r
573 +    /* Allow 24:00:00 to denote end of day. */\r
574 +    if (h == 24 && m == 0 && s == 0)\r
575 +       return true;\r
576 +\r
577 +    return h >= 0 && h <= 23 && m >= 0 && m <= 59 && s >= 0 && s <= 59;\r
578 +}\r
579 +\r
580 +static bool is_valid_mday (int mday)\r
581 +{\r
582 +    return mday >= 1 && mday <= 31;\r
583 +}\r
584 +\r
585 +static bool is_valid_mon (int mon)\r
586 +{\r
587 +    return mon >= 1 && mon <= 12;\r
588 +}\r
589 +\r
590 +static bool is_valid_year (int year)\r
591 +{\r
592 +    return year >= 1970;\r
593 +}\r
594 +\r
595 +static bool is_valid_date (int year, int mon, int mday)\r
596 +{\r
597 +    return is_valid_year (year) && is_valid_mon (mon) && is_valid_mday (mday);\r
598 +}\r
599 +\r
600 +/* Unset indicator for time and date set helpers. */\r
601 +#define UNSET -1\r
602 +\r
603 +/* Time set helper. No input checking. Use UNSET (-1) to leave unset. */\r
604 +static int\r
605 +set_abs_time (struct state *state, int hour, int min, int sec)\r
606 +{\r
607 +    int r;\r
608 +\r
609 +    if (hour != UNSET) {\r
610 +       if ((r = set_field (state, TM_ABS_HOUR, hour)))\r
611 +           return r;\r
612 +    }\r
613 +\r
614 +    if (min != UNSET) {\r
615 +       if ((r = set_field (state, TM_ABS_MIN, min)))\r
616 +           return r;\r
617 +    }\r
618 +\r
619 +    if (sec != UNSET) {\r
620 +       if ((r = set_field (state, TM_ABS_SEC, sec)))\r
621 +           return r;\r
622 +    }\r
623 +\r
624 +    return 0;\r
625 +}\r
626 +\r
627 +/* Date set helper. No input checking. Use UNSET (-1) to leave unset. */\r
628 +static int\r
629 +set_abs_date (struct state *state, int year, int mon, int mday)\r
630 +{\r
631 +    int r;\r
632 +\r
633 +    if (year != UNSET) {\r
634 +       if ((r = set_field (state, TM_ABS_YEAR, year)))\r
635 +           return r;\r
636 +    }\r
637 +\r
638 +    if (mon != UNSET) {\r
639 +       if ((r = set_field (state, TM_ABS_MON, mon)))\r
640 +           return r;\r
641 +    }\r
642 +\r
643 +    if (mday != UNSET) {\r
644 +       if ((r = set_field (state, TM_ABS_MDAY, mday)))\r
645 +           return r;\r
646 +    }\r
647 +\r
648 +    return 0;\r
649 +}\r
650 +\r
651 +/*\r
652 + * Keyword parsing and handling.\r
653 + */\r
654 +struct keyword;\r
655 +typedef int (*setter_t)(struct state *state, struct keyword *kw);\r
656 +\r
657 +struct keyword {\r
658 +    const char *name;  /* keyword */\r
659 +    enum field field;  /* field to set, or FIELD_NONE if N/A */\r
660 +    int value;         /* value to set, or 0 if N/A */\r
661 +    setter_t set;      /* function to use for setting, if non-NULL */\r
662 +};\r
663 +\r
664 +/*\r
665 + * Setter callback functions for keywords.\r
666 + */\r
667 +static int\r
668 +kw_set_default (struct state *state, struct keyword *kw)\r
669 +{\r
670 +    return set_field (state, kw->field, kw->value);\r
671 +}\r
672 +\r
673 +static int\r
674 +kw_set_rel (struct state *state, struct keyword *kw)\r
675 +{\r
676 +    int multiplier = 1;\r
677 +\r
678 +    /* Get a previously set multiplier, if any. */\r
679 +    get_postponed_number (state, &multiplier, NULL, NULL);\r
680 +\r
681 +    /* Accumulate relative field values. */\r
682 +    return mod_field (state, kw->field, multiplier * kw->value);\r
683 +}\r
684 +\r
685 +static int\r
686 +kw_set_number (struct state *state, struct keyword *kw)\r
687 +{\r
688 +    /* -1 = no length, from keyword. */\r
689 +    return set_postponed_number (state, kw->value, -1);\r
690 +}\r
691 +\r
692 +static int\r
693 +kw_set_month (struct state *state, struct keyword *kw)\r
694 +{\r
695 +    int n = get_postponed_length (state);\r
696 +\r
697 +    /* Consume postponed number if it could be mday. This handles "20\r
698 +     * January". */\r
699 +    if (n == 1 || n == 2) {\r
700 +       int r, v;\r
701 +\r
702 +       get_postponed_number (state, &v, NULL, NULL);\r
703 +\r
704 +       if (!is_valid_mday (v))\r
705 +           return -PARSE_TIME_ERR_INVALIDDATE;\r
706 +\r
707 +       r = set_field (state, TM_ABS_MDAY, v);\r
708 +       if (r)\r
709 +           return r;\r
710 +    }\r
711 +\r
712 +    return set_field (state, kw->field, kw->value);\r
713 +}\r
714 +\r
715 +static int\r
716 +kw_set_ampm (struct state *state, struct keyword *kw)\r
717 +{\r
718 +    int n = get_postponed_length (state);\r
719 +\r
720 +    /* Consume postponed number if it could be hour. This handles\r
721 +     * "5pm". */\r
722 +    if (n == 1 || n == 2) {\r
723 +       int r, v;\r
724 +\r
725 +       get_postponed_number (state, &v, NULL, NULL);\r
726 +\r
727 +       if (!is_valid_12hour (v))\r
728 +           return -PARSE_TIME_ERR_INVALIDTIME;\r
729 +\r
730 +       r = set_abs_time (state, v, 0, 0);\r
731 +       if (r)\r
732 +           return r;\r
733 +    }\r
734 +\r
735 +    return set_field (state, kw->field, kw->value);\r
736 +}\r
737 +\r
738 +static int\r
739 +kw_set_timeofday (struct state *state, struct keyword *kw)\r
740 +{\r
741 +    return set_abs_time (state, kw->value, 0, 0);\r
742 +}\r
743 +\r
744 +static int\r
745 +kw_set_today (struct state *state, unused (struct keyword *kw))\r
746 +{\r
747 +    enum field fields[] = { TM_ABS_YEAR, TM_ABS_MON, TM_ABS_MDAY };\r
748 +\r
749 +    return set_fields_to_now (state, fields, ARRAY_SIZE (fields));\r
750 +}\r
751 +\r
752 +static int\r
753 +kw_set_now (struct state *state, unused (struct keyword *kw))\r
754 +{\r
755 +    enum field fields[] = { TM_ABS_HOUR, TM_ABS_MIN, TM_ABS_SEC };\r
756 +\r
757 +    return set_fields_to_now (state, fields, ARRAY_SIZE (fields));\r
758 +}\r
759 +\r
760 +static int\r
761 +kw_set_ordinal (struct state *state, struct keyword *kw)\r
762 +{\r
763 +    int n, v;\r
764 +\r
765 +    /* Require a postponed number. */\r
766 +    if (!get_postponed_number (state, &v, &n, NULL))\r
767 +       return -PARSE_TIME_ERR_DATEFORMAT;\r
768 +\r
769 +    /* Ordinals are mday. */\r
770 +    if (n != 1 && n != 2)\r
771 +       return -PARSE_TIME_ERR_DATEFORMAT;\r
772 +\r
773 +    /* Be strict about st, nd, rd, and lax about th. */\r
774 +    if (strcasecmp (kw->name, "st") == 0 && v != 1 && v != 21 && v != 31)\r
775 +       return -PARSE_TIME_ERR_INVALIDDATE;\r
776 +    else if (strcasecmp (kw->name, "nd") == 0 && v != 2 && v != 22)\r
777 +       return -PARSE_TIME_ERR_INVALIDDATE;\r
778 +    else if (strcasecmp (kw->name, "rd") == 0 && v != 3 && v != 23)\r
779 +       return -PARSE_TIME_ERR_INVALIDDATE;\r
780 +    else if (strcasecmp (kw->name, "th") == 0 && !is_valid_mday (v))\r
781 +       return -PARSE_TIME_ERR_INVALIDDATE;\r
782 +\r
783 +    return set_field (state, TM_ABS_MDAY, v);\r
784 +}\r
785 +\r
786 +/*\r
787 + * Accepted keywords.\r
788 + *\r
789 + * A keyword may optionally contain a '|' to indicate the minimum\r
790 + * match length. Without one, full match is required. It's advisable\r
791 + * to keep the minimum match parts unique across all keywords.\r
792 + *\r
793 + * If keyword begins with upper case letter, then the matching will be\r
794 + * case sensitive. Otherwise the matching is case insensitive.\r
795 + *\r
796 + * If setter is NULL, set_default will be used.\r
797 + *\r
798 + * Note: Order matters. Matching is greedy, longest match is used, but\r
799 + * of equal length matches the first one is used, unless there's an\r
800 + * equal length case sensitive match which trumps case insensitive\r
801 + * matches.\r
802 + */\r
803 +static struct keyword keywords[] = {\r
804 +    /* Weekdays. */\r
805 +    { N_("sun|day"),   TM_ABS_WDAY,    0,      NULL },\r
806 +    { N_("mon|day"),   TM_ABS_WDAY,    1,      NULL },\r
807 +    { N_("tue|sday"),  TM_ABS_WDAY,    2,      NULL },\r
808 +    { N_("wed|nesday"),        TM_ABS_WDAY,    3,      NULL },\r
809 +    { N_("thu|rsday"), TM_ABS_WDAY,    4,      NULL },\r
810 +    { N_("fri|day"),   TM_ABS_WDAY,    5,      NULL },\r
811 +    { N_("sat|urday"), TM_ABS_WDAY,    6,      NULL },\r
812 +\r
813 +    /* Months. */\r
814 +    { N_("jan|uary"),  TM_ABS_MON,     1,      kw_set_month },\r
815 +    { N_("feb|ruary"), TM_ABS_MON,     2,      kw_set_month },\r
816 +    { N_("mar|ch"),    TM_ABS_MON,     3,      kw_set_month },\r
817 +    { N_("apr|il"),    TM_ABS_MON,     4,      kw_set_month },\r
818 +    { N_("may"),       TM_ABS_MON,     5,      kw_set_month },\r
819 +    { N_("jun|e"),     TM_ABS_MON,     6,      kw_set_month },\r
820 +    { N_("jul|y"),     TM_ABS_MON,     7,      kw_set_month },\r
821 +    { N_("aug|ust"),   TM_ABS_MON,     8,      kw_set_month },\r
822 +    { N_("sep|tember"),        TM_ABS_MON,     9,      kw_set_month },\r
823 +    { N_("oct|ober"),  TM_ABS_MON,     10,     kw_set_month },\r
824 +    { N_("nov|ember"), TM_ABS_MON,     11,     kw_set_month },\r
825 +    { N_("dec|ember"), TM_ABS_MON,     12,     kw_set_month },\r
826 +\r
827 +    /* Durations. */\r
828 +    { N_("y|ears"),    TM_REL_YEAR,    1,      kw_set_rel },\r
829 +    { N_("w|eeks"),    TM_REL_WEEK,    1,      kw_set_rel },\r
830 +    { N_("d|ays"),     TM_REL_DAY,     1,      kw_set_rel },\r
831 +    { N_("h|ours"),    TM_REL_HOUR,    1,      kw_set_rel },\r
832 +    { N_("hr|s"),      TM_REL_HOUR,    1,      kw_set_rel },\r
833 +    { N_("m|inutes"),  TM_REL_MIN,     1,      kw_set_rel },\r
834 +    /* M=months, m=minutes */\r
835 +    { N_("M"),         TM_REL_MON,     1,      kw_set_rel },\r
836 +    { N_("mins"),      TM_REL_MIN,     1,      kw_set_rel },\r
837 +    { N_("mo|nths"),   TM_REL_MON,     1,      kw_set_rel },\r
838 +    { N_("s|econds"),  TM_REL_SEC,     1,      kw_set_rel },\r
839 +    { N_("secs"),      TM_REL_SEC,     1,      kw_set_rel },\r
840 +\r
841 +    /* Numbers. */\r
842 +    { N_("one"),       TM_NONE,        1,      kw_set_number },\r
843 +    { N_("two"),       TM_NONE,        2,      kw_set_number },\r
844 +    { N_("three"),     TM_NONE,        3,      kw_set_number },\r
845 +    { N_("four"),      TM_NONE,        4,      kw_set_number },\r
846 +    { N_("five"),      TM_NONE,        5,      kw_set_number },\r
847 +    { N_("six"),       TM_NONE,        6,      kw_set_number },\r
848 +    { N_("seven"),     TM_NONE,        7,      kw_set_number },\r
849 +    { N_("eight"),     TM_NONE,        8,      kw_set_number },\r
850 +    { N_("nine"),      TM_NONE,        9,      kw_set_number },\r
851 +    { N_("ten"),       TM_NONE,        10,     kw_set_number },\r
852 +    { N_("dozen"),     TM_NONE,        12,     kw_set_number },\r
853 +    { N_("hundred"),   TM_NONE,        100,    kw_set_number },\r
854 +\r
855 +    /* Special number forms. */\r
856 +    { N_("this"),      TM_NONE,        0,      kw_set_number },\r
857 +    { N_("last"),      TM_NONE,        1,      kw_set_number },\r
858 +\r
859 +    /* Other special keywords. */\r
860 +    { N_("yesterday"), TM_REL_DAY,     1,      kw_set_rel },\r
861 +    { N_("today"),     TM_NONE,        0,      kw_set_today },\r
862 +    { N_("now"),       TM_NONE,        0,      kw_set_now },\r
863 +    { N_("noon"),      TM_NONE,        12,     kw_set_timeofday },\r
864 +    { N_("midnight"),  TM_NONE,        0,      kw_set_timeofday },\r
865 +    { N_("am"),                TM_AMPM,        0,      kw_set_ampm },\r
866 +    { N_("a.m."),      TM_AMPM,        0,      kw_set_ampm },\r
867 +    { N_("pm"),                TM_AMPM,        1,      kw_set_ampm },\r
868 +    { N_("p.m."),      TM_AMPM,        1,      kw_set_ampm },\r
869 +    { N_("st"),                TM_NONE,        0,      kw_set_ordinal },\r
870 +    { N_("nd"),                TM_NONE,        0,      kw_set_ordinal },\r
871 +    { N_("rd"),                TM_NONE,        0,      kw_set_ordinal },\r
872 +    { N_("th"),                TM_NONE,        0,      kw_set_ordinal },\r
873 +\r
874 +    /* Timezone codes: offset in minutes. XXX: Add more codes. */\r
875 +    { N_("pst"),       TM_TZ,          -8*60,  NULL },\r
876 +    { N_("mst"),       TM_TZ,          -7*60,  NULL },\r
877 +    { N_("cst"),       TM_TZ,          -6*60,  NULL },\r
878 +    { N_("est"),       TM_TZ,          -5*60,  NULL },\r
879 +    { N_("ast"),       TM_TZ,          -4*60,  NULL },\r
880 +    { N_("nst"),       TM_TZ,          -(3*60+30),     NULL },\r
881 +\r
882 +    { N_("gmt"),       TM_TZ,          0,      NULL },\r
883 +    { N_("utc"),       TM_TZ,          0,      NULL },\r
884 +\r
885 +    { N_("wet"),       TM_TZ,          0,      NULL },\r
886 +    { N_("cet"),       TM_TZ,          1*60,   NULL },\r
887 +    { N_("eet"),       TM_TZ,          2*60,   NULL },\r
888 +    { N_("fet"),       TM_TZ,          3*60,   NULL },\r
889 +\r
890 +    { N_("wat"),       TM_TZ,          1*60,   NULL },\r
891 +    { N_("cat"),       TM_TZ,          2*60,   NULL },\r
892 +    { N_("eat"),       TM_TZ,          3*60,   NULL },\r
893 +};\r
894 +\r
895 +/*\r
896 + * Compare strings s and keyword. Return number of matching chars on\r
897 + * match, 0 for no match. Match must be at least n chars, or all of\r
898 + * keyword if n < 0, otherwise it's not a match. Use match_case for\r
899 + * case sensitive matching.\r
900 + */\r
901 +static size_t\r
902 +stringcmp (const char *s, const char *keyword, ssize_t n, bool match_case)\r
903 +{\r
904 +    ssize_t i;\r
905 +\r
906 +    if (!n)\r
907 +       return 0;\r
908 +\r
909 +    for (i = 0; *s && *keyword; i++, s++, keyword++) {\r
910 +       if (match_case) {\r
911 +           if (*s != *keyword)\r
912 +               break;\r
913 +       } else {\r
914 +           if (tolower ((unsigned char) *s) !=\r
915 +               tolower ((unsigned char) *keyword))\r
916 +               break;\r
917 +       }\r
918 +    }\r
919 +\r
920 +    if (n > 0)\r
921 +       return i < n ? 0 : i;\r
922 +    else\r
923 +       return *keyword ? 0 : i;\r
924 +}\r
925 +\r
926 +/*\r
927 + * Parse a keyword. Return < 0 on error, number of parsed chars on\r
928 + * success.\r
929 + */\r
930 +static ssize_t\r
931 +parse_keyword (struct state *state, const char *s)\r
932 +{\r
933 +    unsigned int i;\r
934 +    size_t n, max_n = 0;\r
935 +    struct keyword *kw = NULL;\r
936 +    int r;\r
937 +\r
938 +    /* Match longest keyword */\r
939 +    for (i = 0; i < ARRAY_SIZE (keywords); i++) {\r
940 +       /* Match case if keyword begins with upper case letter. */\r
941 +       bool mcase = isupper ((unsigned char) keywords[i].name[0]);\r
942 +       ssize_t minlen = -1;\r
943 +       char keyword[128];\r
944 +       char *p;\r
945 +\r
946 +       strncpy (keyword, _(keywords[i].name), sizeof (keyword));\r
947 +\r
948 +       /* Truncate too long keywords. XXX: Make this dynamic? */\r
949 +       keyword[sizeof (keyword) - 1] = '\0';\r
950 +\r
951 +       /* Minimum match length. */\r
952 +       p = strchr (keyword, '|');\r
953 +       if (p) {\r
954 +           minlen = p - keyword;\r
955 +\r
956 +           /* Remove the minimum match length separator. */\r
957 +           memmove (p, p + 1, strlen (p + 1) + 1);\r
958 +       }\r
959 +\r
960 +       n = stringcmp (s, keyword, minlen, mcase);\r
961 +       if (n > max_n || (n == max_n && mcase)) {\r
962 +           max_n = n;\r
963 +           kw = &keywords[i];\r
964 +       }\r
965 +    }\r
966 +\r
967 +    if (!kw)\r
968 +       return -PARSE_TIME_ERR_KEYWORD;\r
969 +\r
970 +    if (kw->set)\r
971 +       r = kw->set (state, kw);\r
972 +    else\r
973 +       r = kw_set_default (state, kw);\r
974 +\r
975 +    if (r < 0)\r
976 +       return r;\r
977 +\r
978 +    return max_n;\r
979 +}\r
980 +\r
981 +/*\r
982 + * Non-keyword parsers and their helpers.\r
983 + */\r
984 +\r
985 +static int\r
986 +set_user_tz (struct state *state, char sign, int hour, int min)\r
987 +{\r
988 +    int tz = hour * 60 + min;\r
989 +\r
990 +    assert (sign == '+' || sign == '-');\r
991 +\r
992 +    if (hour < 0 || hour > 14 || min < 0 || min > 59 || min % 15)\r
993 +       return -PARSE_TIME_ERR_INVALIDTIME;\r
994 +\r
995 +    if (sign == '-')\r
996 +       tz = -tz;\r
997 +\r
998 +    return set_field (state, TM_TZ, tz);\r
999 +}\r
1000 +\r
1001 +/*\r
1002 + * Independent parsing of a postponed number when it wasn't consumed\r
1003 + * during parsing of the following token.\r
1004 + */\r
1005 +static int\r
1006 +parse_postponed_number (struct state *state, int v, int n, char d)\r
1007 +{\r
1008 +    if (n == 1 || n == 2) {\r
1009 +       /* Notable exception: Previous field affects parsing. This\r
1010 +        * handles "January 20". */\r
1011 +       if (state->last_field == TM_ABS_MON) {\r
1012 +           /* D[D] */\r
1013 +           if (!is_valid_mday (v))\r
1014 +               return -PARSE_TIME_ERR_INVALIDDATE;\r
1015 +\r
1016 +           return set_field (state, TM_ABS_MDAY, v);\r
1017 +       } else if (n == 2) {\r
1018 +           /* XXX: Only allow if last field is hour, min, or sec? */\r
1019 +           if (d == '+' || d == '-') {\r
1020 +               /* +/-HH */\r
1021 +               return set_user_tz (state, d, v, 0);\r
1022 +           }\r
1023 +       }\r
1024 +    } else if (n == 4) {\r
1025 +       /* Notable exception: Value affects parsing. Time zones are\r
1026 +        * always at most 1400 and we don't understand years before\r
1027 +        * 1970. */\r
1028 +       if (!is_valid_year (v)) {\r
1029 +           if (d == '+' || d == '-') {\r
1030 +               /* +/-HHMM */\r
1031 +               return set_user_tz (state, d, v / 100, v % 100);\r
1032 +           }\r
1033 +       } else {\r
1034 +           /* YYYY */\r
1035 +           return set_field (state, TM_ABS_YEAR, v);\r
1036 +       }\r
1037 +    } else if (n == 6) {\r
1038 +       /* HHMMSS */\r
1039 +       int hour = v / 10000;\r
1040 +       int min = (v / 100) % 100;\r
1041 +       int sec = v % 100;\r
1042 +\r
1043 +       if (!is_valid_time (hour, min, sec))\r
1044 +           return -PARSE_TIME_ERR_INVALIDTIME;\r
1045 +\r
1046 +       return set_abs_time (state, hour, min, sec);\r
1047 +    } else if (n == 8) {\r
1048 +       /* YYYYMMDD */\r
1049 +       int year = v / 10000;\r
1050 +       int mon = (v / 100) % 100;\r
1051 +       int mday = v % 100;\r
1052 +\r
1053 +       if (!is_valid_date (year, mon, mday))\r
1054 +           return -PARSE_TIME_ERR_INVALIDDATE;\r
1055 +\r
1056 +       return set_abs_date (state, year, mon, mday);\r
1057 +    } else {\r
1058 +       return -PARSE_TIME_ERR_FORMAT;\r
1059 +    }\r
1060 +\r
1061 +    return -PARSE_TIME_ERR_FORMAT;\r
1062 +}\r
1063 +\r
1064 +static int tm_get_field (const struct tm *tm, enum field field);\r
1065 +\r
1066 +static int\r
1067 +set_timestamp (struct state *state, time_t t)\r
1068 +{\r
1069 +    struct tm tm;\r
1070 +    enum field f;\r
1071 +    int r;\r
1072 +\r
1073 +    if (gmtime_r (&t, &tm) == NULL)\r
1074 +       return -PARSE_TIME_ERR_LIB;\r
1075 +\r
1076 +    for (f = TM_ABS_SEC; f != TM_NONE; f = next_abs_field (f)) {\r
1077 +       r = set_field (state, f, tm_get_field (&tm, f));\r
1078 +       if (r)\r
1079 +           return r;\r
1080 +    }\r
1081 +\r
1082 +    r = set_field (state, TM_TZ, 0);\r
1083 +    if (r)\r
1084 +       return r;\r
1085 +\r
1086 +    /* XXX: Prevent TM_AMPM with timestamp, e.g. "@123456 pm" */\r
1087 +\r
1088 +    return 0;\r
1089 +}\r
1090 +\r
1091 +/* Parse a single number. Typically postpone parsing until later. */\r
1092 +static int\r
1093 +parse_single_number (struct state *state, unsigned long v,\r
1094 +                    unsigned long n)\r
1095 +{\r
1096 +    assert (n);\r
1097 +\r
1098 +    if (state->delim == '@')\r
1099 +       return set_timestamp (state, (time_t) v);\r
1100 +\r
1101 +    if (v > INT_MAX)\r
1102 +       return -PARSE_TIME_ERR_FORMAT;\r
1103 +\r
1104 +    return set_postponed_number (state, v, n);\r
1105 +}\r
1106 +\r
1107 +static bool\r
1108 +is_time_sep (char c)\r
1109 +{\r
1110 +    return c == ':';\r
1111 +}\r
1112 +\r
1113 +static bool\r
1114 +is_date_sep (char c)\r
1115 +{\r
1116 +    return c == '/' || c == '-' || c == '.';\r
1117 +}\r
1118 +\r
1119 +static bool\r
1120 +is_sep (char c)\r
1121 +{\r
1122 +    return is_time_sep (c) || is_date_sep (c);\r
1123 +}\r
1124 +\r
1125 +/* Two-digit year: 00...69 is 2000s, 70...99 1900s, if n == 0 keep\r
1126 + * unset. */\r
1127 +static int\r
1128 +expand_year (unsigned long year, size_t n)\r
1129 +{\r
1130 +    if (n == 2) {\r
1131 +       return (year < 70 ? 2000 : 1900) + year;\r
1132 +    } else if (n == 4) {\r
1133 +       return year;\r
1134 +    } else {\r
1135 +       return UNSET;\r
1136 +    }\r
1137 +}\r
1138 +\r
1139 +/* Parse a date number triplet. */\r
1140 +static int\r
1141 +parse_date (struct state *state, char sep,\r
1142 +           unsigned long v1, unsigned long v2, unsigned long v3,\r
1143 +           size_t n1, size_t n2, size_t n3)\r
1144 +{\r
1145 +    int year = UNSET, mon = UNSET, mday = UNSET;\r
1146 +\r
1147 +    assert (is_date_sep (sep));\r
1148 +\r
1149 +    switch (sep) {\r
1150 +    case '/': /* Date: M[M]/D[D][/YY[YY]] or M[M]/YYYY */\r
1151 +       if (n1 != 1 && n1 != 2)\r
1152 +           return -PARSE_TIME_ERR_DATEFORMAT;\r
1153 +\r
1154 +       if ((n2 == 1 || n2 == 2) && (n3 == 0 || n3 == 2 || n3 == 4)) {\r
1155 +           /* M[M]/D[D][/YY[YY]] */\r
1156 +           year = expand_year (v3, n3);\r
1157 +           mon = v1;\r
1158 +           mday = v2;\r
1159 +       } else if (n2 == 4 && n3 == 0) {\r
1160 +           /* M[M]/YYYY */\r
1161 +           year = v2;\r
1162 +           mon = v1;\r
1163 +       } else {\r
1164 +           return -PARSE_TIME_ERR_DATEFORMAT;\r
1165 +       }\r
1166 +       break;\r
1167 +\r
1168 +    case '-': /* Date: YYYY-MM[-DD] or DD-MM[-YY[YY]] or MM-YYYY */\r
1169 +       if (n1 == 4 && n2 == 2 && (n3 == 0 || n3 == 2)) {\r
1170 +           /* YYYY-MM[-DD] */\r
1171 +           year = v1;\r
1172 +           mon = v2;\r
1173 +           if (n3)\r
1174 +               mday = v3;\r
1175 +       } else if (n1 == 2 && n2 == 2 && (n3 == 0 || n3 == 2 || n3 == 4)) {\r
1176 +           /* DD-MM[-YY[YY]] */\r
1177 +           year = expand_year (v3, n3);\r
1178 +           mon = v2;\r
1179 +           mday = v1;\r
1180 +       } else if (n1 == 2 && n2 == 4 && n3 == 0) {\r
1181 +           /* MM-YYYY */\r
1182 +           year = v2;\r
1183 +           mon = v1;\r
1184 +       } else {\r
1185 +           return -PARSE_TIME_ERR_DATEFORMAT;\r
1186 +       }\r
1187 +       break;\r
1188 +\r
1189 +    case '.': /* Date: D[D].M[M][.[YY[YY]]] */\r
1190 +       if ((n1 != 1 && n1 != 2) || (n2 != 1 && n2 != 2) ||\r
1191 +           (n3 != 0 && n3 != 2 && n3 != 4))\r
1192 +           return -PARSE_TIME_ERR_DATEFORMAT;\r
1193 +\r
1194 +       year = expand_year (v3, n3);\r
1195 +       mon = v2;\r
1196 +       mday = v1;\r
1197 +       break;\r
1198 +    }\r
1199 +\r
1200 +    if (year != UNSET && !is_valid_year (year))\r
1201 +       return -PARSE_TIME_ERR_INVALIDDATE;\r
1202 +\r
1203 +    if (mon != UNSET && !is_valid_mon (mon))\r
1204 +       return -PARSE_TIME_ERR_INVALIDDATE;\r
1205 +\r
1206 +    if (mday != UNSET && !is_valid_mday (mday))\r
1207 +       return -PARSE_TIME_ERR_INVALIDDATE;\r
1208 +\r
1209 +    return set_abs_date (state, year, mon, mday);\r
1210 +}\r
1211 +\r
1212 +/* Parse a time number triplet. */\r
1213 +static int\r
1214 +parse_time (struct state *state, char sep,\r
1215 +           unsigned long v1, unsigned long v2, unsigned long v3,\r
1216 +           size_t n1, size_t n2, size_t n3)\r
1217 +{\r
1218 +    assert (is_time_sep (sep));\r
1219 +\r
1220 +    if ((n1 != 1 && n1 != 2) || n2 != 2 || (n3 != 0 && n3 != 2))\r
1221 +       return -PARSE_TIME_ERR_TIMEFORMAT;\r
1222 +\r
1223 +    /*\r
1224 +     * Notable exception: Previously set fields affect\r
1225 +     * parsing. Interpret (+|-)HH:MM as time zone only if hour and\r
1226 +     * minute have been set.\r
1227 +     *\r
1228 +     * XXX: This could be fixed by restricting the delimiters\r
1229 +     * preceding time. For '+' it would be justified, but for '-' it\r
1230 +     * might be inconvenient. However prefer to allow '-' as an\r
1231 +     * insignificant delimiter preceding time for convenience, and\r
1232 +     * handle '+' the same way for consistency between positive and\r
1233 +     * negative time zones.\r
1234 +     */\r
1235 +    if (is_field_set (state, TM_ABS_HOUR) &&\r
1236 +       is_field_set (state, TM_ABS_MIN) &&\r
1237 +       n1 == 2 && n2 == 2 && n3 == 0 &&\r
1238 +       (state->delim == '+' || state->delim == '-')) {\r
1239 +       return set_user_tz (state, state->delim, v1, v2);\r
1240 +    }\r
1241 +\r
1242 +    if (!is_valid_time (v1, v2, v3))\r
1243 +       return -PARSE_TIME_ERR_INVALIDTIME;\r
1244 +\r
1245 +    return set_abs_time (state, v1, v2, n3 ? v3 : 0);\r
1246 +}\r
1247 +\r
1248 +/* strtoul helper that assigns length. */\r
1249 +static unsigned long\r
1250 +strtoul_len (const char *s, const char **endp, size_t *len)\r
1251 +{\r
1252 +    unsigned long val = strtoul (s, (char **) endp, 10);\r
1253 +\r
1254 +    *len = *endp - s;\r
1255 +    return val;\r
1256 +}\r
1257 +\r
1258 +/*\r
1259 + * Parse a (group of) number(s). Return < 0 on error, number of parsed\r
1260 + * chars on success.\r
1261 + */\r
1262 +static ssize_t\r
1263 +parse_number (struct state *state, const char *s)\r
1264 +{\r
1265 +    int r;\r
1266 +    unsigned long v1, v2, v3 = 0;\r
1267 +    size_t n1, n2, n3 = 0;\r
1268 +    const char *p = s;\r
1269 +    char sep;\r
1270 +\r
1271 +    v1 = strtoul_len (p, &p, &n1);\r
1272 +\r
1273 +    if (is_sep (*p) && isdigit ((unsigned char) *(p + 1))) {\r
1274 +       sep = *p;\r
1275 +       v2 = strtoul_len (p + 1, &p, &n2);\r
1276 +    } else {\r
1277 +       /* A single number. */\r
1278 +       r = parse_single_number (state, v1, n1);\r
1279 +       if (r)\r
1280 +           return r;\r
1281 +\r
1282 +       return p - s;\r
1283 +    }\r
1284 +\r
1285 +    /* A group of two or three numbers? */\r
1286 +    if (*p == sep && isdigit ((unsigned char) *(p + 1)))\r
1287 +       v3 = strtoul_len (p + 1, &p, &n3);\r
1288 +\r
1289 +    if (is_time_sep (sep))\r
1290 +       r = parse_time (state, sep, v1, v2, v3, n1, n2, n3);\r
1291 +    else\r
1292 +       r = parse_date (state, sep, v1, v2, v3, n1, n2, n3);\r
1293 +\r
1294 +    if (r)\r
1295 +       return r;\r
1296 +\r
1297 +    return p - s;\r
1298 +}\r
1299 +\r
1300 +/*\r
1301 + * Parse delimiter(s). Throw away all except the last one, which is\r
1302 + * stored for parsing the next non-delimiter. Return < 0 on error,\r
1303 + * number of parsed chars on success.\r
1304 + *\r
1305 + * XXX: We might want to be more strict here.\r
1306 + */\r
1307 +static ssize_t\r
1308 +parse_delim (struct state *state, const char *s)\r
1309 +{\r
1310 +    const char *p = s;\r
1311 +\r
1312 +    /*\r
1313 +     * Skip non-alpha and non-digit, and store the last for further\r
1314 +     * processing.\r
1315 +     */\r
1316 +    while (*p && !isalnum ((unsigned char) *p)) {\r
1317 +       set_delim (state, *p);\r
1318 +       p++;\r
1319 +    }\r
1320 +\r
1321 +    return p - s;\r
1322 +}\r
1323 +\r
1324 +/*\r
1325 + * Parse a date/time string. Return < 0 on error, number of parsed\r
1326 + * chars on success.\r
1327 + */\r
1328 +static ssize_t\r
1329 +parse_input (struct state *state, const char *s)\r
1330 +{\r
1331 +    const char *p = s;\r
1332 +    ssize_t n;\r
1333 +    int r;\r
1334 +\r
1335 +    while (*p) {\r
1336 +       if (isalpha ((unsigned char) *p)) {\r
1337 +           n = parse_keyword (state, p);\r
1338 +       } else if (isdigit ((unsigned char) *p)) {\r
1339 +           n = parse_number (state, p);\r
1340 +       } else {\r
1341 +           n = parse_delim (state, p);\r
1342 +       }\r
1343 +\r
1344 +       if (n <= 0) {\r
1345 +           if (n == 0)\r
1346 +               n = -PARSE_TIME_ERR;\r
1347 +\r
1348 +           return n;\r
1349 +       }\r
1350 +\r
1351 +       p += n;\r
1352 +    }\r
1353 +\r
1354 +    /* Parse postponed number, if any. */\r
1355 +    r = handle_postponed_number (state, TM_NONE);\r
1356 +    if (r < 0)\r
1357 +       return r;\r
1358 +\r
1359 +    return p - s;\r
1360 +}\r
1361 +\r
1362 +/*\r
1363 + * Processing the parsed input.\r
1364 + */\r
1365 +\r
1366 +/*\r
1367 + * Initialize reference time to tm. Use time zone in state if\r
1368 + * specified, otherwise local time. Use now for reference time if\r
1369 + * non-NULL, otherwise current time.\r
1370 + */\r
1371 +static int\r
1372 +initialize_now (struct state *state, struct tm *tm, const time_t *now)\r
1373 +{\r
1374 +    time_t t;\r
1375 +\r
1376 +    if (now) {\r
1377 +       t = *now;\r
1378 +    } else {\r
1379 +       if (time (&t) == (time_t) -1)\r
1380 +           return -PARSE_TIME_ERR_LIB;\r
1381 +    }\r
1382 +\r
1383 +    if (is_field_set (state, TM_TZ)) {\r
1384 +       /* Some other time zone. */\r
1385 +\r
1386 +       /* Adjust now according to the TZ. */\r
1387 +       t += get_field (state, TM_TZ) * 60;\r
1388 +\r
1389 +       /* It's not gm, but this doesn't mess with the TZ. */\r
1390 +       if (gmtime_r (&t, tm) == NULL)\r
1391 +           return -PARSE_TIME_ERR_LIB;\r
1392 +    } else {\r
1393 +       /* Local time. */\r
1394 +       if (localtime_r (&t, tm) == NULL)\r
1395 +           return -PARSE_TIME_ERR_LIB;\r
1396 +    }\r
1397 +\r
1398 +    return 0;\r
1399 +}\r
1400 +\r
1401 +/*\r
1402 + * Normalize tm according to mktime(3). Both mktime(3) and\r
1403 + * localtime_r(3) use local time, but they cancel each other out here,\r
1404 + * making this function agnostic to time zone.\r
1405 + */\r
1406 +static int\r
1407 +normalize_tm (struct tm *tm)\r
1408 +{\r
1409 +    time_t t = mktime (tm);\r
1410 +\r
1411 +    if (t == (time_t) -1)\r
1412 +       return -PARSE_TIME_ERR_LIB;\r
1413 +\r
1414 +    if (!localtime_r (&t, tm))\r
1415 +       return -PARSE_TIME_ERR_LIB;\r
1416 +\r
1417 +    return 0;\r
1418 +}\r
1419 +\r
1420 +/* Get field out of a struct tm. */\r
1421 +static int\r
1422 +tm_get_field (const struct tm *tm, enum field field)\r
1423 +{\r
1424 +    switch (field) {\r
1425 +    case TM_ABS_SEC:   return tm->tm_sec;\r
1426 +    case TM_ABS_MIN:   return tm->tm_min;\r
1427 +    case TM_ABS_HOUR:  return tm->tm_hour;\r
1428 +    case TM_ABS_MDAY:  return tm->tm_mday;\r
1429 +    case TM_ABS_MON:   return tm->tm_mon + 1; /* 0- to 1-based */\r
1430 +    case TM_ABS_YEAR:  return 1900 + tm->tm_year;\r
1431 +    case TM_ABS_WDAY:  return tm->tm_wday;\r
1432 +    case TM_ABS_ISDST: return tm->tm_isdst;\r
1433 +    default:\r
1434 +       assert (false);\r
1435 +       break;\r
1436 +    }\r
1437 +\r
1438 +    return 0;\r
1439 +}\r
1440 +\r
1441 +/* Modify hour according to am/pm setting. */\r
1442 +static int\r
1443 +fixup_ampm (struct state *state)\r
1444 +{\r
1445 +    int hour, hdiff = 0;\r
1446 +\r
1447 +    if (!is_field_set (state, TM_AMPM))\r
1448 +       return 0;\r
1449 +\r
1450 +    if (!is_field_set (state, TM_ABS_HOUR))\r
1451 +       return -PARSE_TIME_ERR_TIMEFORMAT;\r
1452 +\r
1453 +    hour = get_field (state, TM_ABS_HOUR);\r
1454 +    if (!is_valid_12hour (hour))\r
1455 +       return -PARSE_TIME_ERR_INVALIDTIME;\r
1456 +\r
1457 +    if (get_field (state, TM_AMPM)) {\r
1458 +       /* 12pm is noon. */\r
1459 +       if (hour != 12)\r
1460 +           hdiff = 12;\r
1461 +    } else {\r
1462 +       /* 12am is midnight, beginning of day. */\r
1463 +       if (hour == 12)\r
1464 +           hdiff = -12;\r
1465 +    }\r
1466 +\r
1467 +    mod_field (state, TM_REL_HOUR, -hdiff);\r
1468 +\r
1469 +    return 0;\r
1470 +}\r
1471 +\r
1472 +/* Combine absolute and relative fields, and round. */\r
1473 +static int\r
1474 +create_output (struct state *state, time_t *t_out, const time_t *ref,\r
1475 +              int round)\r
1476 +{\r
1477 +    struct tm tm = { .tm_isdst = -1 };\r
1478 +    struct tm now;\r
1479 +    time_t t;\r
1480 +    enum field f;\r
1481 +    int r;\r
1482 +    int week_round = PARSE_TIME_NO_ROUND;\r
1483 +\r
1484 +    r = initialize_now (state, &now, ref);\r
1485 +    if (r)\r
1486 +       return r;\r
1487 +\r
1488 +    /* Initialize fields flagged as "now" to reference time. */\r
1489 +    for (f = TM_ABS_SEC; f != TM_NONE; f = next_abs_field (f)) {\r
1490 +       if (state->set[f] == FIELD_NOW) {\r
1491 +           state->tm[f] = tm_get_field (&now, f);\r
1492 +           state->set[f] = FIELD_SET;\r
1493 +       }\r
1494 +    }\r
1495 +\r
1496 +    /*\r
1497 +     * If WDAY is set but MDAY is not, we consider WDAY relative\r
1498 +     *\r
1499 +     * XXX: This fails on stuff like "two months monday" because two\r
1500 +     * months ago wasn't the same day as today. Postpone until we know\r
1501 +     * date?\r
1502 +     */\r
1503 +    if (is_field_set (state, TM_ABS_WDAY) &&\r
1504 +       !is_field_set (state, TM_ABS_MDAY)) {\r
1505 +       int wday = get_field (state, TM_ABS_WDAY);\r
1506 +       int today = tm_get_field (&now, TM_ABS_WDAY);\r
1507 +       int rel_days;\r
1508 +\r
1509 +       if (today > wday)\r
1510 +           rel_days = today - wday;\r
1511 +       else\r
1512 +           rel_days = today + 7 - wday;\r
1513 +\r
1514 +       /* This also prevents special week rounding from happening. */\r
1515 +       mod_field (state, TM_REL_DAY, rel_days);\r
1516 +\r
1517 +       unset_field (state, TM_ABS_WDAY);\r
1518 +    }\r
1519 +\r
1520 +    r = fixup_ampm (state);\r
1521 +    if (r)\r
1522 +       return r;\r
1523 +\r
1524 +    /*\r
1525 +     * Iterate fields from most accurate to least accurate, and set\r
1526 +     * unset fields according to requested rounding.\r
1527 +     */\r
1528 +    for (f = TM_ABS_SEC; f != TM_NONE; f = next_abs_field (f)) {\r
1529 +       if (round != PARSE_TIME_NO_ROUND) {\r
1530 +           enum field r = abs_to_rel_field (f);\r
1531 +\r
1532 +           if (is_field_set (state, f) || is_field_set (state, r)) {\r
1533 +               if (round >= PARSE_TIME_ROUND_UP && f != TM_ABS_SEC) {\r
1534 +                   mod_field (state, r, -1);\r
1535 +                   if (round == PARSE_TIME_ROUND_UP_INCLUSIVE)\r
1536 +                       mod_field (state, TM_REL_SEC, 1);\r
1537 +               }\r
1538 +               round = PARSE_TIME_NO_ROUND; /* No more rounding. */\r
1539 +           } else {\r
1540 +               if (f == TM_ABS_MDAY &&\r
1541 +                   is_field_set (state, TM_REL_WEEK)) {\r
1542 +                   /* Week is most accurate. */\r
1543 +                   week_round = round;\r
1544 +                   round = PARSE_TIME_NO_ROUND;\r
1545 +               } else {\r
1546 +                   set_field (state, f, field_epoch (f));\r
1547 +               }\r
1548 +           }\r
1549 +       }\r
1550 +\r
1551 +       if (!is_field_set (state, f))\r
1552 +           set_field (state, f, tm_get_field (&now, f));\r
1553 +    }\r
1554 +\r
1555 +    /* Special case: rounding with week accuracy. */\r
1556 +    if (week_round != PARSE_TIME_NO_ROUND) {\r
1557 +       /* Temporarily set more accurate fields to now. */\r
1558 +       set_field (state, TM_ABS_SEC, tm_get_field (&now, TM_ABS_SEC));\r
1559 +       set_field (state, TM_ABS_MIN, tm_get_field (&now, TM_ABS_MIN));\r
1560 +       set_field (state, TM_ABS_HOUR, tm_get_field (&now, TM_ABS_HOUR));\r
1561 +       set_field (state, TM_ABS_MDAY, tm_get_field (&now, TM_ABS_MDAY));\r
1562 +    }\r
1563 +\r
1564 +    /*\r
1565 +     * Set all fields. They may contain out of range values before\r
1566 +     * normalization by mktime(3).\r
1567 +     */\r
1568 +    tm.tm_sec = get_field (state, TM_ABS_SEC) - get_field (state, TM_REL_SEC);\r
1569 +    tm.tm_min = get_field (state, TM_ABS_MIN) - get_field (state, TM_REL_MIN);\r
1570 +    tm.tm_hour = get_field (state, TM_ABS_HOUR) - get_field (state, TM_REL_HOUR);\r
1571 +    tm.tm_mday = get_field (state, TM_ABS_MDAY) -\r
1572 +                get_field (state, TM_REL_DAY) - 7 * get_field (state, TM_REL_WEEK);\r
1573 +    tm.tm_mon = get_field (state, TM_ABS_MON) - get_field (state, TM_REL_MON);\r
1574 +    tm.tm_mon--; /* 1- to 0-based */\r
1575 +    tm.tm_year = get_field (state, TM_ABS_YEAR) - get_field (state, TM_REL_YEAR) - 1900;\r
1576 +\r
1577 +    /*\r
1578 +     * It's always normal time.\r
1579 +     *\r
1580 +     * XXX: This is probably not a solution that universally\r
1581 +     * works. Just make sure DST is not taken into account. We don't\r
1582 +     * want rounding to be affected by DST.\r
1583 +     */\r
1584 +    tm.tm_isdst = -1;\r
1585 +\r
1586 +    /* Special case: rounding with week accuracy. */\r
1587 +    if (week_round != PARSE_TIME_NO_ROUND) {\r
1588 +       /* Normalize to get proper tm.wday. */\r
1589 +       r = normalize_tm (&tm);\r
1590 +       if (r < 0)\r
1591 +           return r;\r
1592 +\r
1593 +       /* Set more accurate fields back to zero. */\r
1594 +       tm.tm_sec = 0;\r
1595 +       tm.tm_min = 0;\r
1596 +       tm.tm_hour = 0;\r
1597 +       tm.tm_isdst = -1;\r
1598 +\r
1599 +       /* Monday is the true 1st day of week, but this is easier. */\r
1600 +       if (week_round >= PARSE_TIME_ROUND_UP) {\r
1601 +           tm.tm_mday += 7 - tm.tm_wday;\r
1602 +           if (week_round == PARSE_TIME_ROUND_UP_INCLUSIVE)\r
1603 +               tm.tm_sec--;\r
1604 +       } else {\r
1605 +           tm.tm_mday -= tm.tm_wday;\r
1606 +       }\r
1607 +    }\r
1608 +\r
1609 +    if (is_field_set (state, TM_TZ)) {\r
1610 +       /* tm is in specified TZ, convert to UTC for timegm(3). */\r
1611 +       tm.tm_min -= get_field (state, TM_TZ);\r
1612 +       t = timegm (&tm);\r
1613 +    } else {\r
1614 +       /* tm is in local time. */\r
1615 +       t = mktime (&tm);\r
1616 +    }\r
1617 +\r
1618 +    if (t == (time_t) -1)\r
1619 +       return -PARSE_TIME_ERR_LIB;\r
1620 +\r
1621 +    *t_out = t;\r
1622 +\r
1623 +    return 0;\r
1624 +}\r
1625 +\r
1626 +/* Internally, all errors are < 0. parse_time_string() returns errors > 0. */\r
1627 +#define EXTERNAL_ERR(r) (-r)\r
1628 +\r
1629 +int\r
1630 +parse_time_string (const char *s, time_t *t, const time_t *ref, int round)\r
1631 +{\r
1632 +    struct state state = { .last_field = TM_NONE };\r
1633 +    int r;\r
1634 +\r
1635 +    if (!s || !t)\r
1636 +       return EXTERNAL_ERR (-PARSE_TIME_ERR);\r
1637 +\r
1638 +    r = parse_input (&state, s);\r
1639 +    if (r < 0)\r
1640 +       return EXTERNAL_ERR (r);\r
1641 +\r
1642 +    r = create_output (&state, t, ref, round);\r
1643 +    if (r < 0)\r
1644 +       return EXTERNAL_ERR (r);\r
1645 +\r
1646 +    return 0;\r
1647 +}\r
1648 diff --git a/parse-time-string/parse-time-string.h b/parse-time-string/parse-time-string.h\r
1649 new file mode 100644\r
1650 index 0000000..bfa4ee3\r
1651 --- /dev/null\r
1652 +++ b/parse-time-string/parse-time-string.h\r
1653 @@ -0,0 +1,102 @@\r
1654 +/*\r
1655 + * parse time string - user friendly date and time parser\r
1656 + * Copyright © 2012 Jani Nikula\r
1657 + *\r
1658 + * This program is free software: you can redistribute it and/or modify\r
1659 + * it under the terms of the GNU General Public License as published by\r
1660 + * the Free Software Foundation, either version 2 of the License, or\r
1661 + * (at your option) any later version.\r
1662 + *\r
1663 + * This program is distributed in the hope that it will be useful,\r
1664 + * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
1665 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
1666 + * GNU General Public License for more details.\r
1667 + *\r
1668 + * You should have received a copy of the GNU General Public License\r
1669 + * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r
1670 + *\r
1671 + * Author: Jani Nikula <jani@nikula.org>\r
1672 + */\r
1673 +\r
1674 +#ifndef PARSE_TIME_STRING_H\r
1675 +#define PARSE_TIME_STRING_H\r
1676 +\r
1677 +#ifdef __cplusplus\r
1678 +extern "C" {\r
1679 +#endif\r
1680 +\r
1681 +#include <time.h>\r
1682 +\r
1683 +/* return values for parse_time_string() */\r
1684 +enum {\r
1685 +    PARSE_TIME_OK = 0,\r
1686 +    PARSE_TIME_ERR,            /* unspecified error */\r
1687 +    PARSE_TIME_ERR_LIB,                /* library call failed */\r
1688 +    PARSE_TIME_ERR_ALREADYSET, /* attempt to set unit twice */\r
1689 +    PARSE_TIME_ERR_FORMAT,     /* generic date/time format error */\r
1690 +    PARSE_TIME_ERR_DATEFORMAT, /* date format error */\r
1691 +    PARSE_TIME_ERR_TIMEFORMAT, /* time format error */\r
1692 +    PARSE_TIME_ERR_INVALIDDATE,        /* date value error */\r
1693 +    PARSE_TIME_ERR_INVALIDTIME,        /* time value error */\r
1694 +    PARSE_TIME_ERR_KEYWORD,    /* unknown keyword */\r
1695 +};\r
1696 +\r
1697 +/* round values for parse_time_string() */\r
1698 +enum {\r
1699 +    PARSE_TIME_ROUND_DOWN = -1,\r
1700 +    PARSE_TIME_NO_ROUND = 0,\r
1701 +    PARSE_TIME_ROUND_UP = 1,\r
1702 +    PARSE_TIME_ROUND_UP_INCLUSIVE = 2,\r
1703 +};\r
1704 +\r
1705 +/**\r
1706 + * parse_time_string() - user friendly date and time parser\r
1707 + * @s:         string to parse\r
1708 + * @t:         pointer to time_t to store parsed time in\r
1709 + * @ref:       pointer to time_t containing reference date/time, or NULL\r
1710 + * @round:     PARSE_TIME_NO_ROUND, PARSE_TIME_ROUND_DOWN, or\r
1711 + *             PARSE_TIME_ROUND_UP\r
1712 + *\r
1713 + * Parse a date/time string 's' and store the parsed date/time result\r
1714 + * in 't'.\r
1715 + *\r
1716 + * A reference date/time is used for determining the "date/time units"\r
1717 + * (roughly equivalent to struct tm members) not specified by 's'. If\r
1718 + * 'ref' is non-NULL, it must contain a pointer to a time_t to be used\r
1719 + * as reference date/time. Otherwise, the current time is used.\r
1720 + *\r
1721 + * If 's' does not specify a full date/time, the 'round' parameter\r
1722 + * specifies if and how the result should be rounded as follows:\r
1723 + *\r
1724 + *   PARSE_TIME_NO_ROUND: All date/time units that are not specified\r
1725 + *   by 's' are set to the corresponding unit derived from the\r
1726 + *   reference date/time.\r
1727 + *\r
1728 + *   PARSE_TIME_ROUND_DOWN: All date/time units that are more accurate\r
1729 + *   than the most accurate unit specified by 's' are set to the\r
1730 + *   smallest valid value for that unit. Rest of the unspecified units\r
1731 + *   are set as in PARSE_TIME_NO_ROUND.\r
1732 + *\r
1733 + *   PARSE_TIME_ROUND_UP: All date/time units that are more accurate\r
1734 + *   than the most accurate unit specified by 's' are set to the\r
1735 + *   smallest valid value for that unit. The most accurate unit\r
1736 + *   specified by 's' is incremented by one (and this is rolled over\r
1737 + *   to the less accurate units as necessary), unless the most\r
1738 + *   accurate unit is seconds. Rest of the unspecified units are set\r
1739 + *   as in PARSE_TIME_NO_ROUND.\r
1740 + *\r
1741 + *   PARSE_TIME_ROUND_UP_INCLUSIVE: Same as PARSE_TIME_ROUND_UP, minus\r
1742 + *   one second, unless the most accurate unit specified by 's' is\r
1743 + *   seconds. This is useful for callers that require a value for\r
1744 + *   inclusive comparison of the result.\r
1745 + *\r
1746 + * Return 0 (PARSE_TIME_OK) for succesfully parsed date/time, or one\r
1747 + * of PARSE_TIME_ERR_* on error. 't' is not modified on error.\r
1748 + */\r
1749 +int parse_time_string (const char *s, time_t *t, const time_t *ref, int round);\r
1750 +\r
1751 +#ifdef __cplusplus\r
1752 +}\r
1753 +#endif\r
1754 +\r
1755 +#endif /* PARSE_TIME_STRING_H */\r
1756 -- \r
1757 1.7.9.5\r
1758 \r