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 69600431FC2
\r
6 for <notmuch@notmuchmail.org>; Sun, 19 Feb 2012 14:56:09 -0800 (PST)
\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 jSPVZhoQaww0 for <notmuch@notmuchmail.org>;
\r
17 Sun, 19 Feb 2012 14:56:01 -0800 (PST)
\r
18 Received: from mail-lpp01m010-f53.google.com (mail-lpp01m010-f53.google.com
\r
19 [209.85.215.53]) (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 8FACD431FAF
\r
22 for <notmuch@notmuchmail.org>; Sun, 19 Feb 2012 14:56:00 -0800 (PST)
\r
23 Received: by mail-lpp01m010-f53.google.com with SMTP id d3so5943891lah.26
\r
24 for <notmuch@notmuchmail.org>; Sun, 19 Feb 2012 14:56:00 -0800 (PST)
\r
25 Received-SPF: pass (google.com: domain of jani@nikula.org designates
\r
26 10.152.127.136 as permitted sender) client-ip=10.152.127.136;
\r
27 Authentication-Results: mr.google.com;
\r
28 spf=pass (google.com: domain of jani@nikula.org
\r
29 designates 10.152.127.136 as permitted sender)
\r
30 smtp.mail=jani@nikula.org
\r
31 Received: from mr.google.com ([10.152.127.136])
\r
32 by 10.152.127.136 with SMTP id ng8mr13655022lab.16.1329692160207
\r
33 (num_hops = 1); Sun, 19 Feb 2012 14:56:00 -0800 (PST)
\r
34 Received: by 10.152.127.136 with SMTP id ng8mr11420481lab.16.1329692160131;
\r
35 Sun, 19 Feb 2012 14:56:00 -0800 (PST)
\r
36 Received: from localhost (dsl-hkibrasgw4-fe50f800-253.dhcp.inet.fi.
\r
38 by mx.google.com with ESMTPS id w10sm21412303lbe.14.2012.02.19.14.55.57
\r
39 (version=SSLv3 cipher=OTHER); Sun, 19 Feb 2012 14:55:59 -0800 (PST)
\r
40 From: Jani Nikula <jani@nikula.org>
\r
41 To: notmuch@notmuchmail.org
\r
42 Subject: [RFC PATCH 1/2] lib: add date/time parser
\r
43 Date: Mon, 20 Feb 2012 00:55:51 +0200
\r
45 <25ea278088dd81c51496825c4f8365796de29094.1329689945.git.jani@nikula.org>
\r
46 X-Mailer: git-send-email 1.7.5.4
\r
47 In-Reply-To: <cover.1329689945.git.jani@nikula.org>
\r
48 References: <cover.1329689945.git.jani@nikula.org>
\r
49 In-Reply-To: <cover.1329689945.git.jani@nikula.org>
\r
50 References: <cover.1329689945.git.jani@nikula.org>
\r
52 Content-Type: text/plain; charset=UTF-8
\r
53 Content-Transfer-Encoding: 8bit
\r
55 ALoCoQlxnB1PStZBQKPPefNV0WjSZ39EwlJf1HVTA3biy+mvHge/BE939zs3RRF1bm+12NQYdCX0
\r
56 X-BeenThere: notmuch@notmuchmail.org
\r
57 X-Mailman-Version: 2.1.13
\r
59 List-Id: "Use and development of the notmuch mail system."
\r
60 <notmuch.notmuchmail.org>
\r
61 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
\r
62 <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
\r
63 List-Archive: <http://notmuchmail.org/pipermail/notmuch>
\r
64 List-Post: <mailto:notmuch@notmuchmail.org>
\r
65 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
\r
66 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
\r
67 <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
\r
68 X-List-Received-Date: Sun, 19 Feb 2012 22:56:10 -0000
\r
70 Signed-off-by: Jani Nikula <jani@nikula.org>
\r
72 lib/Makefile.local | 1 +
\r
73 lib/parse-time-string.c | 1304 +++++++++++++++++++++++++++++++++++++++++++++++
\r
74 lib/parse-time-string.h | 95 ++++
\r
75 3 files changed, 1400 insertions(+), 0 deletions(-)
\r
76 create mode 100644 lib/parse-time-string.c
\r
77 create mode 100644 lib/parse-time-string.h
\r
79 diff --git a/lib/Makefile.local b/lib/Makefile.local
\r
80 index 54c4dea..803a284 100644
\r
81 --- a/lib/Makefile.local
\r
82 +++ b/lib/Makefile.local
\r
83 @@ -53,6 +53,7 @@ libnotmuch_c_srcs = \
\r
85 $(dir)/message-file.c \
\r
87 + $(dir)/parse-time-string.c \
\r
91 diff --git a/lib/parse-time-string.c b/lib/parse-time-string.c
\r
92 new file mode 100644
\r
93 index 0000000..59713dc
\r
95 +++ b/lib/parse-time-string.c
\r
98 + * parse time string - user friendly date and time parser
\r
99 + * Copyright © 2012 Jani Nikula
\r
101 + * This program is free software: you can redistribute it and/or modify
\r
102 + * it under the terms of the GNU General Public License as published by
\r
103 + * the Free Software Foundation, either version 2 of the License, or
\r
104 + * (at your option) any later version.
\r
106 + * This program is distributed in the hope that it will be useful,
\r
107 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
108 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
109 + * GNU General Public License for more details.
\r
111 + * You should have received a copy of the GNU General Public License
\r
112 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
\r
114 + * Author: Jani Nikula <jani@nikula.org>
\r
117 +#ifndef PARSE_TIME_DEBUG
\r
118 +#define NDEBUG /* for assert() */
\r
121 +#include <assert.h>
\r
122 +#include <ctype.h>
\r
123 +#include <errno.h>
\r
124 +#include <limits.h>
\r
125 +#include <stdio.h>
\r
126 +#include <stdarg.h>
\r
127 +#include <stdbool.h>
\r
128 +#include <stdlib.h>
\r
129 +#include <string.h>
\r
130 +#include <strings.h>
\r
132 +#include <sys/time.h>
\r
133 +#include <sys/types.h>
\r
135 +#include "parse-time-string.h"
\r
137 +#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0]))
\r
139 +/* field indices in struct state tm, and set fields */
\r
141 + /* keep SEC...YEAR in this order */
\r
142 + TM_ABS_SEC, /* seconds */
\r
143 + TM_ABS_MIN, /* minutes */
\r
144 + TM_ABS_HOUR, /* hours */
\r
145 + TM_ABS_MDAY, /* day of the month */
\r
146 + TM_ABS_MON, /* month */
\r
147 + TM_ABS_YEAR, /* year */
\r
149 + TM_ABS_WDAY, /* day of the week. special: may be relative */
\r
150 + TM_ABS_ISDST, /* daylight saving time */
\r
152 + TM_AMPM, /* am vs. pm */
\r
153 + TM_TZ, /* timezone in minutes */
\r
155 + /* keep SEC...YEAR in this order */
\r
156 + TM_REL_SEC, /* seconds relative to now */
\r
157 + TM_REL_MIN, /* minutes ... */
\r
158 + TM_REL_HOUR, /* hours ... */
\r
159 + TM_REL_DAY, /* days ... */
\r
160 + TM_REL_MON, /* months ... */
\r
161 + TM_REL_YEAR, /* years ... */
\r
162 + TM_REL_WEEK, /* weeks ... */
\r
164 + TM_NONE, /* not a field */
\r
166 + TM_SIZE = TM_NONE,
\r
176 +next_field (enum field field)
\r
178 + /* note: depends on the enum ordering */
\r
179 + return field < TM_ABS_YEAR ? field + 1 : TM_NONE;
\r
183 +abs_to_rel_field (enum field field)
\r
185 + assert (field <= TM_ABS_YEAR);
\r
187 + /* note: depends on the enum ordering */
\r
188 + return field + (TM_REL_SEC - TM_ABS_SEC);
\r
191 +/* get zero value for field */
\r
193 +field_zero (enum field field)
\r
195 + if (field == TM_ABS_MDAY || field == TM_ABS_MON)
\r
197 + else if (field == TM_ABS_YEAR)
\r
204 + int tm[TM_SIZE]; /* parsed date and time */
\r
205 + enum field_set set[TM_SIZE]; /* set status of tm */
\r
207 + enum field last_field;
\r
210 + int postponed_length; /* number of digits in postponed value */
\r
211 + int postponed_value;
\r
215 + * Helpers for postponed numbers.
\r
217 + * postponed_length is the number of digits in postponed value. 0
\r
218 + * means there is no postponed number. -1 means there is a postponed
\r
219 + * number, but it comes from a keyword, and it doesn't have digits.
\r
222 +get_postponed_length (struct state *state)
\r
224 + return state->postponed_length;
\r
228 +get_postponed_number (struct state *state, int *v, int *n)
\r
230 + if (!state->postponed_length)
\r
234 + *n = state->postponed_length;
\r
237 + *v = state->postponed_value;
\r
239 + state->postponed_length = 0;
\r
240 + state->postponed_value = 0;
\r
245 +/* parse postponed number if one exists */
\r
246 +static int parse_postponed_number (struct state *state, int v, int n);
\r
248 +handle_postponed_number (struct state *state)
\r
250 + int v = state->postponed_value;
\r
251 + int n = state->postponed_length;
\r
256 + state->postponed_value = 0;
\r
257 + state->postponed_length = 0;
\r
259 + return parse_postponed_number (state, v, n);
\r
263 + * set new postponed number to be handled later. if one exists
\r
264 + * already, handle it first. n may be -1 to indicate a keyword that
\r
265 + * has no number length.
\r
268 +set_postponed_number (struct state *state, int v, int n)
\r
272 + /* parse previous postponed number, if any */
\r
273 + r = handle_postponed_number (state);
\r
277 + state->postponed_length = n;
\r
278 + state->postponed_value = v;
\r
284 +set_delim (struct state *state, char delim)
\r
286 + state->delim = delim;
\r
290 +unset_delim (struct state *state)
\r
292 + state->delim = 0;
\r
296 + * Field set/get/mod helpers.
\r
299 +/* returns unset for non-tracked fields */
\r
301 +is_field_set (struct state *state, enum field field)
\r
303 + assert (field < ARRAY_SIZE (state->tm));
\r
305 + return field < ARRAY_SIZE (state->set) &&
\r
306 + state->set[field] != FIELD_UNSET;
\r
310 +unset_field (struct state *state, enum field field)
\r
312 + assert (field < ARRAY_SIZE (state->tm));
\r
314 + state->set[field] = FIELD_UNSET;
\r
315 + state->tm[field] = 0;
\r
318 +/* Set field to value. */
\r
320 +set_field (struct state *state, enum field field, int value)
\r
324 + assert (field < ARRAY_SIZE (state->tm));
\r
326 + /* some fields can only be set once */
\r
327 + if (field < ARRAY_SIZE (state->set) && state->set[field] != FIELD_UNSET)
\r
328 + return -PARSE_TIME_ERR_ALREADYSET;
\r
330 + state->set[field] = FIELD_SET;
\r
333 + * REVISIT: There could be a "next_field" that would be set from
\r
334 + * "field" for the duration of the handle_postponed_number() call,
\r
335 + * so it has more information to work with.
\r
338 + /* parse postponed number, if any */
\r
339 + r = handle_postponed_number (state);
\r
343 + unset_delim (state);
\r
345 + state->tm[field] = value;
\r
346 + state->last_field = field;
\r
352 + * Mark n fields in fields to be set to current date/time in the
\r
353 + * specified time zone, or local timezone if not specified. The fields
\r
354 + * will be initialized after parsing is complete and timezone is
\r
358 +set_fields_to_now (struct state *state, enum field *fields, size_t n)
\r
363 + for (i = 0; i < n; i++) {
\r
364 + r = set_field (state, fields[i], 0);
\r
367 + state->set[fields[i]] = FIELD_NOW;
\r
373 +/* Modify field by adding value to it. To be used on relative fields. */
\r
375 +mod_field (struct state *state, enum field field, int value)
\r
379 + assert (field < ARRAY_SIZE (state->tm)); /* assert relative??? */
\r
381 + if (field < ARRAY_SIZE (state->set))
\r
382 + state->set[field] = FIELD_SET;
\r
384 + /* parse postponed number, if any */
\r
385 + r = handle_postponed_number (state);
\r
389 + unset_delim (state);
\r
391 + state->tm[field] += value;
\r
392 + state->last_field = field;
\r
398 + * Get field value. Make sure the field is set before query. It's most
\r
399 + * likely an error to call this while parsing (for example fields set
\r
400 + * as FIELD_NOW will only be set to some value after parsing).
\r
403 +get_field (struct state *state, enum field field)
\r
405 + assert (field < ARRAY_SIZE (state->tm));
\r
407 + return state->tm[field];
\r
410 +/* Unset indicator for time and date set helpers. */
\r
413 +/* Time set helper. No input checking. Use UNSET (-1) to leave unset. */
\r
415 +set_abs_time (struct state *state, int hour, int min, int sec)
\r
419 + if (hour != UNSET) {
\r
420 + if ((r = set_field (state, TM_ABS_HOUR, hour)))
\r
424 + if (min != UNSET) {
\r
425 + if ((r = set_field (state, TM_ABS_MIN, min)))
\r
429 + if (sec != UNSET) {
\r
430 + if ((r = set_field (state, TM_ABS_SEC, sec)))
\r
437 +/* Date set helper. No input checking. Use UNSET (-1) to leave unset. */
\r
439 +set_abs_date (struct state *state, int year, int mon, int mday)
\r
443 + if (year != UNSET) {
\r
444 + if ((r = set_field (state, TM_ABS_YEAR, year)))
\r
448 + if (mon != UNSET) {
\r
449 + if ((r = set_field (state, TM_ABS_MON, mon)))
\r
453 + if (mday != UNSET) {
\r
454 + if ((r = set_field (state, TM_ABS_MDAY, mday)))
\r
462 + * Keyword parsing and handling.
\r
465 +typedef int (*setter_t)(struct state *state, struct keyword *kw);
\r
468 + const char *name; /* keyword */
\r
469 + size_t minlen; /* min length to match, 0 = must match all */
\r
470 + enum field field; /* field to set, or FIELD_NONE if N/A */
\r
471 + int value; /* value to set, or 0 if N/A */
\r
472 + setter_t set; /* function to use for setting, if non-NULL */
\r
476 + * Setter callback functions for keywords.
\r
479 +kw_set_default (struct state *state, struct keyword *kw)
\r
481 + return set_field (state, kw->field, kw->value);
\r
485 +kw_set_rel (struct state *state, struct keyword *kw)
\r
487 + int multiplier = 1;
\r
489 + /* get a previously set multiplier, if any */
\r
490 + get_postponed_number (state, &multiplier, NULL);
\r
492 + /* accumulate relative field values */
\r
493 + return mod_field (state, kw->field, multiplier * kw->value);
\r
497 +kw_set_number (struct state *state, struct keyword *kw)
\r
499 + /* -1 = no length, from keyword */
\r
500 + return set_postponed_number (state, kw->value, -1);
\r
504 +kw_set_month (struct state *state, struct keyword *kw)
\r
506 + int n = get_postponed_length (state);
\r
508 + /* consume postponed number if it could be mday */
\r
509 + if (n == 1 || n == 2) {
\r
512 + get_postponed_number (state, &v, NULL);
\r
514 + if (v < 1 || v > 31)
\r
515 + return -PARSE_TIME_ERR_INVALIDDATE;
\r
517 + r = set_field (state, TM_ABS_MDAY, v);
\r
522 + return set_field (state, kw->field, kw->value);
\r
526 +kw_set_ampm (struct state *state, struct keyword *kw)
\r
528 + int n = get_postponed_length (state);
\r
530 + /* consume postponed number if it could be hour */
\r
531 + if (n == 1 || n == 2) {
\r
534 + get_postponed_number (state, &v, NULL);
\r
536 + if (v < 1 || v > 12)
\r
537 + return -PARSE_TIME_ERR_INVALIDTIME;
\r
539 + r = set_abs_time (state, v, 0, 0);
\r
544 + return set_field (state, kw->field, kw->value);
\r
548 +kw_set_timeofday (struct state *state, struct keyword *kw)
\r
550 + return set_abs_time (state, kw->value, 0, 0);
\r
554 +kw_set_today (struct state *state, struct keyword *kw)
\r
556 + enum field fields[] = { TM_ABS_YEAR, TM_ABS_MON, TM_ABS_MDAY };
\r
558 + return set_fields_to_now (state, fields, ARRAY_SIZE (fields));
\r
562 +kw_set_now (struct state *state, struct keyword *kw)
\r
564 + enum field fields[] = { TM_ABS_HOUR, TM_ABS_MIN, TM_ABS_SEC };
\r
566 + return set_fields_to_now (state, fields, ARRAY_SIZE (fields));
\r
570 +kw_set_ordinal (struct state *state, struct keyword *kw)
\r
574 + /* require a postponed number */
\r
575 + if (!get_postponed_number (state, &v, &n))
\r
576 + return -PARSE_TIME_ERR_DATEFORMAT;
\r
578 + /* ordinals are mday */
\r
579 + if (n != 1 && n != 2)
\r
580 + return -PARSE_TIME_ERR_DATEFORMAT;
\r
582 + /* be strict about st, nd, rd, and lax about th */
\r
583 + if (strcasecmp (kw->name, "st") == 0 && v != 1 && v != 21 && v != 31)
\r
584 + return -PARSE_TIME_ERR_INVALIDDATE;
\r
585 + else if (strcasecmp (kw->name, "nd") == 0 && v != 2 && v != 22)
\r
586 + return -PARSE_TIME_ERR_INVALIDDATE;
\r
587 + else if (strcasecmp (kw->name, "rd") == 0 && v != 3 && v != 23)
\r
588 + return -PARSE_TIME_ERR_INVALIDDATE;
\r
589 + else if (strcasecmp (kw->name, "th") == 0 && (v < 1 || v > 31))
\r
590 + return -PARSE_TIME_ERR_INVALIDDATE;
\r
592 + return set_field (state, TM_ABS_MDAY, v);
\r
596 + * Accepted keywords.
\r
598 + * If keyword begins with upper case letter, then the matching will be
\r
599 + * case sensitive. Otherwise the matching is case insensitive.
\r
601 + * If setter is NULL, set_default will be used.
\r
603 + * Note: Order matters. Matching is greedy, longest match is used, but
\r
604 + * of equal length matches the first one is used.
\r
606 +static struct keyword keywords[] = {
\r
608 + { "sunday", 3, TM_ABS_WDAY, 0, NULL },
\r
609 + { "monday", 3, TM_ABS_WDAY, 1, NULL },
\r
610 + { "tuesday", 3, TM_ABS_WDAY, 2, NULL },
\r
611 + { "wednesday", 3, TM_ABS_WDAY, 3, NULL },
\r
612 + { "thursday", 3, TM_ABS_WDAY, 4, NULL },
\r
613 + { "friday", 3, TM_ABS_WDAY, 5, NULL },
\r
614 + { "saturday", 3, TM_ABS_WDAY, 6, NULL },
\r
617 + { "january", 3, TM_ABS_MON, 1, kw_set_month },
\r
618 + { "february", 3, TM_ABS_MON, 2, kw_set_month },
\r
619 + { "march", 3, TM_ABS_MON, 3, kw_set_month },
\r
620 + { "april", 3, TM_ABS_MON, 4, kw_set_month },
\r
621 + { "may", 3, TM_ABS_MON, 5, kw_set_month },
\r
622 + { "june", 3, TM_ABS_MON, 6, kw_set_month },
\r
623 + { "july", 3, TM_ABS_MON, 7, kw_set_month },
\r
624 + { "august", 3, TM_ABS_MON, 8, kw_set_month },
\r
625 + { "september", 3, TM_ABS_MON, 9, kw_set_month },
\r
626 + { "october", 3, TM_ABS_MON, 10, kw_set_month },
\r
627 + { "november", 3, TM_ABS_MON, 11, kw_set_month },
\r
628 + { "december", 3, TM_ABS_MON, 12, kw_set_month },
\r
631 + { "years", 1, TM_REL_YEAR, 1, kw_set_rel },
\r
632 + { "weeks", 1, TM_REL_WEEK, 1, kw_set_rel },
\r
633 + { "days", 1, TM_REL_DAY, 1, kw_set_rel },
\r
634 + { "hours", 1, TM_REL_HOUR, 1, kw_set_rel },
\r
635 + { "hrs", 1, TM_REL_HOUR, 1, kw_set_rel },
\r
636 + /* M=months, m=minutes. single M must precede minutes in the list. */
\r
637 + { "M", 1, TM_REL_MON, 1, kw_set_rel },
\r
638 + { "minutes", 1, TM_REL_MIN, 1, kw_set_rel },
\r
639 + { "mins", 1, TM_REL_MIN, 1, kw_set_rel },
\r
640 + { "months", 1, TM_REL_MON, 1, kw_set_rel },
\r
641 + { "seconds", 1, TM_REL_SEC, 1, kw_set_rel },
\r
642 + { "secs", 1, TM_REL_SEC, 1, kw_set_rel },
\r
645 + { "one", 0, TM_NONE, 1, kw_set_number },
\r
646 + { "two", 0, TM_NONE, 2, kw_set_number },
\r
647 + { "three", 0, TM_NONE, 3, kw_set_number },
\r
648 + { "four", 0, TM_NONE, 4, kw_set_number },
\r
649 + { "five", 0, TM_NONE, 5, kw_set_number },
\r
650 + { "six", 0, TM_NONE, 6, kw_set_number },
\r
651 + { "seven", 0, TM_NONE, 7, kw_set_number },
\r
652 + { "eight", 0, TM_NONE, 8, kw_set_number },
\r
653 + { "nine", 0, TM_NONE, 9, kw_set_number },
\r
654 + { "ten", 0, TM_NONE, 10, kw_set_number },
\r
655 + { "dozen", 0, TM_NONE, 12, kw_set_number },
\r
656 + { "hundred", 0, TM_NONE, 100, kw_set_number },
\r
658 + /* special number forms */
\r
659 + { "this", 0, TM_NONE, 0, kw_set_number },
\r
660 + { "last", 0, TM_NONE, 1, kw_set_number },
\r
663 + { "yesterday", 0, TM_REL_DAY, 1, kw_set_rel },
\r
664 + { "today", 0, TM_NONE, 0, kw_set_today },
\r
665 + { "now", 0, TM_NONE, 0, kw_set_now },
\r
666 + { "noon", 0, TM_NONE, 12, kw_set_timeofday },
\r
667 + { "midnight", 0, TM_NONE, 0, kw_set_timeofday },
\r
668 + { "am", 0, TM_AMPM, 0, kw_set_ampm },
\r
669 + { "a.m.", 0, TM_AMPM, 0, kw_set_ampm },
\r
670 + { "pm", 0, TM_AMPM, 1, kw_set_ampm },
\r
671 + { "p.m.", 0, TM_AMPM, 1, kw_set_ampm },
\r
672 + { "st", 0, TM_NONE, 0, kw_set_ordinal },
\r
673 + { "nd", 0, TM_NONE, 0, kw_set_ordinal },
\r
674 + { "rd", 0, TM_NONE, 0, kw_set_ordinal },
\r
675 + { "th", 0, TM_NONE, 0, kw_set_ordinal },
\r
677 + /* timezone codes: offset in minutes. FIXME: add more codes. */
\r
678 + { "pst", 0, TM_TZ, -8*60, NULL },
\r
679 + { "mst", 0, TM_TZ, -7*60, NULL },
\r
680 + { "cst", 0, TM_TZ, -6*60, NULL },
\r
681 + { "est", 0, TM_TZ, -5*60, NULL },
\r
682 + { "ast", 0, TM_TZ, -4*60, NULL },
\r
683 + { "nst", 0, TM_TZ, -(3*60+30), NULL },
\r
685 + { "gmt", 0, TM_TZ, 0, NULL },
\r
686 + { "utc", 0, TM_TZ, 0, NULL },
\r
688 + { "wet", 0, TM_TZ, 0, NULL },
\r
689 + { "cet", 0, TM_TZ, 1*60, NULL },
\r
690 + { "eet", 0, TM_TZ, 2*60, NULL },
\r
691 + { "fet", 0, TM_TZ, 3*60, NULL },
\r
693 + { "wat", 0, TM_TZ, 1*60, NULL },
\r
694 + { "cat", 0, TM_TZ, 2*60, NULL },
\r
695 + { "eat", 0, TM_TZ, 3*60, NULL },
\r
699 + * Compare strings s and keyword. Return number of matching chars on
\r
700 + * match, 0 for no match. Match must be at least n chars (n == 0 all
\r
701 + * of keyword), otherwise it's not a match. Use match_case for case
\r
702 + * sensitive matching.
\r
705 +stringcmp (const char *s, const char *keyword, size_t n, bool match_case)
\r
709 + for (i = 0; *s && *keyword; i++, s++, keyword++) {
\r
710 + if (match_case) {
\r
711 + if (*s != *keyword)
\r
714 + if (tolower ((unsigned char) *s) !=
\r
715 + tolower ((unsigned char) *keyword))
\r
721 + return i < n ? 0 : i;
\r
723 + return *keyword ? 0 : i;
\r
727 + * Parse a keyword. Return < 0 on error, number of parsed chars on
\r
731 +parse_keyword (struct state *state, const char *s)
\r
734 + size_t n, max_n = 0;
\r
735 + struct keyword *kw = NULL;
\r
738 + /* Match longest keyword */
\r
739 + for (i = 0; i < ARRAY_SIZE (keywords); i++) {
\r
740 + /* Match case if keyword begins with upper case letter. */
\r
741 + bool mcase = isupper ((unsigned char) keywords[i].name[0]);
\r
743 + n = stringcmp (s, keywords[i].name, keywords[i].minlen, mcase);
\r
746 + kw = &keywords[i];
\r
751 + return -PARSE_TIME_ERR_KEYWORD;
\r
754 + r = kw->set (state, kw);
\r
756 + r = kw_set_default (state, kw);
\r
758 + return r < 0 ? r : max_n;
\r
762 + * Non-keyword parsers and their helpers.
\r
766 +set_user_tz (struct state *state, char sign, int hour, int min)
\r
768 + int tz = hour * 60 + min;
\r
770 + assert (sign == '+' || sign == '-');
\r
772 + if (hour < 0 || hour > 14 || min < 0 || min > 60 || min % 15)
\r
773 + return -PARSE_TIME_ERR_INVALIDTIME;
\r
778 + return set_field (state, TM_TZ, tz);
\r
782 + * Independent parsing of a postponed number when it wasn't consumed
\r
783 + * during parsing of the following token.
\r
785 + * This should be able to trust that last_field and next_field are
\r
789 +parse_postponed_number (struct state *state, int v, int n)
\r
792 + * alright, these are really lone, won't affect parsing of
\r
793 + * following items... it's not a multiplier, those have been eaten
\r
796 + * also note numbers eaten away by parse_single_number.
\r
804 + /* hour or mday or year */
\r
805 + if (state->last_field == TM_ABS_MON && /* FIXME: written mon! */
\r
806 + !is_field_set (state, TM_ABS_MDAY)) {
\r
807 + return set_field (state, TM_ABS_MDAY, v);
\r
811 + /* YYYY or +/-HHMM for TZ or HHMM or DDMM */
\r
812 + /* FIXME: state->delim is no longer right for this function!
\r
813 + * why not, it could be! */
\r
814 + if (!is_field_set (state, TM_ABS_YEAR)) {
\r
815 + /* FIXME: check year? */
\r
816 + return set_field (state, TM_ABS_YEAR, v);
\r
820 + /* FIXME: HHMMSS or DDMMYY */
\r
832 + return -PARSE_TIME_ERR_FORMAT;
\r
835 +/* Parse a single number. Typically postpone parsing until later. */
\r
837 +parse_single_number (struct state *state, unsigned long v,
\r
842 + /* parse things that can be parsed immediately */
\r
845 + int year = v / 10000;
\r
846 + int mon = (v / 100) % 100;
\r
847 + int mday = v % 100;
\r
849 + if (year < 1970 || mon < 1 || mon > 12 || mday < 1 || mday > 31)
\r
850 + return -PARSE_TIME_ERR_INVALIDDATE;
\r
852 + return set_abs_date (state, year, mon, mday);
\r
853 + } else if (n > 8) {
\r
854 + /* FIXME: seconds since epoch */
\r
855 + return -PARSE_TIME_ERR_FORMAT;
\r
859 + return -PARSE_TIME_ERR_FORMAT;
\r
861 + return set_postponed_number (state, v, n);
\r
865 +is_time_sep (char c)
\r
871 +is_date_sep (char c)
\r
873 + return c == '/' || c == '-' || c == '.';
\r
879 + return is_time_sep (c) || is_date_sep (c);
\r
882 +/* two-digit year: 00...69 is 2000s, 70...99 1900s, if n == 0 keep unset */
\r
884 +expand_year (unsigned long year, size_t n)
\r
887 + return (year < 70 ? 2000 : 1900) + year;
\r
888 + } else if (n == 4) {
\r
896 +parse_date (struct state *state, char sep,
\r
897 + unsigned long v1, unsigned long v2, unsigned long v3,
\r
898 + size_t n1, size_t n2, size_t n3)
\r
900 + int year = UNSET, mon = UNSET, mday = UNSET;
\r
902 + assert (is_date_sep (sep));
\r
905 + case '/': /* Date: M[M]/D[D][/YY[YY]] or M[M]/YYYY */
\r
906 + if (n1 != 1 && n1 != 2)
\r
907 + return -PARSE_TIME_ERR_DATEFORMAT;
\r
909 + if ((n2 == 1 || n2 == 2) && (n3 == 0 || n3 == 2 || n3 == 4)) {
\r
910 + /* M[M]/D[D][/YY[YY]] */
\r
911 + year = expand_year (v3, n3);
\r
914 + } else if (n2 == 4 && n3 == 0) {
\r
919 + return -PARSE_TIME_ERR_DATEFORMAT;
\r
923 + case '-': /* Date: YYYY-MM[-DD] or DD-MM[-YY[YY]] or MM-YYYY */
\r
924 + if (n1 == 4 && n2 == 2 && (n3 == 0 || n3 == 2)) {
\r
925 + /* YYYY-MM[-DD] */
\r
930 + } else if (n1 == 2 && n2 == 2 && (n3 == 0 || n3 == 2 || n3 == 4)) {
\r
931 + /* DD-MM[-YY[YY]] */
\r
932 + year = expand_year (v3, n3);
\r
935 + } else if (n1 == 2 && n2 == 4 && n3 == 0) {
\r
940 + return -PARSE_TIME_ERR_DATEFORMAT;
\r
944 + case '.': /* Date: D[D].M[M][.[YY[YY]]] */
\r
945 + if ((n1 != 1 && n1 != 2) || (n2 != 1 && n2 != 2) ||
\r
946 + (n3 != 0 && n3 != 2 && n3 != 4))
\r
947 + return -PARSE_TIME_ERR_DATEFORMAT;
\r
949 + year = expand_year (v3, n3);
\r
955 + if (year != UNSET && year < 1970)
\r
956 + return -PARSE_TIME_ERR_INVALIDDATE;
\r
958 + if (mon != UNSET && (mon < 1 || mon > 12))
\r
959 + return -PARSE_TIME_ERR_INVALIDDATE;
\r
961 + if (mday != UNSET && (mday < 1 || mday > 31))
\r
962 + return -PARSE_TIME_ERR_INVALIDDATE;
\r
964 + return set_abs_date (state, year, mon, mday);
\r
968 +parse_time (struct state *state, char sep,
\r
969 + unsigned long v1, unsigned long v2, unsigned long v3,
\r
970 + size_t n1, size_t n2, size_t n3)
\r
972 + assert (is_time_sep (sep));
\r
974 + if ((n1 != 1 && n1 != 2) || n2 != 2 || (n3 != 0 && n3 != 2))
\r
975 + return -PARSE_TIME_ERR_TIMEFORMAT;
\r
978 + * REVISIT: this means it's required to set time *before* being
\r
979 + * able to set timezone
\r
981 + if (is_field_set (state, TM_ABS_HOUR) &&
\r
982 + is_field_set (state, TM_ABS_MIN) &&
\r
983 + n1 == 2 && n2 == 2 && n3 == 0 &&
\r
984 + (state->delim == '+' || state->delim == '-')) {
\r
985 + return set_user_tz (state, state->delim, v1, v2);
\r
988 + if (v1 > 24 || v2 > 60 || v3 > 60)
\r
989 + return -PARSE_TIME_ERR_INVALIDTIME;
\r
991 + return set_abs_time (state, v1, v2, n3 ? v3 : 0);
\r
994 +/* strtoul helper that assigns length */
\r
995 +static unsigned long
\r
996 +strtoul_len (const char *s, const char **endp, size_t *len)
\r
998 + unsigned long val = strtoul (s, (char **) endp, 10);
\r
1000 + *len = *endp - s;
\r
1005 + * Parse a (group of) number(s). Return < 0 on error, number of parsed
\r
1006 + * chars on success.
\r
1009 +parse_number (struct state *state, const char *s)
\r
1012 + unsigned long v1, v2, v3 = 0;
\r
1013 + size_t n1, n2, n3 = 0;
\r
1014 + const char *p = s;
\r
1017 + v1 = strtoul_len (p, &p, &n1);
\r
1019 + if (is_sep (*p) && isdigit ((unsigned char) *(p + 1))) {
\r
1021 + v2 = strtoul_len (p + 1, &p, &n2);
\r
1023 + /* a single number */
\r
1024 + r = parse_single_number (state, v1, n1);
\r
1031 + /* a group of two or three numbers? */
\r
1032 + if (*p == sep && isdigit ((unsigned char) *(p + 1)))
\r
1033 + v3 = strtoul_len (p + 1, &p, &n3);
\r
1035 + if (is_time_sep (sep))
\r
1036 + r = parse_time (state, sep, v1, v2, v3, n1, n2, n3);
\r
1038 + r = parse_date (state, sep, v1, v2, v3, n1, n2, n3);
\r
1047 + * Parse delimiter(s). Return < 0 on error, number of parsed chars on
\r
1051 +parse_delim (struct state *state, const char *s)
\r
1053 + const char *p = s;
\r
1056 + * REVISIT: any actions depending on the first delim after last
\r
1057 + * field? what could it be?
\r
1061 + * skip non-alpha and non-digit, and store the last for further
\r
1064 + while (*p && !isalnum ((unsigned char) *p)) {
\r
1065 + set_delim (state, *p);
\r
1073 + * Parse a date/time string. Return < 0 on error, number of parsed
\r
1074 + * chars on success.
\r
1077 +parse_input (struct state *state, const char *s)
\r
1079 + const char *p = s;
\r
1084 + if (isalpha ((unsigned char) *p)) {
\r
1085 + n = parse_keyword (state, p);
\r
1086 + } else if (isdigit ((unsigned char) *p)) {
\r
1087 + n = parse_number (state, p);
\r
1089 + n = parse_delim (state, p);
\r
1094 + n = -PARSE_TIME_ERR;
\r
1096 + return n; /* FIXME */
\r
1102 + /* parse postponed number, if any */
\r
1103 + r = handle_postponed_number (state);
\r
1111 + * Processing the parsed input.
\r
1115 + * Initialize reference time to tm. Use time zone in state if
\r
1116 + * specified, otherwise local time. Use now for reference time if
\r
1117 + * non-NULL, otherwise current time.
\r
1120 +initialize_now (struct state *state, struct tm *tm, const time_t *now)
\r
1127 + if (time (&t) == (time_t) -1)
\r
1128 + return -PARSE_TIME_ERR_LIB;
\r
1131 + if (is_field_set (state, TM_TZ)) {
\r
1132 + /* some other time zone */
\r
1134 + /* adjust now according to the TZ */
\r
1135 + t += get_field (state, TM_TZ) * 60;
\r
1137 + /* it's not gm, but this doesn't mess with the tz */
\r
1138 + if (gmtime_r (&t, tm) == NULL)
\r
1139 + return -PARSE_TIME_ERR_LIB;
\r
1141 + /* local time */
\r
1142 + if (localtime_r (&t, tm) == NULL)
\r
1143 + return -PARSE_TIME_ERR_LIB;
\r
1150 + * Normalize tm according to mktime(3). Both mktime(3) and
\r
1151 + * localtime_r(3) use local time, but they cancel each other out here,
\r
1152 + * making this function agnostic to time zone.
\r
1155 +normalize_tm (struct tm *tm)
\r
1157 + time_t t = mktime (tm);
\r
1159 + if (t == (time_t) -1)
\r
1160 + return -PARSE_TIME_ERR_LIB;
\r
1162 + if (!localtime_r (&t, tm))
\r
1163 + return -PARSE_TIME_ERR_LIB;
\r
1168 +/* Get field out of a struct tm. */
\r
1170 +tm_get_field (const struct tm *tm, enum field field)
\r
1172 + switch (field) {
\r
1173 + case TM_ABS_SEC: return tm->tm_sec;
\r
1174 + case TM_ABS_MIN: return tm->tm_min;
\r
1175 + case TM_ABS_HOUR: return tm->tm_hour;
\r
1176 + case TM_ABS_MDAY: return tm->tm_mday;
\r
1177 + case TM_ABS_MON: return tm->tm_mon + 1; /* 0- to 1-based */
\r
1178 + case TM_ABS_YEAR: return 1900 + tm->tm_year;
\r
1179 + case TM_ABS_WDAY: return tm->tm_wday;
\r
1180 + case TM_ABS_ISDST: return tm->tm_isdst;
\r
1189 +/* Modify hour according to am/pm setting. */
\r
1191 +fixup_ampm (struct state *state)
\r
1193 + int hour, hdiff = 0;
\r
1195 + if (!is_field_set (state, TM_AMPM))
\r
1198 + if (!is_field_set (state, TM_ABS_HOUR))
\r
1199 + return -PARSE_TIME_ERR_TIMEFORMAT;
\r
1201 + hour = get_field (state, TM_ABS_HOUR);
\r
1202 + if (hour < 1 || hour > 12)
\r
1203 + return -PARSE_TIME_ERR_INVALIDTIME;
\r
1205 + if (get_field (state, TM_AMPM)) {
\r
1206 + /* 12pm is noon */
\r
1210 + /* 12am is midnight, beginning of day */
\r
1215 + mod_field (state, TM_REL_HOUR, -hdiff);
\r
1220 +/* Combine absolute and relative fields, and round. */
\r
1222 +create_output (struct state *state, time_t *t_out, const time_t *tnow,
\r
1225 + struct tm tm = { 0 };
\r
1229 + int week_round = PARSE_TIME_NO_ROUND;
\r
1231 + r = initialize_now (state, &now, tnow);
\r
1235 + /* initialize uninitialized fields to now */
\r
1236 + for (f = TM_ABS_SEC; f != TM_NONE; f = next_field (f)) {
\r
1237 + if (state->set[f] == FIELD_NOW) {
\r
1238 + state->tm[f] = tm_get_field (&now, f);
\r
1239 + state->set[f] = FIELD_SET;
\r
1244 + * If MON is set but YEAR is not, refer to past month.
\r
1246 + * REVISIT: Why are month/week special in this regard? What about
\r
1247 + * mday, or time. Should refer to past.
\r
1249 + if (is_field_set (state, TM_ABS_MON) &&
\r
1250 + !is_field_set (state, TM_ABS_YEAR)) {
\r
1251 + if (get_field (state, TM_ABS_MON) >= tm_get_field (&now, TM_ABS_MON))
\r
1252 + mod_field (state, TM_REL_YEAR, 1);
\r
1256 + * If WDAY is set but MDAY is not, we consider WDAY relative
\r
1258 + * REVISIT: This fails on stuff like "two months ago monday"
\r
1259 + * because two months ago wasn't the same day as today. Postpone
\r
1260 + * until we know date?
\r
1262 + if (is_field_set (state, TM_ABS_WDAY) &&
\r
1263 + !is_field_set (state, TM_ABS_MDAY)) {
\r
1264 + int wday = get_field (state, TM_ABS_WDAY);
\r
1265 + int today = tm_get_field (&now, TM_ABS_WDAY);
\r
1268 + if (today > wday)
\r
1269 + rel_days = today - wday;
\r
1271 + rel_days = today + 7 - wday;
\r
1273 + /* this also prevents special week rounding from happening */
\r
1274 + mod_field (state, TM_REL_DAY, rel_days);
\r
1276 + unset_field (state, TM_ABS_WDAY);
\r
1279 + r = fixup_ampm (state);
\r
1284 + * Iterate fields from least accurate to most accurate, and set
\r
1285 + * unset fields according to requested rounding.
\r
1287 + for (f = TM_ABS_SEC; f != TM_NONE; f = next_field (f)) {
\r
1288 + if (round != PARSE_TIME_NO_ROUND) {
\r
1289 + enum field r = abs_to_rel_field (f);
\r
1291 + if (is_field_set (state, f) || is_field_set (state, r)) {
\r
1292 + if (round >= PARSE_TIME_ROUND_UP)
\r
1293 + mod_field (state, r, -1);
\r
1294 + round = PARSE_TIME_NO_ROUND; /* no more rounding */
\r
1296 + if (f == TM_ABS_MDAY &&
\r
1297 + is_field_set (state, TM_REL_WEEK)) {
\r
1298 + /* week is most accurate */
\r
1299 + week_round = round;
\r
1300 + round = PARSE_TIME_NO_ROUND;
\r
1302 + set_field (state, f, field_zero (f));
\r
1307 + if (!is_field_set (state, f))
\r
1308 + set_field (state, f, tm_get_field (&now, f));
\r
1311 + /* special case: rounding with week accuracy */
\r
1312 + if (week_round != PARSE_TIME_NO_ROUND) {
\r
1313 + /* temporarily set more accurate fields to now */
\r
1314 + set_field (state, TM_ABS_SEC, tm_get_field (&now, TM_ABS_SEC));
\r
1315 + set_field (state, TM_ABS_MIN, tm_get_field (&now, TM_ABS_MIN));
\r
1316 + set_field (state, TM_ABS_HOUR, tm_get_field (&now, TM_ABS_HOUR));
\r
1317 + set_field (state, TM_ABS_MDAY, tm_get_field (&now, TM_ABS_MDAY));
\r
1321 + * set all fields. they may contain out of range values before
\r
1322 + * normalization by mktime(3).
\r
1324 + tm.tm_sec = get_field (state, TM_ABS_SEC) - get_field (state, TM_REL_SEC);
\r
1325 + tm.tm_min = get_field (state, TM_ABS_MIN) - get_field (state, TM_REL_MIN);
\r
1326 + tm.tm_hour = get_field (state, TM_ABS_HOUR) - get_field (state, TM_REL_HOUR);
\r
1327 + tm.tm_mday = get_field (state, TM_ABS_MDAY) -
\r
1328 + get_field (state, TM_REL_DAY) - 7 * get_field (state, TM_REL_WEEK);
\r
1329 + tm.tm_mon = get_field (state, TM_ABS_MON) - get_field (state, TM_REL_MON);
\r
1330 + tm.tm_mon--; /* 1- to 0-based */
\r
1331 + tm.tm_year = get_field (state, TM_ABS_YEAR) - get_field (state, TM_REL_YEAR) - 1900;
\r
1334 + * It's always normal time.
\r
1336 + * REVISIT: This is probably not a solution that universally
\r
1337 + * works. Just make sure DST is not taken into account. We don't
\r
1338 + * want rounding to be affected by DST.
\r
1340 + tm.tm_isdst = -1;
\r
1342 + /* special case: rounding with week accuracy */
\r
1343 + if (week_round != PARSE_TIME_NO_ROUND) {
\r
1344 + /* normalize to get proper tm.wday */
\r
1345 + r = normalize_tm (&tm);
\r
1349 + /* set more accurate fields back to zero */
\r
1353 + tm.tm_isdst = -1;
\r
1355 + /* monday is the true 1st day of week, but this is easier */
\r
1356 + if (week_round <= PARSE_TIME_ROUND_DOWN)
\r
1357 + tm.tm_mday -= tm.tm_wday;
\r
1359 + tm.tm_mday += 7 - tm.tm_wday;
\r
1362 + /* if TZ specified, convert from TZ to local time for mktime(3) */
\r
1363 + if (is_field_set (state, TM_TZ)) {
\r
1364 + time_t t = mktime (&tm);
\r
1366 + /* from specified TZ to UTC */
\r
1367 + tm.tm_min -= get_field (state, TM_TZ);
\r
1369 + /* from UTC to local TZ (yes, it's hacky - FIXME) */
\r
1370 + tm.tm_sec += difftime (mktime (localtime (&t)), mktime (gmtime (&t)));
\r
1373 + /* FIXME: check return value, don't set if fail */
\r
1374 + *t_out = mktime (&tm);
\r
1379 +/* internally, all errors are < 0. parse_time_string() returns errors > 0. */
\r
1380 +#define EXTERNAL_ERR(r) (-r)
\r
1383 +parse_time_string (const char *s, time_t *t, const time_t *now, int round)
\r
1385 + struct state state = { { 0 } };
\r
1389 + return EXTERNAL_ERR (-PARSE_TIME_ERR);
\r
1391 + r = parse_input (&state, s);
\r
1393 + return EXTERNAL_ERR (r);
\r
1395 + r = create_output (&state, t, now, round);
\r
1397 + return EXTERNAL_ERR (r);
\r
1401 diff --git a/lib/parse-time-string.h b/lib/parse-time-string.h
\r
1402 new file mode 100644
\r
1403 index 0000000..50b7c6f
\r
1405 +++ b/lib/parse-time-string.h
\r
1408 + * parse time string - user friendly date and time parser
\r
1409 + * Copyright © 2012 Jani Nikula
\r
1411 + * This program is free software: you can redistribute it and/or modify
\r
1412 + * it under the terms of the GNU General Public License as published by
\r
1413 + * the Free Software Foundation, either version 2 of the License, or
\r
1414 + * (at your option) any later version.
\r
1416 + * This program is distributed in the hope that it will be useful,
\r
1417 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
1418 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
1419 + * GNU General Public License for more details.
\r
1421 + * You should have received a copy of the GNU General Public License
\r
1422 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
\r
1424 + * Author: Jani Nikula <jani@nikula.org>
\r
1427 +#ifndef PARSE_TIME_STRING_H
\r
1428 +#define PARSE_TIME_STRING_H
\r
1430 +#ifdef __cplusplus
\r
1434 +#include <time.h>
\r
1436 +/* return values for parse_time_string() */
\r
1438 + PARSE_TIME_OK = 0,
\r
1439 + PARSE_TIME_ERR, /* unspecified error */
\r
1440 + PARSE_TIME_ERR_LIB, /* library call failed */
\r
1441 + PARSE_TIME_ERR_ALREADYSET, /* attempt to set unit twice */
\r
1442 + PARSE_TIME_ERR_FORMAT, /* generic date/time format error */
\r
1443 + PARSE_TIME_ERR_DATEFORMAT, /* date format error */
\r
1444 + PARSE_TIME_ERR_TIMEFORMAT, /* time format error */
\r
1445 + PARSE_TIME_ERR_INVALIDDATE, /* date value error */
\r
1446 + PARSE_TIME_ERR_INVALIDTIME, /* time value error */
\r
1447 + PARSE_TIME_ERR_KEYWORD, /* unknown keyword */
\r
1450 +/* round values for parse_time_string() */
\r
1452 + PARSE_TIME_ROUND_DOWN = -1,
\r
1453 + PARSE_TIME_NO_ROUND = 0,
\r
1454 + PARSE_TIME_ROUND_UP = 1,
\r
1458 + * parse_time_string() - user friendly date and time parser
\r
1459 + * @s: string to parse
\r
1460 + * @t: pointer to time_t to store parsed time in
\r
1461 + * @now: pointer to time_t containing reference date/time, or NULL
\r
1462 + * @round: PARSE_TIME_NO_ROUND, PARSE_TIME_ROUND_DOWN, or
\r
1463 + * PARSE_TIME_ROUND_UP
\r
1465 + * Parse a date/time string 's' and store the parsed date/time result
\r
1468 + * A reference date/time is used for determining the "date/time units"
\r
1469 + * (roughly equivalent to struct tm members) not specified by 's'. If
\r
1470 + * 'now' is non-NULL, it must contain a pointer to a time_t to be used
\r
1471 + * as reference date/time. Otherwise, the current time is used.
\r
1473 + * If 's' does not specify a full date/time, the 'round' parameter
\r
1474 + * specifies if and how the result should be rounded as follows:
\r
1476 + * PARSE_TIME_NO_ROUND: All date/time units that are not specified
\r
1477 + * by 's' are set to the corresponding unit derived from the
\r
1478 + * reference date/time.
\r
1480 + * PARSE_TIME_ROUND_DOWN: All date/time units that are more accurate
\r
1481 + * than the most accurate unit specified by 's' are set to the
\r
1482 + * smallest valid value for that unit. Rest of the unspecified units
\r
1483 + * are set as in PARSE_TIME_NO_ROUND.
\r
1485 + * PARSE_TIME_ROUND_UP: All date/time units that are more accurate
\r
1486 + * than the most accurate unit specified by 's' are set to the
\r
1487 + * smallest valid value for that unit. The most accurate unit
\r
1488 + * specified by 's' is incremented by one (and this is rolled over
\r
1489 + * to the less accurate units as necessary). Rest of the unspecified
\r
1490 + * units are set as in PARSE_TIME_NO_ROUND.
\r
1492 + * Return 0 (PARSE_TIME_OK) for succesfully parsed date/time, or one
\r
1493 + * of PARSE_TIME_ERR_* on error. 't' is not modified on error.
\r
1495 +int parse_time_string (const char *s, time_t *t, const time_t *now, int round);
\r
1497 +#ifdef __cplusplus
\r
1501 +#endif /* PARSE_TIME_STRING_H */
\r