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
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
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
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
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
55 Content-Type: text/plain; charset=UTF-8
\r
56 Content-Transfer-Encoding: 8bit
\r
58 ALoCoQnQLxZm/uM1aN8MkPUXXLgqj3CK6dyPn1atHscGR70RrX8Ok5uQks7Kvhf6IaojQG352cB0
\r
59 X-BeenThere: notmuch@notmuchmail.org
\r
60 X-Mailman-Version: 2.1.13
\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
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
78 Signed-off-by: Jani Nikula <jani@nikula.org>
\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
93 diff --git a/Makefile b/Makefile
\r
94 index e5e2e3a..bb9c316 100644
\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
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
110 +++ b/parse-time-string/Makefile
\r
113 + $(MAKE) -C .. all
\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
121 +++ b/parse-time-string/Makefile.local
\r
123 +dir := parse-time-string
\r
124 +extra_cflags += -I$(srcdir)/$(dir)
\r
126 +libparse-time-string_c_srcs := $(dir)/parse-time-string.c
\r
128 +libparse-time-string_modules := $(libparse-time-string_c_srcs:.c=.o)
\r
130 +$(dir)/libparse-time-string.a: $(libparse-time-string_modules)
\r
131 + $(call quiet,AR) rcs $@ $^
\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
139 +++ b/parse-time-string/README
\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
154 +++ b/parse-time-string/parse-time-string.c
\r
157 + * parse time string - user friendly date and time parser
\r
158 + * Copyright © 2012 Jani Nikula
\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
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
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
173 + * Author: Jani Nikula <jani@nikula.org>
\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
187 +#include <sys/time.h>
\r
188 +#include <sys/types.h>
\r
190 +#include "parse-time-string.h"
\r
193 + * IMPLEMENTATION DETAILS
\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
202 + * 1) The parsing phase - parse_input()
\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
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
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
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
231 + * 2) The processing phase - create_output()
\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
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
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
251 + * Finally, the result is stored to time_t.
\r
254 +#define unused(x) x __attribute__ ((unused))
\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
262 +#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0]))
\r
265 + * Field indices in the tm and set arrays of struct state.
\r
267 + * NOTE: There's some code that depends on the ordering of this enum.
\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
278 + TM_ABS_WDAY, /* day of the week. special: may be relative */
\r
279 + TM_ABS_ISDST, /* daylight saving time */
\r
281 + TM_AMPM, /* am vs. pm */
\r
282 + TM_TZ, /* timezone in minutes */
\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
293 + TM_NONE, /* not a field */
\r
295 + TM_SIZE = TM_NONE,
\r
296 + TM_FIRST_ABS = TM_ABS_SEC,
\r
297 + TM_FIRST_REL = TM_REL_SEC,
\r
300 +/* Values for the set array of struct state. */
\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
308 +next_abs_field (enum field field)
\r
310 + /* NOTE: Depends on the enum ordering. */
\r
311 + return field < TM_ABS_YEAR ? field + 1 : TM_NONE;
\r
315 +abs_to_rel_field (enum field field)
\r
317 + assert (field <= TM_ABS_YEAR);
\r
319 + /* NOTE: Depends on the enum ordering. */
\r
320 + return field + (TM_FIRST_REL - TM_FIRST_ABS);
\r
323 +/* Get epoch value for field. */
\r
325 +field_epoch (enum field field)
\r
327 + if (field == TM_ABS_MDAY || field == TM_ABS_MON)
\r
329 + else if (field == TM_ABS_YEAR)
\r
335 +/* The parsing state. */
\r
337 + int tm[TM_SIZE]; /* parsed date and time */
\r
338 + enum field_set set[TM_SIZE]; /* set status of tm */
\r
340 + enum field last_field; /* Previously set field. */
\r
341 + enum field next_field; /* Next field for parse_postponed_number() */
\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
350 + * Helpers for postponed numbers.
\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
357 +get_postponed_length (struct state *state)
\r
359 + return state->postponed_length;
\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
370 +get_postponed_number (struct state *state, int *v, int *n, char *d)
\r
372 + if (!state->postponed_length)
\r
376 + *n = state->postponed_length;
\r
379 + *v = state->postponed_value;
\r
382 + *d = state->postponed_delim;
\r
384 + state->postponed_length = 0;
\r
385 + state->postponed_value = 0;
\r
386 + state->postponed_delim = 0;
\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
394 +handle_postponed_number (struct state *state, enum field next_field)
\r
396 + int v = state->postponed_value;
\r
397 + int n = state->postponed_length;
\r
398 + char d = state->postponed_delim;
\r
404 + state->postponed_value = 0;
\r
405 + state->postponed_length = 0;
\r
406 + state->postponed_delim = 0;
\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
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
421 +set_postponed_number (struct state *state, int v, int n)
\r
424 + char d = state->delim;
\r
426 + /* Parse a previously postponed number, if any. */
\r
427 + r = handle_postponed_number (state, TM_NONE);
\r
431 + state->postponed_length = n;
\r
432 + state->postponed_value = v;
\r
433 + state->postponed_delim = d;
\r
439 +set_delim (struct state *state, char delim)
\r
441 + state->delim = delim;
\r
445 +unset_delim (struct state *state)
\r
447 + state->delim = 0;
\r
451 + * Field set/get/mod helpers.
\r
454 +/* Return true if field has been set. */
\r
456 +is_field_set (struct state *state, enum field field)
\r
458 + assert (field < ARRAY_SIZE (state->tm));
\r
460 + return field < ARRAY_SIZE (state->set) &&
\r
461 + state->set[field] != FIELD_UNSET;
\r
465 +unset_field (struct state *state, enum field field)
\r
467 + assert (field < ARRAY_SIZE (state->tm));
\r
469 + state->set[field] = FIELD_UNSET;
\r
470 + state->tm[field] = 0;
\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
478 +set_field (struct state *state, enum field field, int value)
\r
482 + assert (field < ARRAY_SIZE (state->tm));
\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
488 + state->set[field] = FIELD_SET;
\r
490 + /* Parse postponed number, if any. */
\r
491 + r = handle_postponed_number (state, field);
\r
495 + unset_delim (state);
\r
497 + state->tm[field] = value;
\r
498 + state->last_field = field;
\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
510 +set_fields_to_now (struct state *state, enum field *fields, size_t n)
\r
515 + for (i = 0; i < n; i++) {
\r
516 + r = set_field (state, fields[i], 0);
\r
519 + state->set[fields[i]] = FIELD_NOW;
\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
528 +mod_field (struct state *state, enum field field, int value)
\r
532 + assert (field < ARRAY_SIZE (state->tm)); /* assert relative??? */
\r
534 + if (field < ARRAY_SIZE (state->set))
\r
535 + state->set[field] = FIELD_SET;
\r
537 + /* Parse postponed number, if any. */
\r
538 + r = handle_postponed_number (state, field);
\r
542 + unset_delim (state);
\r
544 + state->tm[field] += value;
\r
545 + state->last_field = field;
\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
556 +get_field (struct state *state, enum field field)
\r
558 + assert (field < ARRAY_SIZE (state->tm));
\r
560 + return state->tm[field];
\r
564 + * Validity checkers.
\r
566 +static bool is_valid_12hour (int h)
\r
568 + return h >= 0 && h <= 12;
\r
571 +static bool is_valid_time (int h, int m, int s)
\r
573 + /* Allow 24:00:00 to denote end of day. */
\r
574 + if (h == 24 && m == 0 && s == 0)
\r
577 + return h >= 0 && h <= 23 && m >= 0 && m <= 59 && s >= 0 && s <= 59;
\r
580 +static bool is_valid_mday (int mday)
\r
582 + return mday >= 1 && mday <= 31;
\r
585 +static bool is_valid_mon (int mon)
\r
587 + return mon >= 1 && mon <= 12;
\r
590 +static bool is_valid_year (int year)
\r
592 + return year >= 1970;
\r
595 +static bool is_valid_date (int year, int mon, int mday)
\r
597 + return is_valid_year (year) && is_valid_mon (mon) && is_valid_mday (mday);
\r
600 +/* Unset indicator for time and date set helpers. */
\r
603 +/* Time set helper. No input checking. Use UNSET (-1) to leave unset. */
\r
605 +set_abs_time (struct state *state, int hour, int min, int sec)
\r
609 + if (hour != UNSET) {
\r
610 + if ((r = set_field (state, TM_ABS_HOUR, hour)))
\r
614 + if (min != UNSET) {
\r
615 + if ((r = set_field (state, TM_ABS_MIN, min)))
\r
619 + if (sec != UNSET) {
\r
620 + if ((r = set_field (state, TM_ABS_SEC, sec)))
\r
627 +/* Date set helper. No input checking. Use UNSET (-1) to leave unset. */
\r
629 +set_abs_date (struct state *state, int year, int mon, int mday)
\r
633 + if (year != UNSET) {
\r
634 + if ((r = set_field (state, TM_ABS_YEAR, year)))
\r
638 + if (mon != UNSET) {
\r
639 + if ((r = set_field (state, TM_ABS_MON, mon)))
\r
643 + if (mday != UNSET) {
\r
644 + if ((r = set_field (state, TM_ABS_MDAY, mday)))
\r
652 + * Keyword parsing and handling.
\r
655 +typedef int (*setter_t)(struct state *state, struct keyword *kw);
\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
665 + * Setter callback functions for keywords.
\r
668 +kw_set_default (struct state *state, struct keyword *kw)
\r
670 + return set_field (state, kw->field, kw->value);
\r
674 +kw_set_rel (struct state *state, struct keyword *kw)
\r
676 + int multiplier = 1;
\r
678 + /* Get a previously set multiplier, if any. */
\r
679 + get_postponed_number (state, &multiplier, NULL, NULL);
\r
681 + /* Accumulate relative field values. */
\r
682 + return mod_field (state, kw->field, multiplier * kw->value);
\r
686 +kw_set_number (struct state *state, struct keyword *kw)
\r
688 + /* -1 = no length, from keyword. */
\r
689 + return set_postponed_number (state, kw->value, -1);
\r
693 +kw_set_month (struct state *state, struct keyword *kw)
\r
695 + int n = get_postponed_length (state);
\r
697 + /* Consume postponed number if it could be mday. This handles "20
\r
699 + if (n == 1 || n == 2) {
\r
702 + get_postponed_number (state, &v, NULL, NULL);
\r
704 + if (!is_valid_mday (v))
\r
705 + return -PARSE_TIME_ERR_INVALIDDATE;
\r
707 + r = set_field (state, TM_ABS_MDAY, v);
\r
712 + return set_field (state, kw->field, kw->value);
\r
716 +kw_set_ampm (struct state *state, struct keyword *kw)
\r
718 + int n = get_postponed_length (state);
\r
720 + /* Consume postponed number if it could be hour. This handles
\r
722 + if (n == 1 || n == 2) {
\r
725 + get_postponed_number (state, &v, NULL, NULL);
\r
727 + if (!is_valid_12hour (v))
\r
728 + return -PARSE_TIME_ERR_INVALIDTIME;
\r
730 + r = set_abs_time (state, v, 0, 0);
\r
735 + return set_field (state, kw->field, kw->value);
\r
739 +kw_set_timeofday (struct state *state, struct keyword *kw)
\r
741 + return set_abs_time (state, kw->value, 0, 0);
\r
745 +kw_set_today (struct state *state, unused (struct keyword *kw))
\r
747 + enum field fields[] = { TM_ABS_YEAR, TM_ABS_MON, TM_ABS_MDAY };
\r
749 + return set_fields_to_now (state, fields, ARRAY_SIZE (fields));
\r
753 +kw_set_now (struct state *state, unused (struct keyword *kw))
\r
755 + enum field fields[] = { TM_ABS_HOUR, TM_ABS_MIN, TM_ABS_SEC };
\r
757 + return set_fields_to_now (state, fields, ARRAY_SIZE (fields));
\r
761 +kw_set_ordinal (struct state *state, struct keyword *kw)
\r
765 + /* Require a postponed number. */
\r
766 + if (!get_postponed_number (state, &v, &n, NULL))
\r
767 + return -PARSE_TIME_ERR_DATEFORMAT;
\r
769 + /* Ordinals are mday. */
\r
770 + if (n != 1 && n != 2)
\r
771 + return -PARSE_TIME_ERR_DATEFORMAT;
\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
783 + return set_field (state, TM_ABS_MDAY, v);
\r
787 + * Accepted keywords.
\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
793 + * If keyword begins with upper case letter, then the matching will be
\r
794 + * case sensitive. Otherwise the matching is case insensitive.
\r
796 + * If setter is NULL, set_default will be used.
\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
803 +static struct keyword keywords[] = {
\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
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
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
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
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
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
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
882 + { N_("gmt"), TM_TZ, 0, NULL },
\r
883 + { N_("utc"), TM_TZ, 0, NULL },
\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
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
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
902 +stringcmp (const char *s, const char *keyword, ssize_t n, bool match_case)
\r
909 + for (i = 0; *s && *keyword; i++, s++, keyword++) {
\r
910 + if (match_case) {
\r
911 + if (*s != *keyword)
\r
914 + if (tolower ((unsigned char) *s) !=
\r
915 + tolower ((unsigned char) *keyword))
\r
921 + return i < n ? 0 : i;
\r
923 + return *keyword ? 0 : i;
\r
927 + * Parse a keyword. Return < 0 on error, number of parsed chars on
\r
931 +parse_keyword (struct state *state, const char *s)
\r
934 + size_t n, max_n = 0;
\r
935 + struct keyword *kw = NULL;
\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
946 + strncpy (keyword, _(keywords[i].name), sizeof (keyword));
\r
948 + /* Truncate too long keywords. XXX: Make this dynamic? */
\r
949 + keyword[sizeof (keyword) - 1] = '\0';
\r
951 + /* Minimum match length. */
\r
952 + p = strchr (keyword, '|');
\r
954 + minlen = p - keyword;
\r
956 + /* Remove the minimum match length separator. */
\r
957 + memmove (p, p + 1, strlen (p + 1) + 1);
\r
960 + n = stringcmp (s, keyword, minlen, mcase);
\r
961 + if (n > max_n || (n == max_n && mcase)) {
\r
963 + kw = &keywords[i];
\r
968 + return -PARSE_TIME_ERR_KEYWORD;
\r
971 + r = kw->set (state, kw);
\r
973 + r = kw_set_default (state, kw);
\r
982 + * Non-keyword parsers and their helpers.
\r
986 +set_user_tz (struct state *state, char sign, int hour, int min)
\r
988 + int tz = hour * 60 + min;
\r
990 + assert (sign == '+' || sign == '-');
\r
992 + if (hour < 0 || hour > 14 || min < 0 || min > 59 || min % 15)
\r
993 + return -PARSE_TIME_ERR_INVALIDTIME;
\r
998 + return set_field (state, TM_TZ, tz);
\r
1002 + * Independent parsing of a postponed number when it wasn't consumed
\r
1003 + * during parsing of the following token.
\r
1006 +parse_postponed_number (struct state *state, int v, int n, char d)
\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
1013 + if (!is_valid_mday (v))
\r
1014 + return -PARSE_TIME_ERR_INVALIDDATE;
\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
1021 + return set_user_tz (state, d, v, 0);
\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
1028 + if (!is_valid_year (v)) {
\r
1029 + if (d == '+' || d == '-') {
\r
1031 + return set_user_tz (state, d, v / 100, v % 100);
\r
1035 + return set_field (state, TM_ABS_YEAR, v);
\r
1037 + } else if (n == 6) {
\r
1039 + int hour = v / 10000;
\r
1040 + int min = (v / 100) % 100;
\r
1041 + int sec = v % 100;
\r
1043 + if (!is_valid_time (hour, min, sec))
\r
1044 + return -PARSE_TIME_ERR_INVALIDTIME;
\r
1046 + return set_abs_time (state, hour, min, sec);
\r
1047 + } else if (n == 8) {
\r
1049 + int year = v / 10000;
\r
1050 + int mon = (v / 100) % 100;
\r
1051 + int mday = v % 100;
\r
1053 + if (!is_valid_date (year, mon, mday))
\r
1054 + return -PARSE_TIME_ERR_INVALIDDATE;
\r
1056 + return set_abs_date (state, year, mon, mday);
\r
1058 + return -PARSE_TIME_ERR_FORMAT;
\r
1061 + return -PARSE_TIME_ERR_FORMAT;
\r
1064 +static int tm_get_field (const struct tm *tm, enum field field);
\r
1067 +set_timestamp (struct state *state, time_t t)
\r
1073 + if (gmtime_r (&t, &tm) == NULL)
\r
1074 + return -PARSE_TIME_ERR_LIB;
\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
1082 + r = set_field (state, TM_TZ, 0);
\r
1086 + /* XXX: Prevent TM_AMPM with timestamp, e.g. "@123456 pm" */
\r
1091 +/* Parse a single number. Typically postpone parsing until later. */
\r
1093 +parse_single_number (struct state *state, unsigned long v,
\r
1094 + unsigned long n)
\r
1098 + if (state->delim == '@')
\r
1099 + return set_timestamp (state, (time_t) v);
\r
1101 + if (v > INT_MAX)
\r
1102 + return -PARSE_TIME_ERR_FORMAT;
\r
1104 + return set_postponed_number (state, v, n);
\r
1108 +is_time_sep (char c)
\r
1110 + return c == ':';
\r
1114 +is_date_sep (char c)
\r
1116 + return c == '/' || c == '-' || c == '.';
\r
1122 + return is_time_sep (c) || is_date_sep (c);
\r
1125 +/* Two-digit year: 00...69 is 2000s, 70...99 1900s, if n == 0 keep
\r
1128 +expand_year (unsigned long year, size_t n)
\r
1131 + return (year < 70 ? 2000 : 1900) + year;
\r
1132 + } else if (n == 4) {
\r
1139 +/* Parse a date number triplet. */
\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
1145 + int year = UNSET, mon = UNSET, mday = UNSET;
\r
1147 + assert (is_date_sep (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
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
1159 + } else if (n2 == 4 && n3 == 0) {
\r
1164 + return -PARSE_TIME_ERR_DATEFORMAT;
\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
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
1180 + } else if (n1 == 2 && n2 == 4 && n3 == 0) {
\r
1185 + return -PARSE_TIME_ERR_DATEFORMAT;
\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
1194 + year = expand_year (v3, n3);
\r
1200 + if (year != UNSET && !is_valid_year (year))
\r
1201 + return -PARSE_TIME_ERR_INVALIDDATE;
\r
1203 + if (mon != UNSET && !is_valid_mon (mon))
\r
1204 + return -PARSE_TIME_ERR_INVALIDDATE;
\r
1206 + if (mday != UNSET && !is_valid_mday (mday))
\r
1207 + return -PARSE_TIME_ERR_INVALIDDATE;
\r
1209 + return set_abs_date (state, year, mon, mday);
\r
1212 +/* Parse a time number triplet. */
\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
1218 + assert (is_time_sep (sep));
\r
1220 + if ((n1 != 1 && n1 != 2) || n2 != 2 || (n3 != 0 && n3 != 2))
\r
1221 + return -PARSE_TIME_ERR_TIMEFORMAT;
\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
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
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
1242 + if (!is_valid_time (v1, v2, v3))
\r
1243 + return -PARSE_TIME_ERR_INVALIDTIME;
\r
1245 + return set_abs_time (state, v1, v2, n3 ? v3 : 0);
\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
1252 + unsigned long val = strtoul (s, (char **) endp, 10);
\r
1254 + *len = *endp - s;
\r
1259 + * Parse a (group of) number(s). Return < 0 on error, number of parsed
\r
1260 + * chars on success.
\r
1263 +parse_number (struct state *state, const char *s)
\r
1266 + unsigned long v1, v2, v3 = 0;
\r
1267 + size_t n1, n2, n3 = 0;
\r
1268 + const char *p = s;
\r
1271 + v1 = strtoul_len (p, &p, &n1);
\r
1273 + if (is_sep (*p) && isdigit ((unsigned char) *(p + 1))) {
\r
1275 + v2 = strtoul_len (p + 1, &p, &n2);
\r
1277 + /* A single number. */
\r
1278 + r = parse_single_number (state, v1, n1);
\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
1289 + if (is_time_sep (sep))
\r
1290 + r = parse_time (state, sep, v1, v2, v3, n1, n2, n3);
\r
1292 + r = parse_date (state, sep, v1, v2, v3, n1, n2, n3);
\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
1305 + * XXX: We might want to be more strict here.
\r
1308 +parse_delim (struct state *state, const char *s)
\r
1310 + const char *p = s;
\r
1313 + * Skip non-alpha and non-digit, and store the last for further
\r
1316 + while (*p && !isalnum ((unsigned char) *p)) {
\r
1317 + set_delim (state, *p);
\r
1325 + * Parse a date/time string. Return < 0 on error, number of parsed
\r
1326 + * chars on success.
\r
1329 +parse_input (struct state *state, const char *s)
\r
1331 + const char *p = s;
\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
1341 + n = parse_delim (state, p);
\r
1346 + n = -PARSE_TIME_ERR;
\r
1354 + /* Parse postponed number, if any. */
\r
1355 + r = handle_postponed_number (state, TM_NONE);
\r
1363 + * Processing the parsed input.
\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
1372 +initialize_now (struct state *state, struct tm *tm, const time_t *now)
\r
1379 + if (time (&t) == (time_t) -1)
\r
1380 + return -PARSE_TIME_ERR_LIB;
\r
1383 + if (is_field_set (state, TM_TZ)) {
\r
1384 + /* Some other time zone. */
\r
1386 + /* Adjust now according to the TZ. */
\r
1387 + t += get_field (state, TM_TZ) * 60;
\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
1393 + /* Local time. */
\r
1394 + if (localtime_r (&t, tm) == NULL)
\r
1395 + return -PARSE_TIME_ERR_LIB;
\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
1407 +normalize_tm (struct tm *tm)
\r
1409 + time_t t = mktime (tm);
\r
1411 + if (t == (time_t) -1)
\r
1412 + return -PARSE_TIME_ERR_LIB;
\r
1414 + if (!localtime_r (&t, tm))
\r
1415 + return -PARSE_TIME_ERR_LIB;
\r
1420 +/* Get field out of a struct tm. */
\r
1422 +tm_get_field (const struct tm *tm, enum field field)
\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
1441 +/* Modify hour according to am/pm setting. */
\r
1443 +fixup_ampm (struct state *state)
\r
1445 + int hour, hdiff = 0;
\r
1447 + if (!is_field_set (state, TM_AMPM))
\r
1450 + if (!is_field_set (state, TM_ABS_HOUR))
\r
1451 + return -PARSE_TIME_ERR_TIMEFORMAT;
\r
1453 + hour = get_field (state, TM_ABS_HOUR);
\r
1454 + if (!is_valid_12hour (hour))
\r
1455 + return -PARSE_TIME_ERR_INVALIDTIME;
\r
1457 + if (get_field (state, TM_AMPM)) {
\r
1458 + /* 12pm is noon. */
\r
1462 + /* 12am is midnight, beginning of day. */
\r
1467 + mod_field (state, TM_REL_HOUR, -hdiff);
\r
1472 +/* Combine absolute and relative fields, and round. */
\r
1474 +create_output (struct state *state, time_t *t_out, const time_t *ref,
\r
1477 + struct tm tm = { .tm_isdst = -1 };
\r
1482 + int week_round = PARSE_TIME_NO_ROUND;
\r
1484 + r = initialize_now (state, &now, ref);
\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
1497 + * If WDAY is set but MDAY is not, we consider WDAY relative
\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
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
1509 + if (today > wday)
\r
1510 + rel_days = today - wday;
\r
1512 + rel_days = today + 7 - wday;
\r
1514 + /* This also prevents special week rounding from happening. */
\r
1515 + mod_field (state, TM_REL_DAY, rel_days);
\r
1517 + unset_field (state, TM_ABS_WDAY);
\r
1520 + r = fixup_ampm (state);
\r
1525 + * Iterate fields from most accurate to least accurate, and set
\r
1526 + * unset fields according to requested rounding.
\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
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
1538 + round = PARSE_TIME_NO_ROUND; /* No more rounding. */
\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
1546 + set_field (state, f, field_epoch (f));
\r
1551 + if (!is_field_set (state, f))
\r
1552 + set_field (state, f, tm_get_field (&now, f));
\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
1565 + * Set all fields. They may contain out of range values before
\r
1566 + * normalization by mktime(3).
\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
1578 + * It's always normal time.
\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
1584 + tm.tm_isdst = -1;
\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
1593 + /* Set more accurate fields back to zero. */
\r
1597 + tm.tm_isdst = -1;
\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
1605 + tm.tm_mday -= tm.tm_wday;
\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
1614 + /* tm is in local time. */
\r
1615 + t = mktime (&tm);
\r
1618 + if (t == (time_t) -1)
\r
1619 + return -PARSE_TIME_ERR_LIB;
\r
1626 +/* Internally, all errors are < 0. parse_time_string() returns errors > 0. */
\r
1627 +#define EXTERNAL_ERR(r) (-r)
\r
1630 +parse_time_string (const char *s, time_t *t, const time_t *ref, int round)
\r
1632 + struct state state = { .last_field = TM_NONE };
\r
1636 + return EXTERNAL_ERR (-PARSE_TIME_ERR);
\r
1638 + r = parse_input (&state, s);
\r
1640 + return EXTERNAL_ERR (r);
\r
1642 + r = create_output (&state, t, ref, round);
\r
1644 + return EXTERNAL_ERR (r);
\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
1652 +++ b/parse-time-string/parse-time-string.h
\r
1655 + * parse time string - user friendly date and time parser
\r
1656 + * Copyright © 2012 Jani Nikula
\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
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
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
1671 + * Author: Jani Nikula <jani@nikula.org>
\r
1674 +#ifndef PARSE_TIME_STRING_H
\r
1675 +#define PARSE_TIME_STRING_H
\r
1677 +#ifdef __cplusplus
\r
1681 +#include <time.h>
\r
1683 +/* return values for parse_time_string() */
\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
1697 +/* round values for parse_time_string() */
\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
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
1713 + * Parse a date/time string 's' and store the parsed date/time result
\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
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
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
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
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
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
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
1749 +int parse_time_string (const char *s, time_t *t, const time_t *ref, int round);
\r
1751 +#ifdef __cplusplus
\r
1755 +#endif /* PARSE_TIME_STRING_H */
\r