1 | /* Parse a string, yielding a struct partime that describes it. */
|
---|
2 |
|
---|
3 | /* Copyright (C) 1993, 1994, 1995, 1997, 2002 Paul Eggert
|
---|
4 | Distributed under license by the Free Software Foundation, Inc.
|
---|
5 |
|
---|
6 | This file is part of RCS.
|
---|
7 |
|
---|
8 | RCS is free software; you can redistribute it and/or modify
|
---|
9 | it under the terms of the GNU General Public License as published by
|
---|
10 | the Free Software Foundation; either version 2, or (at your option)
|
---|
11 | any later version.
|
---|
12 |
|
---|
13 | RCS is distributed in the hope that it will be useful,
|
---|
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
---|
16 | GNU General Public License for more details.
|
---|
17 |
|
---|
18 | You should have received a copy of the GNU General Public License
|
---|
19 | along with RCS; see the file COPYING.
|
---|
20 | If not, write to the Free Software Foundation,
|
---|
21 | 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
---|
22 |
|
---|
23 | Report problems and direct all questions to:
|
---|
24 |
|
---|
25 | rcs-bugs@cs.purdue.edu
|
---|
26 |
|
---|
27 | */
|
---|
28 |
|
---|
29 | #if has_conf_h
|
---|
30 | # include <conf.h>
|
---|
31 | #else
|
---|
32 | # if HAVE_CONFIG_H
|
---|
33 | # include <config.h>
|
---|
34 | # else
|
---|
35 | # ifndef __STDC__
|
---|
36 | # define const
|
---|
37 | # endif
|
---|
38 | # endif
|
---|
39 | # if HAVE_LIMITS_H
|
---|
40 | # include <limits.h>
|
---|
41 | # endif
|
---|
42 | # ifndef LONG_MIN
|
---|
43 | # define LONG_MIN (-1-2147483647L)
|
---|
44 | # endif
|
---|
45 | # if HAVE_STDDEF_H
|
---|
46 | # include <stddef.h>
|
---|
47 | # endif
|
---|
48 | # if STDC_HEADERS
|
---|
49 | # include <stdlib.h>
|
---|
50 | # endif
|
---|
51 | # include <time.h>
|
---|
52 | # ifdef __STDC__
|
---|
53 | # define P(x) x
|
---|
54 | # else
|
---|
55 | # define P(x) ()
|
---|
56 | # endif
|
---|
57 | #endif
|
---|
58 |
|
---|
59 | #ifndef offsetof
|
---|
60 | #define offsetof(aggregate, member) ((size_t) &((aggregate *) 0)->member)
|
---|
61 | #endif
|
---|
62 |
|
---|
63 | #include <ctype.h>
|
---|
64 | #if STDC_HEADERS
|
---|
65 | # define CTYPE_DOMAIN(c) 1
|
---|
66 | #else
|
---|
67 | # define CTYPE_DOMAIN(c) ((unsigned) (c) <= 0177)
|
---|
68 | #endif
|
---|
69 | #define ISALNUM(c) (CTYPE_DOMAIN (c) && isalnum (c))
|
---|
70 | #define ISALPHA(c) (CTYPE_DOMAIN (c) && isalpha (c))
|
---|
71 | #define ISSPACE(c) (CTYPE_DOMAIN (c) && isspace (c))
|
---|
72 | #define ISUPPER(c) (CTYPE_DOMAIN (c) && isupper (c))
|
---|
73 | #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
|
---|
74 |
|
---|
75 | #include <partime.h>
|
---|
76 |
|
---|
77 | char const partime_id[] =
|
---|
78 | "$Id: partime.c,v 1.2 2002/02/18 07:42:58 eggert Exp $";
|
---|
79 |
|
---|
80 |
|
---|
81 | /* Lookup tables for names of months, weekdays, time zones. */
|
---|
82 |
|
---|
83 | #define NAME_LENGTH_MAXIMUM 4
|
---|
84 |
|
---|
85 | struct name_val
|
---|
86 | {
|
---|
87 | char name[NAME_LENGTH_MAXIMUM];
|
---|
88 | int val;
|
---|
89 | };
|
---|
90 |
|
---|
91 |
|
---|
92 | static char const *parse_decimal P ((char const *, int, int, int, int, int *, int *));
|
---|
93 | static char const *parse_fixed P ((char const *, int, int *));
|
---|
94 | static char const *parse_pattern_letter P ((char const *, int, struct partime *));
|
---|
95 | static char const *parse_prefix P ((char const *, char const **, struct partime *));
|
---|
96 | static char const *parse_ranged P ((char const *, int, int, int, int *));
|
---|
97 | static char const *parse_varying P ((char const *, int *));
|
---|
98 | static int lookup P ((char const *, struct name_val const[]));
|
---|
99 | static int merge_partime P ((struct partime *, struct partime const *));
|
---|
100 | static void undefine P ((struct partime *));
|
---|
101 |
|
---|
102 |
|
---|
103 | static struct name_val const month_names[] =
|
---|
104 | {
|
---|
105 | {"jan", 0},
|
---|
106 | {"feb", 1},
|
---|
107 | {"mar", 2},
|
---|
108 | {"apr", 3},
|
---|
109 | {"may", 4},
|
---|
110 | {"jun", 5},
|
---|
111 | {"jul", 6},
|
---|
112 | {"aug", 7},
|
---|
113 | {"sep", 8},
|
---|
114 | {"oct", 9},
|
---|
115 | {"nov", 10},
|
---|
116 | {"dec", 11},
|
---|
117 | {"", TM_UNDEFINED}
|
---|
118 | };
|
---|
119 |
|
---|
120 | static struct name_val const weekday_names[] =
|
---|
121 | {
|
---|
122 | {"sun", 0},
|
---|
123 | {"mon", 1},
|
---|
124 | {"tue", 2},
|
---|
125 | {"wed", 3},
|
---|
126 | {"thu", 4},
|
---|
127 | {"fri", 5},
|
---|
128 | {"sat", 6},
|
---|
129 | {"", TM_UNDEFINED}
|
---|
130 | };
|
---|
131 |
|
---|
132 | #define RELATIVE_CONS(member, multiplier) \
|
---|
133 | (offsetof (struct tm, member) + (multiplier) * sizeof (struct tm))
|
---|
134 | #define RELATIVE_OFFSET(c) ((c) % sizeof (struct tm))
|
---|
135 | #define RELATIVE_MULTIPLIER(c) ((c) / sizeof (struct tm))
|
---|
136 | static struct name_val const relative_units[] =
|
---|
137 | {
|
---|
138 | {"year", RELATIVE_CONS (tm_year, 1) },
|
---|
139 | {"mont", RELATIVE_CONS (tm_mon , 1) },
|
---|
140 | {"fort", RELATIVE_CONS (tm_mday, 14) },
|
---|
141 | {"week", RELATIVE_CONS (tm_mday, 7) },
|
---|
142 | {"day" , RELATIVE_CONS (tm_mday, 1) },
|
---|
143 | {"hour", RELATIVE_CONS (tm_hour, 1) },
|
---|
144 | {"min" , RELATIVE_CONS (tm_min , 1) },
|
---|
145 | {"sec" , RELATIVE_CONS (tm_sec , 1) },
|
---|
146 | {"", TM_UNDEFINED}
|
---|
147 | };
|
---|
148 |
|
---|
149 | static struct name_val const ago[] =
|
---|
150 | {
|
---|
151 | {"ago", 0},
|
---|
152 | {"", TM_UNDEFINED}
|
---|
153 | };
|
---|
154 |
|
---|
155 | static struct name_val const dst_names[] =
|
---|
156 | {
|
---|
157 | {"dst", 1},
|
---|
158 | {"", 0}
|
---|
159 | };
|
---|
160 |
|
---|
161 | #define hr60nonnegative(t) ((t)/100 * 60 + (t)%100)
|
---|
162 | #define hr60(t) ((t) < 0 ? - hr60nonnegative (-(t)) : hr60nonnegative (t))
|
---|
163 | #define zs(t, s) {s, hr60 (t)}
|
---|
164 | #define zd(t, s, d) zs (t, s), zs ((t) + 100, d)
|
---|
165 |
|
---|
166 | static struct name_val const zone_names[] =
|
---|
167 | {
|
---|
168 | zs (-1000, "hst"), /* Hawaii */
|
---|
169 | zd (-1000, "hast", "hadt"), /* Hawaii-Aleutian */
|
---|
170 | zd (- 900, "akst", "akdt"), /* Alaska */
|
---|
171 | zd (- 800, "pst" , "pdt" ), /* Pacific */
|
---|
172 | zd (- 700, "mst" , "mdt" ), /* Mountain */
|
---|
173 | zd (- 600, "cst" , "cdt" ), /* Central */
|
---|
174 | zd (- 500, "est" , "edt" ), /* Eastern */
|
---|
175 | zd (- 400, "ast" , "adt" ), /* Atlantic */
|
---|
176 | zd (- 330, "nst" , "ndt" ), /* Newfoundland */
|
---|
177 | zs ( 000, "utc" ), /* Coordinated Universal */
|
---|
178 | zs ( 000, "uct" ), /* " */
|
---|
179 | zs ( 000, "cut" ), /* " */
|
---|
180 | zs ( 000, "ut"), /* Universal */
|
---|
181 | zs ( 000, "z"), /* Zulu (required by ISO 8601) */
|
---|
182 | zd ( 000, "gmt" , "bst" ), /* Greenwich Mean, British Summer */
|
---|
183 | zd ( 000, "wet" , "west"), /* Western European */
|
---|
184 | zd ( 100, "cet" , "cest"), /* Central European */
|
---|
185 | zd ( 100, "met" , "mest"), /* Middle European (bug in old tz versions) */
|
---|
186 | zd ( 100, "mez" , "mesz"), /* Mittel-Europaeische Zeit */
|
---|
187 | zd ( 200, "eet" , "eest"), /* Eastern European */
|
---|
188 | zs ( 530, "ist" ), /* India */
|
---|
189 | zd ( 900, "jst" , "jdt" ), /* Japan */
|
---|
190 | zd ( 900, "kst" , "kdt" ), /* Korea */
|
---|
191 | zd ( 1200, "nzst", "nzdt"), /* New Zealand */
|
---|
192 | {"lt", 1},
|
---|
193 | #if 0
|
---|
194 | /* The following names are duplicates or are not well attested.
|
---|
195 | It's not worth keeping a complete list, since alphabetic time zone names
|
---|
196 | are deprecated and there are lots more where these came from. */
|
---|
197 | zs (-1100, "sst" ), /* Samoan */
|
---|
198 | zd (- 900, "yst" , "ydt" ), /* Yukon - name is no longer used */
|
---|
199 | zd (- 500, "ast" , "adt" ), /* Acre */
|
---|
200 | zd (- 400, "wst" , "wdt" ), /* Western Brazil */
|
---|
201 | zd (- 400, "cst" , "cdt" ), /* Chile */
|
---|
202 | zd (- 200, "fst" , "fdt" ), /* Fernando de Noronha */
|
---|
203 | zs ( 000, "wat" ), /* West African */
|
---|
204 | zs ( 100, "cat" ), /* Central African */
|
---|
205 | zs ( 200, "sat" ), /* South African */
|
---|
206 | zd ( 200, "ist" , "idt" ), /* Israel */
|
---|
207 | zs ( 300, "eat" ), /* East African */
|
---|
208 | zd ( 300, "msk" , "msd" ), /* Moscow */
|
---|
209 | zd ( 330, "ist" , "idt" ), /* Iran */
|
---|
210 | zs ( 800, "hkt" ), /* Hong Kong */
|
---|
211 | zs ( 800, "sgt" ), /* Singapore */
|
---|
212 | zd ( 800, "cst" , "cdt" ), /* China */
|
---|
213 | zd ( 800, "wst" , "wst" ), /* Western Australia */
|
---|
214 | zd ( 930, "cst" , "cst" ), /* Central Australia */
|
---|
215 | zs ( 1000, "gst" ), /* Guam */
|
---|
216 | zd ( 1000, "est" , "est" ), /* Eastern Australia */
|
---|
217 | #endif
|
---|
218 | {"", -1}
|
---|
219 | };
|
---|
220 |
|
---|
221 | /* Look for a prefix of S in TABLE, returning val for first matching entry. */
|
---|
222 | static int
|
---|
223 | lookup (s, table)
|
---|
224 | char const *s;
|
---|
225 | struct name_val const table[];
|
---|
226 | {
|
---|
227 | int j;
|
---|
228 | char buf[NAME_LENGTH_MAXIMUM];
|
---|
229 |
|
---|
230 | for (j = 0; j < NAME_LENGTH_MAXIMUM; j++)
|
---|
231 | {
|
---|
232 | unsigned char c = *s;
|
---|
233 | if (! ISALPHA (c))
|
---|
234 | {
|
---|
235 | buf[j] = '\0';
|
---|
236 | break;
|
---|
237 | }
|
---|
238 | buf[j] = ISUPPER (c) ? tolower (c) : c;
|
---|
239 | s++;
|
---|
240 | s += *s == '.';
|
---|
241 | }
|
---|
242 |
|
---|
243 | for (;; table++)
|
---|
244 | for (j = 0; ; j++)
|
---|
245 | if (j == NAME_LENGTH_MAXIMUM || ! table[0].name[j])
|
---|
246 | return table[0].val;
|
---|
247 | else if (buf[j] != table[0].name[j])
|
---|
248 | break;
|
---|
249 | }
|
---|
250 |
|
---|
251 |
|
---|
252 | /* Set *T to ``undefined'' values. */
|
---|
253 | static void
|
---|
254 | undefine (t)
|
---|
255 | struct partime *t;
|
---|
256 | {
|
---|
257 | t->tm.tm_sec = t->tm.tm_min = t->tm.tm_hour = t->tm.tm_mday = t->tm.tm_mon
|
---|
258 | = t->tm.tm_year = t->tm.tm_wday = t->tm.tm_yday
|
---|
259 | = t->wday_ordinal = t->ymodulus = t->yweek
|
---|
260 | = TM_UNDEFINED;
|
---|
261 | t->tmr.tm_sec = t->tmr.tm_min = t->tmr.tm_hour =
|
---|
262 | t->tmr.tm_mday = t->tmr.tm_mon = t->tmr.tm_year = 0;
|
---|
263 | t->zone = TM_UNDEFINED_ZONE;
|
---|
264 | }
|
---|
265 |
|
---|
266 | /* Patterns to look for in a time string.
|
---|
267 | Order is important: we look for the first matching pattern
|
---|
268 | whose values do not contradict values that we already know about.
|
---|
269 | See `parse_pattern_letter' below for the meaning of the pattern codes. */
|
---|
270 | static char const time_patterns[] =
|
---|
271 | {
|
---|
272 | /* Traditional patterns come first,
|
---|
273 | to prevent an ISO 8601 format from misinterpreting their prefixes. */
|
---|
274 |
|
---|
275 | /* RFC 822, extended */
|
---|
276 | 'E', '_', 'N', '_', 'y', '$', 0,
|
---|
277 | 'x', 0,
|
---|
278 |
|
---|
279 | /* traditional */
|
---|
280 | '4', '_', 'M', '_', 'D', '_', 'h', '_', 'm', '_', 's', '$', 0,
|
---|
281 | 'R', '_', 'M', '_', 'D', '_', 'h', '_', 'm', '_', 's', '$', 0,
|
---|
282 | 'E', '_', 'N', 0,
|
---|
283 | 'N', '_', 'E', '_', 'y', ';', 0,
|
---|
284 | 'N', '_', 'E', ';', 0,
|
---|
285 | 'N', 0,
|
---|
286 | 't', ':', 'm', ':', 's', '_', 'A', 0,
|
---|
287 | 't', ':', 'm', '_', 'A', 0,
|
---|
288 | 't', '_', 'A', 0,
|
---|
289 |
|
---|
290 | /* traditional get_date */
|
---|
291 | 'i', '_', 'x', 0,
|
---|
292 | 'Y', '/', 'n', '/', 'E', ';', 0,
|
---|
293 | 'n', '/', 'E', '/', 'y', ';', 0,
|
---|
294 | 'n', '/', 'E', ';', 0,
|
---|
295 | 'u', 0,
|
---|
296 |
|
---|
297 | /* ISO 8601:1988 formats, generalized a bit. */
|
---|
298 | 'y', '-', 'M', '-', 'D', '$', 0,
|
---|
299 | '4', 'M', 'D', '$', 0,
|
---|
300 | 'Y', '-', 'M', '$', 0,
|
---|
301 | 'R', 'M', 'D', '$', 0,
|
---|
302 | '-', 'R', '=', 'M', '$', 0,
|
---|
303 | '-', 'R', '$', 0,
|
---|
304 | '-', '-', 'M', '=', 'D', '$', 0,
|
---|
305 | 'M', '=', 'D', 'T', 0,
|
---|
306 | '-', '-', 'M', '$', 0,
|
---|
307 | '-', '-', '-', 'D', '$', 0,
|
---|
308 | 'D', 'T', 0,
|
---|
309 | 'Y', '-', 'd', '$', 0,
|
---|
310 | '4', 'd', '$', 0,
|
---|
311 | 'R', '=', 'd', '$', 0,
|
---|
312 | '-', 'd', '$', 0,
|
---|
313 | 'd', 'T', 0,
|
---|
314 | 'y', '-', 'W', '-', 'X', 0,
|
---|
315 | 'y', 'W', 'X', 0,
|
---|
316 | 'y', '=', 'W', 0,
|
---|
317 | '-', 'r', '-', 'W', '-', 'X', 0,
|
---|
318 | 'r', '-', 'W', '-', 'X', 'T', 0,
|
---|
319 | '-', 'r', 'W', 'X', 0,
|
---|
320 | 'r', 'W', 'X', 'T', 0,
|
---|
321 | '-', 'W', '=', 'X', 0,
|
---|
322 | 'W', '=', 'X', 'T', 0,
|
---|
323 | '-', 'W', 0,
|
---|
324 | '-', 'w', '-', 'X', 0,
|
---|
325 | 'w', '-', 'X', 'T', 0,
|
---|
326 | '-', '-', '-', 'X', '$', 0,
|
---|
327 | 'X', 'T', 0,
|
---|
328 | '4', '$', 0,
|
---|
329 | 'T', 0,
|
---|
330 | 'h', ':', 'm', ':', 's', '$', 0,
|
---|
331 | 'h', 'm', 's', '$', 0,
|
---|
332 | 'h', ':', 'L', '$', 0,
|
---|
333 | 'h', 'L', '$', 0,
|
---|
334 | 'H', '$', 0,
|
---|
335 | '-', 'm', ':', 's', '$', 0,
|
---|
336 | '-', 'm', 's', '$', 0,
|
---|
337 | '-', 'L', '$', 0,
|
---|
338 | '-', '-', 's', '$', 0,
|
---|
339 | 'Y', 0,
|
---|
340 | 'Z', 0,
|
---|
341 |
|
---|
342 | 0
|
---|
343 | };
|
---|
344 |
|
---|
345 | /* Parse an initial prefix of STR according to *PATTERNS, setting *T.
|
---|
346 | Return the first character after the prefix, or 0 if it couldn't be parsed.
|
---|
347 | *PATTERNS is a character array containing one pattern string after another;
|
---|
348 | it is terminated by an empty string.
|
---|
349 | If success, set *PATTERNS to the next pattern to try.
|
---|
350 | Set *PATTERNS to 0 if we know there are no more patterns to try;
|
---|
351 | if *PATTERNS is initially 0, give up immediately. */
|
---|
352 | static char const *
|
---|
353 | parse_prefix (str, patterns, t)
|
---|
354 | char const *str;
|
---|
355 | char const **patterns;
|
---|
356 | struct partime *t;
|
---|
357 | {
|
---|
358 | char const *pat = *patterns;
|
---|
359 | unsigned char c;
|
---|
360 |
|
---|
361 | if (! pat)
|
---|
362 | return 0;
|
---|
363 |
|
---|
364 | /* Remove initial noise. */
|
---|
365 | while (! ISALNUM (c = *str) && c != '-' && c != '+')
|
---|
366 | {
|
---|
367 | if (! c)
|
---|
368 | {
|
---|
369 | undefine (t);
|
---|
370 | *patterns = 0;
|
---|
371 | return str;
|
---|
372 | }
|
---|
373 |
|
---|
374 | str++;
|
---|
375 | }
|
---|
376 |
|
---|
377 | /* Try a pattern until one succeeds. */
|
---|
378 | while (*pat)
|
---|
379 | {
|
---|
380 | char const *s = str;
|
---|
381 | undefine (t);
|
---|
382 |
|
---|
383 | do
|
---|
384 | {
|
---|
385 | if (! (c = *pat++))
|
---|
386 | {
|
---|
387 | *patterns = pat;
|
---|
388 | return s;
|
---|
389 | }
|
---|
390 | }
|
---|
391 | while ((s = parse_pattern_letter (s, c, t)) != 0);
|
---|
392 |
|
---|
393 | while (*pat++)
|
---|
394 | continue;
|
---|
395 | }
|
---|
396 |
|
---|
397 | return 0;
|
---|
398 | }
|
---|
399 |
|
---|
400 | /* Parse an initial prefix of S of length DIGITS; it must be a number.
|
---|
401 | Store the parsed number into *RES.
|
---|
402 | Return the first character after the prefix, or 0 if it wasn't parsed. */
|
---|
403 | static char const *
|
---|
404 | parse_fixed (s, digits, res)
|
---|
405 | char const *s;
|
---|
406 | int digits, *res;
|
---|
407 | {
|
---|
408 | int n = 0;
|
---|
409 | char const *lim = s + digits;
|
---|
410 | while (s < lim)
|
---|
411 | {
|
---|
412 | unsigned d = *s++ - '0';
|
---|
413 | if (9 < d)
|
---|
414 | return 0;
|
---|
415 | n = 10 * n + d;
|
---|
416 | }
|
---|
417 | *res = n;
|
---|
418 | return s;
|
---|
419 | }
|
---|
420 |
|
---|
421 | /* Parse a possibly empty initial prefix of S.
|
---|
422 | Store the parsed number into *RES.
|
---|
423 | Return the first character after the prefix. */
|
---|
424 | static char const *
|
---|
425 | parse_varying (s, res)
|
---|
426 | char const *s;
|
---|
427 | int *res;
|
---|
428 | {
|
---|
429 | int n = 0;
|
---|
430 | for (;;)
|
---|
431 | {
|
---|
432 | unsigned d = *s - '0';
|
---|
433 | if (9 < d)
|
---|
434 | break;
|
---|
435 | s++;
|
---|
436 | n = 10 * n + d;
|
---|
437 | }
|
---|
438 | *res = n;
|
---|
439 | return s;
|
---|
440 | }
|
---|
441 |
|
---|
442 | /* Parse an initial prefix of S of length DIGITS;
|
---|
443 | it must be a number in the range LO through HI.
|
---|
444 | Store the parsed number into *RES.
|
---|
445 | Return the first character after the prefix, or 0 if it wasn't parsed. */
|
---|
446 | static char const *
|
---|
447 | parse_ranged (s, digits, lo, hi, res)
|
---|
448 | char const *s;
|
---|
449 | int digits, lo, hi, *res;
|
---|
450 | {
|
---|
451 | s = parse_fixed (s, digits, res);
|
---|
452 | return s && lo <= *res && *res <= hi ? s : 0;
|
---|
453 | }
|
---|
454 |
|
---|
455 | /* Parse an initial prefix of S of length DIGITS;
|
---|
456 | it must be a number in the range LO through HI
|
---|
457 | and it may be followed by a fraction to be computed using RESOLUTION.
|
---|
458 | Store the parsed number into *RES; store the fraction times RESOLUTION,
|
---|
459 | rounded to the nearest integer, into *FRES.
|
---|
460 | Return the first character after the prefix, or 0 if it wasn't parsed. */
|
---|
461 | static char const *
|
---|
462 | parse_decimal (s, digits, lo, hi, resolution, res, fres)
|
---|
463 | char const *s;
|
---|
464 | int digits, lo, hi, resolution, *res, *fres;
|
---|
465 | {
|
---|
466 | s = parse_fixed (s, digits, res);
|
---|
467 | if (s && lo <= *res && *res <= hi)
|
---|
468 | {
|
---|
469 | int f = 0;
|
---|
470 | if ((s[0] == ',' || s[0] == '.') && ISDIGIT (s[1]))
|
---|
471 | {
|
---|
472 | char const *s1 = ++s;
|
---|
473 | int num10 = 0, denom10 = 10, product;
|
---|
474 | while (ISDIGIT (*++s))
|
---|
475 | {
|
---|
476 | int d = denom10 * 10;
|
---|
477 | if (d / 10 != denom10)
|
---|
478 | return 0; /* overflow */
|
---|
479 | denom10 = d;
|
---|
480 | }
|
---|
481 | s = parse_fixed (s1, (int) (s - s1), &num10);
|
---|
482 | product = num10 * resolution;
|
---|
483 | f = (product + (denom10 >> 1)) / denom10;
|
---|
484 | f -= f & (product % denom10 == denom10 >> 1); /* round to even */
|
---|
485 | if (f < 0 || product/resolution != num10)
|
---|
486 | return 0; /* overflow */
|
---|
487 | }
|
---|
488 | *fres = f;
|
---|
489 | return s;
|
---|
490 | }
|
---|
491 | return 0;
|
---|
492 | }
|
---|
493 |
|
---|
494 | /* Parse an initial prefix of S; it must denote a time zone.
|
---|
495 | Set *ZONE to the number of seconds east of GMT,
|
---|
496 | or to TM_LOCAL_ZONE if it is the local time zone.
|
---|
497 | Return the first character after the prefix, or 0 if it wasn't parsed. */
|
---|
498 | char *
|
---|
499 | parzone (s, zone)
|
---|
500 | char const *s;
|
---|
501 | long *zone;
|
---|
502 | {
|
---|
503 | char const *s1;
|
---|
504 | char sign;
|
---|
505 | int hh, mm, ss;
|
---|
506 | int minutes_east_of_UTC;
|
---|
507 | int trailing_DST;
|
---|
508 | long offset, z;
|
---|
509 |
|
---|
510 | /* The formats are LT, n, n DST, nDST, no, o
|
---|
511 | where n is a time zone name
|
---|
512 | and o is a time zone offset of the form [-+]hh[:mm[:ss]]. */
|
---|
513 | switch (*s)
|
---|
514 | {
|
---|
515 | case '-':
|
---|
516 | case '+':
|
---|
517 | z = 0;
|
---|
518 | break;
|
---|
519 |
|
---|
520 | default:
|
---|
521 | minutes_east_of_UTC = lookup (s, zone_names);
|
---|
522 | if (minutes_east_of_UTC == -1)
|
---|
523 | return 0;
|
---|
524 |
|
---|
525 | /* Don't bother to check rest of spelling,
|
---|
526 | but look for an embedded "DST". */
|
---|
527 | trailing_DST = 0;
|
---|
528 | while (ISALPHA ((unsigned char) *s))
|
---|
529 | {
|
---|
530 | if ((*s == 'D' || *s == 'd') && lookup (s, dst_names))
|
---|
531 | trailing_DST = 1;
|
---|
532 | s++;
|
---|
533 | s += *s == '.';
|
---|
534 | }
|
---|
535 |
|
---|
536 | /* Don't modify LT. */
|
---|
537 | if (minutes_east_of_UTC == 1)
|
---|
538 | {
|
---|
539 | *zone = TM_LOCAL_ZONE;
|
---|
540 | return (char *) s;
|
---|
541 | }
|
---|
542 |
|
---|
543 | z = minutes_east_of_UTC * 60L;
|
---|
544 | s1 = s;
|
---|
545 |
|
---|
546 | /* Look for trailing "DST" or " DST". */
|
---|
547 | while (ISSPACE ((unsigned char) *s))
|
---|
548 | s++;
|
---|
549 | if (lookup (s, dst_names))
|
---|
550 | {
|
---|
551 | while (ISALPHA ((unsigned char) *s))
|
---|
552 | {
|
---|
553 | s++;
|
---|
554 | s += *s == '.';
|
---|
555 | }
|
---|
556 | trailing_DST = 1;
|
---|
557 | }
|
---|
558 |
|
---|
559 | if (trailing_DST)
|
---|
560 | {
|
---|
561 | *zone = z + 60*60;
|
---|
562 | return (char *) s;
|
---|
563 | }
|
---|
564 |
|
---|
565 | s = s1;
|
---|
566 |
|
---|
567 | switch (*s)
|
---|
568 | {
|
---|
569 | case '-':
|
---|
570 | case '+':
|
---|
571 | break;
|
---|
572 |
|
---|
573 | default:
|
---|
574 | *zone = z;
|
---|
575 | return (char *) s;
|
---|
576 | }
|
---|
577 |
|
---|
578 | break;
|
---|
579 | }
|
---|
580 |
|
---|
581 | sign = *s++;
|
---|
582 |
|
---|
583 | if (! (s = parse_ranged (s, 2, 0, 23, &hh)))
|
---|
584 | return 0;
|
---|
585 | mm = ss = 0;
|
---|
586 | if (*s == ':')
|
---|
587 | s++;
|
---|
588 | if (ISDIGIT (*s))
|
---|
589 | {
|
---|
590 | if (! (s = parse_ranged (s, 2, 0, 59, &mm)))
|
---|
591 | return 0;
|
---|
592 | if (*s == ':' && s[-3] == ':' && ISDIGIT (s[1])
|
---|
593 | && ! (s = parse_ranged (s + 1, 2, 0, 59, &ss)))
|
---|
594 | return 0;
|
---|
595 | }
|
---|
596 | if (ISDIGIT (*s))
|
---|
597 | return 0;
|
---|
598 | offset = (hh * 60 + mm) * 60L + ss;
|
---|
599 | *zone = z + (sign == '-' ? -offset : offset);
|
---|
600 | /* ?? Are fractions allowed here? If so, they're not implemented. */
|
---|
601 | return (char *) s;
|
---|
602 | }
|
---|
603 |
|
---|
604 | /* Parse an initial prefix of S, matching the pattern whose code is C.
|
---|
605 | Set *T accordingly.
|
---|
606 | Return the first character after the prefix, or 0 if it wasn't parsed. */
|
---|
607 | static char const *
|
---|
608 | parse_pattern_letter (s, c, t)
|
---|
609 | char const *s;
|
---|
610 | int c;
|
---|
611 | struct partime *t;
|
---|
612 | {
|
---|
613 | char const *s0 = s;
|
---|
614 |
|
---|
615 | switch (c)
|
---|
616 | {
|
---|
617 | case '$': /* The next character must be a non-digit. */
|
---|
618 | if (ISDIGIT (*s))
|
---|
619 | return 0;
|
---|
620 | break;
|
---|
621 |
|
---|
622 | case '-':
|
---|
623 | case '/':
|
---|
624 | case ':':
|
---|
625 | /* These characters stand for themselves. */
|
---|
626 | if (*s++ != c)
|
---|
627 | return 0;
|
---|
628 | break;
|
---|
629 |
|
---|
630 | case '4': /* 4-digit year */
|
---|
631 | s = parse_fixed (s, 4, &t->tm.tm_year);
|
---|
632 | break;
|
---|
633 |
|
---|
634 | case ';': /* The next character must be a non-digit, and cannot be ':'. */
|
---|
635 | if (ISDIGIT (*s) || *s == ':')
|
---|
636 | return 0;
|
---|
637 | break;
|
---|
638 |
|
---|
639 | case '=': /* optional '-' */
|
---|
640 | s += *s == '-';
|
---|
641 | break;
|
---|
642 |
|
---|
643 | case 'A': /* AM or PM */
|
---|
644 | /* This matches the regular expression [AaPp]\.?([Mm]\.?)?.
|
---|
645 | It must not be followed by a letter or digit;
|
---|
646 | otherwise it would match prefixes of strings like "PST". */
|
---|
647 | switch (*s)
|
---|
648 | {
|
---|
649 | case 'A':
|
---|
650 | case 'a':
|
---|
651 | if (t->tm.tm_hour == 12)
|
---|
652 | t->tm.tm_hour = 0;
|
---|
653 | break;
|
---|
654 |
|
---|
655 | case 'P':
|
---|
656 | case 'p':
|
---|
657 | if (t->tm.tm_hour != 12)
|
---|
658 | t->tm.tm_hour += 12;
|
---|
659 | break;
|
---|
660 |
|
---|
661 | default:
|
---|
662 | return 0;
|
---|
663 | }
|
---|
664 | s++;
|
---|
665 | s += *s == '.';
|
---|
666 | switch (*s)
|
---|
667 | {
|
---|
668 | case 'M':
|
---|
669 | case 'm':
|
---|
670 | s++;
|
---|
671 | s += *s == '.';
|
---|
672 | break;
|
---|
673 | }
|
---|
674 | if (ISALNUM ((unsigned char) *s))
|
---|
675 | return 0;
|
---|
676 | break;
|
---|
677 |
|
---|
678 | case 'D': /* day of month [01-31] */
|
---|
679 | s = parse_ranged (s, 2, 1, 31, &t->tm.tm_mday);
|
---|
680 | break;
|
---|
681 |
|
---|
682 | case 'd': /* day of year [001-366] */
|
---|
683 | s = parse_ranged (s, 3, 1, 366, &t->tm.tm_yday);
|
---|
684 | t->tm.tm_yday--;
|
---|
685 | break;
|
---|
686 |
|
---|
687 | case 'E': /* traditional day of month [1-9, 01-31] */
|
---|
688 | s = parse_ranged (s, (ISDIGIT (s[0]) && ISDIGIT (s[1])) + 1, 1, 31,
|
---|
689 | &t->tm.tm_mday);
|
---|
690 | break;
|
---|
691 |
|
---|
692 | case 'h': /* hour [00-23] */
|
---|
693 | s = parse_ranged (s, 2, 0, 23, &t->tm.tm_hour);
|
---|
694 | break;
|
---|
695 |
|
---|
696 | case 'H': /* hour [00-23 followed by optional fraction] */
|
---|
697 | {
|
---|
698 | int frac;
|
---|
699 | s = parse_decimal (s, 2, 0, 23, 60 * 60, &t->tm.tm_hour, &frac);
|
---|
700 | t->tm.tm_min = frac / 60;
|
---|
701 | t->tm.tm_sec = frac % 60;
|
---|
702 | }
|
---|
703 | break;
|
---|
704 |
|
---|
705 | case 'i': /* ordinal day number, e.g. "3rd" */
|
---|
706 | s = parse_varying (s, &t->wday_ordinal);
|
---|
707 | if (s == s0)
|
---|
708 | return 0;
|
---|
709 | while (ISALPHA ((unsigned char) *s))
|
---|
710 | s++;
|
---|
711 | break;
|
---|
712 |
|
---|
713 | case 'L': /* minute [00-59 followed by optional fraction] */
|
---|
714 | s = parse_decimal (s, 2, 0, 59, 60, &t->tm.tm_min, &t->tm.tm_sec);
|
---|
715 | break;
|
---|
716 |
|
---|
717 | case 'm': /* minute [00-59] */
|
---|
718 | s = parse_ranged (s, 2, 0, 59, &t->tm.tm_min);
|
---|
719 | break;
|
---|
720 |
|
---|
721 | case 'M': /* month [01-12] */
|
---|
722 | s = parse_ranged (s, 2, 1, 12, &t->tm.tm_mon);
|
---|
723 | t->tm.tm_mon--;
|
---|
724 | break;
|
---|
725 |
|
---|
726 | case 'n': /* traditional month [1-9, 01-12] */
|
---|
727 | s = parse_ranged (s, (ISDIGIT (s[0]) && ISDIGIT (s[1])) + 1, 1, 12,
|
---|
728 | &t->tm.tm_mon);
|
---|
729 | t->tm.tm_mon--;
|
---|
730 | break;
|
---|
731 |
|
---|
732 | case 'N': /* month name [e.g. "Jan"] */
|
---|
733 | if (! TM_DEFINED (t->tm.tm_mon = lookup (s, month_names)))
|
---|
734 | return 0;
|
---|
735 | /* Don't bother to check rest of spelling. */
|
---|
736 | while (ISALPHA ((unsigned char) *s))
|
---|
737 | s++;
|
---|
738 | break;
|
---|
739 |
|
---|
740 | case 'r': /* year % 10 (remainder in origin-0 decade) [0-9] */
|
---|
741 | s = parse_fixed (s, 1, &t->tm.tm_year);
|
---|
742 | t->ymodulus = 10;
|
---|
743 | break;
|
---|
744 |
|
---|
745 | case_R:
|
---|
746 | case 'R': /* year % 100 (remainder in origin-0 century) [00-99] */
|
---|
747 | s = parse_fixed (s, 2, &t->tm.tm_year);
|
---|
748 | t->ymodulus = 100;
|
---|
749 | break;
|
---|
750 |
|
---|
751 | case 's': /* second [00-60 followed by optional fraction] */
|
---|
752 | {
|
---|
753 | int frac;
|
---|
754 | s = parse_decimal (s, 2, 0, 60, 1, &t->tm.tm_sec, &frac);
|
---|
755 | t->tm.tm_sec += frac;
|
---|
756 | }
|
---|
757 | break;
|
---|
758 |
|
---|
759 | case 'T': /* 'T' or 't' */
|
---|
760 | switch (*s++)
|
---|
761 | {
|
---|
762 | case 'T':
|
---|
763 | case 't':
|
---|
764 | break;
|
---|
765 | default:
|
---|
766 | return 0;
|
---|
767 | }
|
---|
768 | break;
|
---|
769 |
|
---|
770 | case 't': /* traditional hour [1-9 or 01-12] */
|
---|
771 | s = parse_ranged (s, (ISDIGIT (s[0]) && ISDIGIT (s[1])) + 1, 1, 12,
|
---|
772 | &t->tm.tm_hour);
|
---|
773 | break;
|
---|
774 |
|
---|
775 | case 'u': /* relative unit */
|
---|
776 | {
|
---|
777 | int i;
|
---|
778 | int n;
|
---|
779 | int negative = 0;
|
---|
780 | switch (*s)
|
---|
781 | {
|
---|
782 | case '-': negative = 1;
|
---|
783 | /* Fall through. */
|
---|
784 | case '+': s++;
|
---|
785 | }
|
---|
786 | if (ISDIGIT (*s))
|
---|
787 | s = parse_varying (s, &n);
|
---|
788 | else if (s == s0)
|
---|
789 | n = 1;
|
---|
790 | else
|
---|
791 | return 0;
|
---|
792 | if (negative)
|
---|
793 | n = -n;
|
---|
794 | while (! ISALNUM ((unsigned char) *s) && *s)
|
---|
795 | s++;
|
---|
796 | i = lookup (s, relative_units);
|
---|
797 | if (!TM_DEFINED (i))
|
---|
798 | return 0;
|
---|
799 | * (int *) ((char *) &t->tmr + RELATIVE_OFFSET (i))
|
---|
800 | += n * RELATIVE_MULTIPLIER (i);
|
---|
801 | while (ISALPHA ((unsigned char) *s))
|
---|
802 | s++;
|
---|
803 | while (! ISALNUM ((unsigned char) *s) && *s)
|
---|
804 | s++;
|
---|
805 | if (TM_DEFINED (lookup (s, ago)))
|
---|
806 | {
|
---|
807 | t->tmr.tm_sec = - t->tmr.tm_sec;
|
---|
808 | t->tmr.tm_min = - t->tmr.tm_min;
|
---|
809 | t->tmr.tm_hour = - t->tmr.tm_hour;
|
---|
810 | t->tmr.tm_mday = - t->tmr.tm_mday;
|
---|
811 | t->tmr.tm_mon = - t->tmr.tm_mon;
|
---|
812 | t->tmr.tm_year = - t->tmr.tm_year;
|
---|
813 | while (ISALPHA ((unsigned char) *s))
|
---|
814 | s++;
|
---|
815 | }
|
---|
816 | break;
|
---|
817 | }
|
---|
818 |
|
---|
819 | case 'w': /* 'W' or 'w' only (stands for current week) */
|
---|
820 | switch (*s++)
|
---|
821 | {
|
---|
822 | case 'W':
|
---|
823 | case 'w':
|
---|
824 | break;
|
---|
825 | default:
|
---|
826 | return 0;
|
---|
827 | }
|
---|
828 | break;
|
---|
829 |
|
---|
830 | case 'W': /* 'W' or 'w', followed by a week of year [00-53] */
|
---|
831 | switch (*s++)
|
---|
832 | {
|
---|
833 | case 'W':
|
---|
834 | case 'w':
|
---|
835 | break;
|
---|
836 | default:
|
---|
837 | return 0;
|
---|
838 | }
|
---|
839 | s = parse_ranged (s, 2, 0, 53, &t->yweek);
|
---|
840 | break;
|
---|
841 |
|
---|
842 | case 'X': /* weekday (1=Mon ... 7=Sun) [1-7] */
|
---|
843 | s = parse_ranged (s, 1, 1, 7, &t->tm.tm_wday);
|
---|
844 | t->tm.tm_wday--;
|
---|
845 | break;
|
---|
846 |
|
---|
847 | case 'x': /* weekday name [e.g. "Sun"] */
|
---|
848 | if (! TM_DEFINED (t->tm.tm_wday = lookup (s, weekday_names)))
|
---|
849 | return 0;
|
---|
850 | /* Don't bother to check rest of spelling. */
|
---|
851 | while (ISALPHA ((unsigned char) *s))
|
---|
852 | s++;
|
---|
853 | break;
|
---|
854 |
|
---|
855 | case 'y': /* either R or Y */
|
---|
856 | if (ISDIGIT (s[0]) && ISDIGIT (s[1]) && ! ISDIGIT (s[2]))
|
---|
857 | goto case_R;
|
---|
858 | /* fall into */
|
---|
859 | case 'Y': /* year in full [4 or more digits] */
|
---|
860 | s = parse_varying (s, &t->tm.tm_year);
|
---|
861 | if (s - s0 < 4)
|
---|
862 | return 0;
|
---|
863 | break;
|
---|
864 |
|
---|
865 | case 'Z': /* time zone */
|
---|
866 | s = parzone (s, &t->zone);
|
---|
867 | break;
|
---|
868 |
|
---|
869 | case '_': /* possibly empty sequence of non-alphanumerics */
|
---|
870 | while (! ISALNUM ((unsigned char) *s) && *s)
|
---|
871 | s++;
|
---|
872 | break;
|
---|
873 |
|
---|
874 | default: /* bad pattern */
|
---|
875 | return 0;
|
---|
876 | }
|
---|
877 |
|
---|
878 | return s;
|
---|
879 | }
|
---|
880 |
|
---|
881 | /* If there is no conflict, merge into *T the additional information in *U
|
---|
882 | and return 0. Otherwise do nothing and return -1. */
|
---|
883 | static int
|
---|
884 | merge_partime (t, u)
|
---|
885 | struct partime *t;
|
---|
886 | struct partime const *u;
|
---|
887 | {
|
---|
888 | # define conflict(a,b) ((a) != (b) && TM_DEFINED (a) && TM_DEFINED (b))
|
---|
889 | if (conflict (t->tm.tm_sec, u->tm.tm_sec)
|
---|
890 | || conflict (t->tm.tm_min, u->tm.tm_min)
|
---|
891 | || conflict (t->tm.tm_hour, u->tm.tm_hour)
|
---|
892 | || conflict (t->tm.tm_mday, u->tm.tm_mday)
|
---|
893 | || conflict (t->tm.tm_mon, u->tm.tm_mon)
|
---|
894 | || conflict (t->tm.tm_year, u->tm.tm_year)
|
---|
895 | || conflict (t->tm.tm_wday, u->tm.tm_wday)
|
---|
896 | || conflict (t->tm.tm_yday, u->tm.tm_yday)
|
---|
897 | || conflict (t->ymodulus, u->ymodulus)
|
---|
898 | || conflict (t->yweek, u->yweek)
|
---|
899 | || (t->zone != u->zone
|
---|
900 | && t->zone != TM_UNDEFINED_ZONE
|
---|
901 | && u->zone != TM_UNDEFINED_ZONE))
|
---|
902 | return -1;
|
---|
903 | # undef conflict
|
---|
904 | # define merge_(a,b) if (TM_DEFINED (b)) (a) = (b);
|
---|
905 | merge_ (t->tm.tm_sec, u->tm.tm_sec)
|
---|
906 | merge_ (t->tm.tm_min, u->tm.tm_min)
|
---|
907 | merge_ (t->tm.tm_hour, u->tm.tm_hour)
|
---|
908 | merge_ (t->tm.tm_mday, u->tm.tm_mday)
|
---|
909 | merge_ (t->tm.tm_mon, u->tm.tm_mon)
|
---|
910 | merge_ (t->tm.tm_year, u->tm.tm_year)
|
---|
911 | merge_ (t->tm.tm_wday, u->tm.tm_wday)
|
---|
912 | merge_ (t->tm.tm_yday, u->tm.tm_yday)
|
---|
913 | merge_ (t->ymodulus, u->ymodulus)
|
---|
914 | merge_ (t->yweek, u->yweek)
|
---|
915 | # undef merge_
|
---|
916 | t->tmr.tm_sec += u->tmr.tm_sec;
|
---|
917 | t->tmr.tm_min += u->tmr.tm_min;
|
---|
918 | t->tmr.tm_hour += u->tmr.tm_hour;
|
---|
919 | t->tmr.tm_mday += u->tmr.tm_mday;
|
---|
920 | t->tmr.tm_mon += u->tmr.tm_mon;
|
---|
921 | t->tmr.tm_year += u->tmr.tm_year;
|
---|
922 | if (u->zone != TM_UNDEFINED_ZONE)
|
---|
923 | t->zone = u->zone;
|
---|
924 | return 0;
|
---|
925 | }
|
---|
926 |
|
---|
927 | /* Parse a date/time prefix of S, putting the parsed result into *T.
|
---|
928 | Return the first character after the prefix.
|
---|
929 | The prefix may contain no useful information;
|
---|
930 | in that case, *T will contain only undefined values. */
|
---|
931 | char *
|
---|
932 | partime (s, t)
|
---|
933 | char const *s;
|
---|
934 | struct partime *t;
|
---|
935 | {
|
---|
936 | struct partime p;
|
---|
937 |
|
---|
938 | undefine (t);
|
---|
939 |
|
---|
940 | while (*s)
|
---|
941 | {
|
---|
942 | char const *patterns = time_patterns;
|
---|
943 | char const *s1;
|
---|
944 |
|
---|
945 | do
|
---|
946 | {
|
---|
947 | if (! (s1 = parse_prefix (s, &patterns, &p)))
|
---|
948 | return (char *) s;
|
---|
949 | }
|
---|
950 | while (merge_partime (t, &p) != 0);
|
---|
951 |
|
---|
952 | s = s1;
|
---|
953 | }
|
---|
954 |
|
---|
955 | return (char *) s;
|
---|
956 | }
|
---|