Add the DragonFly cvs id and perform general cleanups on cvs/rcs/sccs ids. Most
[dragonfly.git] / gnu / usr.bin / rcs / lib / partime.c
1 /* Parse a string, yielding a struct partime that describes it.  */
2
3 /* Copyright 1993, 1994, 1995 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 /*
30  * $FreeBSD: src/gnu/usr.bin/rcs/lib/partime.c,v 1.6 1999/08/27 23:36:44 peter Exp $
31  * $DragonFly: src/gnu/usr.bin/rcs/lib/partime.c,v 1.2 2003/06/17 04:25:47 dillon Exp $
32  */
33
34 #if has_conf_h
35 #       include "conf.h"
36 #else
37 #       ifdef __STDC__
38 #               define P(x) x
39 #       else
40 #               define const
41 #               define P(x) ()
42 #       endif
43 #       include <limits.h>
44 #       include <time.h>
45 #endif
46
47 #include <ctype.h>
48 #undef isdigit
49 #define isdigit(c) (((unsigned)(c)-'0') <= 9) /* faster than stock */
50
51 #include "partime.h"
52
53 /* Lookup tables for names of months, weekdays, time zones.  */
54
55 #define NAME_LENGTH_MAXIMUM 4
56
57 struct name_val {
58         char name[NAME_LENGTH_MAXIMUM];
59         int val;
60 };
61
62
63 static char const *parse_decimal P((char const*,int,int,int,int,int*,int*));
64 static char const *parse_fixed P((char const*,int,int*));
65 static char const *parse_pattern_letter P((char const*,int,struct partime*));
66 static char const *parse_prefix P((char const*,struct partime*,int*));
67 static char const *parse_ranged P((char const*,int,int,int,int*));
68 static int lookup P((char const*,struct name_val const[]));
69 static int merge_partime P((struct partime*, struct partime const*));
70 static void undefine P((struct partime*));
71
72
73 static struct name_val const month_names[] = {
74         {"jan",0}, {"feb",1}, {"mar",2}, {"apr",3}, {"may",4}, {"jun",5},
75         {"jul",6}, {"aug",7}, {"sep",8}, {"oct",9}, {"nov",10}, {"dec",11},
76         {"", TM_UNDEFINED}
77 };
78
79 static struct name_val const weekday_names[] = {
80         {"sun",0}, {"mon",1}, {"tue",2}, {"wed",3}, {"thu",4}, {"fri",5}, {"sat",6},
81         {"", TM_UNDEFINED}
82 };
83
84 #define hr60nonnegative(t)  ((t)/100 * 60  +  (t)%100)
85 #define hr60(t)  ((t)<0 ? -hr60nonnegative(-(t)) : hr60nonnegative(t))
86 #define zs(t,s)  {s, hr60(t)}
87 #define zd(t,s,d)  zs(t, s),  zs((t)+100, d)
88
89 static struct name_val const zone_names[] = {
90         zs(-1000, "hst"),               /* Hawaii */
91         zd(-1000,"hast","hadt"),/* Hawaii-Aleutian */
92         zd(- 900,"akst","akdt"),/* Alaska */
93         zd(- 800, "pst", "pdt"),/* Pacific */
94         zd(- 700, "mst", "mdt"),/* Mountain */
95         zd(- 600, "cst", "cdt"),/* Central */
96         zd(- 500, "est", "edt"),/* Eastern */
97         zd(- 400, "ast", "adt"),/* Atlantic */
98         zd(- 330, "nst", "ndt"),/* Newfoundland */
99         zs(  000, "utc"),               /* Coordinated Universal */
100         zs(  000, "cut"),               /* " */
101         zs(  000,  "ut"),               /* Universal */
102         zs(  000,   "z"),               /* Zulu (required by ISO 8601) */
103         zd(  000, "gmt", "bst"),/* Greenwich Mean, British Summer */
104         zs(  000, "wet"),               /* Western Europe */
105         zs(  100, "met"),               /* Middle Europe */
106         zs(  100, "cet"),               /* Central Europe */
107         zs(  200, "eet"),               /* Eastern Europe */
108         zs(  530, "ist"),               /* India */
109         zd(  900, "jst", "jdt"),/* Japan */
110         zd(  900, "kst", "kdt"),/* Korea */
111         zd( 1200,"nzst","nzdt"),/* New Zealand */
112         { "lt", 1 },
113 #if 0
114         /* The following names are duplicates or are not well attested.  */
115         zs(-1100, "sst"),               /* Samoa */
116         zs(-1000, "tht"),               /* Tahiti */
117         zs(- 930, "mqt"),               /* Marquesas */
118         zs(- 900, "gbt"),               /* Gambier */
119         zd(- 900, "yst", "ydt"),/* Yukon - name is no longer used */
120         zs(- 830, "pit"),               /* Pitcairn */
121         zd(- 500, "cst", "cdt"),/* Cuba */
122         zd(- 500, "ast", "adt"),/* Acre */
123         zd(- 400, "wst", "wdt"),/* Western Brazil */
124         zd(- 400, "ast", "adt"),/* Andes */
125         zd(- 400, "cst", "cdt"),/* Chile */
126         zs(- 300, "wgt"),               /* Western Greenland */
127         zd(- 300, "est", "edt"),/* Eastern South America */
128         zs(- 300, "mgt"),               /* Middle Greenland */
129         zd(- 200, "fst", "fdt"),/* Fernando de Noronha */
130         zs(- 100, "egt"),               /* Eastern Greenland */
131         zs(- 100, "aat"),               /* Atlantic Africa */
132         zs(- 100, "act"),               /* Azores and Canaries */
133         zs(  000, "wat"),               /* West Africa */
134         zs(  100, "cat"),               /* Central Africa */
135         zd(  100, "mez","mesz"),/* Mittel-Europaeische Zeit */
136         zs(  200, "sat"),               /* South Africa */
137         zd(  200, "ist", "idt"),/* Israel */
138         zs(  300, "eat"),               /* East Africa */
139         zd(  300, "ast", "adt"),/* Arabia */
140         zd(  300, "msk", "msd"),/* Moscow */
141         zd(  330, "ist", "idt"),/* Iran */
142         zs(  400, "gst"),               /* Gulf */
143         zs(  400, "smt"),               /* Seychelles & Mascarene */
144         zd(  400, "esk", "esd"),/* Yekaterinburg */
145         zd(  400, "bsk", "bsd"),/* Baku */
146         zs(  430, "aft"),               /* Afghanistan */
147         zd(  500, "osk", "osd"),/* Omsk */
148         zs(  500, "pkt"),               /* Pakistan */
149         zd(  500, "tsk", "tsd"),/* Tashkent */
150         zs(  545, "npt"),               /* Nepal */
151         zs(  600, "bgt"),               /* Bangladesh */
152         zd(  600, "nsk", "nsd"),/* Novosibirsk */
153         zs(  630, "bmt"),               /* Burma */
154         zs(  630, "cct"),               /* Cocos */
155         zs(  700, "ict"),               /* Indochina */
156         zs(  700, "jvt"),               /* Java */
157         zd(  700, "isk", "isd"),/* Irkutsk */
158         zs(  800, "hkt"),               /* Hong Kong */
159         zs(  800, "pst"),               /* Philippines */
160         zs(  800, "sgt"),               /* Singapore */
161         zd(  800, "cst", "cdt"),/* China */
162         zd(  800, "ust", "udt"),/* Ulan Bator */
163         zd(  800, "wst", "wst"),/* Western Australia */
164         zd(  800, "ysk", "ysd"),/* Yakutsk */
165         zs(  900, "blt"),               /* Belau */
166         zs(  900, "mlt"),               /* Moluccas */
167         zd(  900, "vsk", "vsd"),/* Vladivostok */
168         zd(  930, "cst", "cst"),/* Central Australia */
169         zs( 1000, "gst"),               /* Guam */
170         zd( 1000, "gsk", "gsd"),/* Magadan */
171         zd( 1000, "est", "est"),/* Eastern Australia */
172         zd( 1100,"lhst","lhst"),/* Lord Howe */
173         zd( 1100, "psk", "psd"),/* Petropavlovsk-Kamchatski */
174         zs( 1100,"ncst"),               /* New Caledonia */
175         zs( 1130,"nrft"),               /* Norfolk */
176         zd( 1200, "ask", "asd"),/* Anadyr */
177         zs( 1245,"nz-chat"),    /* Chatham */
178         zs( 1300, "tgt"),               /* Tongatapu */
179 #endif
180         {"", -1}
181 };
182
183         static int
184 lookup (s, table)
185         char const *s;
186         struct name_val const table[];
187 /* Look for a prefix of S in TABLE, returning val for first matching entry.  */
188 {
189         int j;
190         char buf[NAME_LENGTH_MAXIMUM];
191
192         for (j = 0;  j < NAME_LENGTH_MAXIMUM;  j++) {
193                 unsigned char c = *s++;
194                 buf[j] = isupper (c) ? tolower (c) : c;
195                 if (!isalpha (c))
196                         break;
197         }
198         for (;  table[0].name[0];  table++)
199                 for (j = 0;  buf[j] == table[0].name[j];  )
200                         if (++j == NAME_LENGTH_MAXIMUM  ||  !table[0].name[j])
201                                 goto done;
202   done:
203         return table[0].val;
204 }
205
206
207         static void
208 undefine (t) struct partime *t;
209 /* Set *T to ``undefined'' values.  */
210 {
211         t->tm.tm_sec = t->tm.tm_min = t->tm.tm_hour = t->tm.tm_mday = t->tm.tm_mon
212                 = t->tm.tm_year = t->tm.tm_wday = t->tm.tm_yday
213                 = t->ymodulus = t->yweek
214                 = TM_UNDEFINED;
215         t->zone = TM_UNDEFINED_ZONE;
216 }
217
218 /*
219 * Array of patterns to look for in a date string.
220 * Order is important: we look for the first matching pattern
221 * whose values do not contradict values that we already know about.
222 * See `parse_pattern_letter' below for the meaning of the pattern codes.
223 */
224 static char const * const patterns[] = {
225         /*
226         * These traditional patterns must come first,
227         * to prevent an ISO 8601 format from misinterpreting their prefixes.
228         */
229         "E_n_y", "x", /* RFC 822 */
230         "E_n", "n_E", "n", "t:m:s_A", "t:m_A", "t_A", /* traditional */
231         "y/N/D$", /* traditional RCS */
232
233         /* ISO 8601:1988 formats, generalized a bit.  */
234         "y-N-D$", "4ND$", "Y-N$",
235         "RND$", "-R=N$", "-R$", "--N=D$", "N=DT",
236         "--N$", "---D$", "DT",
237         "Y-d$", "4d$", "R=d$", "-d$", "dT",
238         "y-W-X", "yWX", "y=W",
239         "-r-W-X", "r-W-XT", "-rWX", "rWXT", "-W=X", "W=XT", "-W",
240         "-w-X", "w-XT", "---X$", "XT", "4$",
241         "T",
242         "h:m:s$", "hms$", "h:m$", "hm$", "h$", "-m:s$", "-ms$", "-m$", "--s$",
243         "Y", "Z",
244
245         0
246 };
247
248         static char const *
249 parse_prefix (str, t, pi) char const *str; struct partime *t; int *pi;
250 /*
251 * Parse an initial prefix of STR, setting *T accordingly.
252 * Return the first character after the prefix, or 0 if it couldn't be parsed.
253 * Start with pattern *PI; if success, set *PI to the next pattern to try.
254 * Set *PI to -1 if we know there are no more patterns to try;
255 * if *PI is initially negative, give up immediately.
256 */
257 {
258         int i = *pi;
259         char const *pat;
260         unsigned char c;
261
262         if (i < 0)
263                 return 0;
264
265         /* Remove initial noise.  */
266         while (!isalnum (c = *str)  &&  c != '-'  &&  c != '+') {
267                 if (!c) {
268                         undefine (t);
269                         *pi = -1;
270                         return str;
271                 }
272                 str++;
273         }
274
275         /* Try a pattern until one succeeds.  */
276         while ((pat = patterns[i++]) != 0) {
277                 char const *s = str;
278                 undefine (t);
279                 do {
280                         if (!(c = *pat++)) {
281                                 *pi = i;
282                                 return s;
283                         }
284                 } while ((s = parse_pattern_letter (s, c, t)) != 0);
285         }
286
287         return 0;
288 }
289
290         static char const *
291 parse_fixed (s, digits, res) char const *s; int digits, *res;
292 /*
293 * Parse an initial prefix of S of length DIGITS; it must be a number.
294 * Store the parsed number into *RES.
295 * Return the first character after the prefix, or 0 if it couldn't be parsed.
296 */
297 {
298         int n = 0;
299         char const *lim = s + digits;
300         while (s < lim) {
301                 unsigned d = *s++ - '0';
302                 if (9 < d)
303                         return 0;
304                 n = 10*n + d;
305         }
306         *res = n;
307         return s;
308 }
309
310         static char const *
311 parse_ranged (s, digits, lo, hi, res) char const *s; int digits, lo, hi, *res;
312 /*
313 * Parse an initial prefix of S of length DIGITS;
314 * it must be a number in the range LO through HI.
315 * Store the parsed number into *RES.
316 * Return the first character after the prefix, or 0 if it couldn't be parsed.
317 */
318 {
319         s = parse_fixed (s, digits, res);
320         return  s && lo<=*res && *res<=hi  ?  s  :  0;
321 }
322
323         static char const *
324 parse_decimal (s, digits, lo, hi, resolution, res, fres)
325         char const *s;
326         int digits, lo, hi, resolution, *res, *fres;
327 /*
328 * Parse an initial prefix of S of length DIGITS;
329 * it must be a number in the range LO through HI
330 * and it may be followed by a fraction that is to be computed using RESOLUTION.
331 * Store the parsed number into *RES; store the fraction times RESOLUTION,
332 * rounded to the nearest integer, into *FRES.
333 * Return the first character after the prefix, or 0 if it couldn't be parsed.
334 */
335 {
336         s = parse_fixed (s, digits, res);
337         if (s && lo<=*res && *res<=hi) {
338                 int f = 0;
339                 if ((s[0]==',' || s[0]=='.')  &&  isdigit ((unsigned char) s[1])) {
340                         char const *s1 = ++s;
341                         int num10 = 0, denom10 = 10, product;
342                         while (isdigit ((unsigned char) *++s))
343                                 denom10 *= 10;
344                         s = parse_fixed (s1, s - s1, &num10);
345                         product = num10*resolution;
346                         f = (product + (denom10>>1)) / denom10;
347                         f -= f & (product%denom10 == denom10>>1); /* round to even */
348                         if (f < 0  ||  product/resolution != num10)
349                                 return 0; /* overflow */
350                 }
351                 *fres = f;
352                 return s;
353         }
354         return 0;
355 }
356
357         char *
358 parzone (s, zone) char const *s; long *zone;
359 /*
360 * Parse an initial prefix of S; it must denote a time zone.
361 * Set *ZONE to the number of seconds east of GMT,
362 * or to TM_LOCAL_ZONE if it is the local time zone.
363 * Return the first character after the prefix, or 0 if it couldn't be parsed.
364 */
365 {
366         char sign;
367         int hh, mm, ss;
368         int minutesEastOfUTC;
369         long offset, z;
370
371         /*
372         * The formats are LT, n, n DST, nDST, no, o
373         * where n is a time zone name
374         * and o is a time zone offset of the form [-+]hh[:mm[:ss]].
375         */
376         switch (*s) {
377                 case '-': case '+':
378                         z = 0;
379                         break;
380
381                 default:
382                         minutesEastOfUTC = lookup (s, zone_names);
383                         if (minutesEastOfUTC == -1)
384                                 return 0;
385
386                         /* Don't bother to check rest of spelling.  */
387                         while (isalpha ((unsigned char) *s))
388                                 s++;
389
390                         /* Don't modify LT.  */
391                         if (minutesEastOfUTC == 1) {
392                                 *zone = TM_LOCAL_ZONE;
393                                 return (char *) s;
394                         }
395
396                         z = minutesEastOfUTC * 60L;
397
398                         /* Look for trailing " DST".  */
399                         if (
400                                 (s[-1]=='T' || s[-1]=='t') &&
401                                 (s[-2]=='S' || s[-2]=='s') &&
402                                 (s[-3]=='D' || s[-3]=='t')
403                         )
404                                 goto trailing_dst;
405                         while (isspace ((unsigned char) *s))
406                                 s++;
407                         if (
408                                 (s[0]=='D' || s[0]=='d') &&
409                                 (s[1]=='S' || s[1]=='s') &&
410                                 (s[2]=='T' || s[2]=='t')
411                         ) {
412                                 s += 3;
413                           trailing_dst:
414                                 *zone = z + 60*60;
415                                 return (char *) s;
416                         }
417
418                         switch (*s) {
419                                 case '-': case '+': break;
420                                 default: return (char *) s;
421                         }
422         }
423         sign = *s++;
424
425         if (!(s = parse_ranged (s, 2, 0, 23, &hh)))
426                 return 0;
427         mm = ss = 0;
428         if (*s == ':')
429                 s++;
430         if (isdigit ((unsigned char) *s)) {
431                 if (!(s = parse_ranged (s, 2, 0, 59, &mm)))
432                         return 0;
433                 if (*s==':' && s[-3]==':' && isdigit ((unsigned char) s[1])) {
434                         if (!(s = parse_ranged (s + 1, 2, 0, 59, &ss)))
435                                 return 0;
436                 }
437         }
438         if (isdigit ((unsigned char) *s))
439                 return 0;
440         offset = (hh*60 + mm)*60L + ss;
441         *zone = z + (sign=='-' ? -offset : offset);
442         /*
443         * ?? Are fractions allowed here?
444         * If so, they're not implemented.
445         */
446         return (char *) s;
447 }
448
449         static char const *
450 parse_pattern_letter (s, c, t) char const *s; int c; struct partime *t;
451 /*
452 * Parse an initial prefix of S, matching the pattern whose code is C.
453 * Set *T accordingly.
454 * Return the first character after the prefix, or 0 if it couldn't be parsed.
455 */
456 {
457         switch (c) {
458                 case '$': /* The next character must be a non-digit.  */
459                         if (isdigit ((unsigned char) *s))
460                                 return 0;
461                         break;
462
463                 case '-': case '/': case ':':
464                         /* These characters stand for themselves.  */
465                         if (*s++ != c)
466                                 return 0;
467                         break;
468
469                 case '4': /* 4-digit year */
470                         s = parse_fixed (s, 4, &t->tm.tm_year);
471                         break;
472
473                 case '=': /* optional '-' */
474                         s  +=  *s == '-';
475                         break;
476
477                 case 'A': /* AM or PM */
478                         /*
479                         * This matches the regular expression [AaPp][Mm]?.
480                         * It must not be followed by a letter or digit;
481                         * otherwise it would match prefixes of strings like "PST".
482                         */
483                         switch (*s++) {
484                                 case 'A': case 'a':
485                                         if (t->tm.tm_hour == 12)
486                                                 t->tm.tm_hour = 0;
487                                         break;
488
489                                 case 'P': case 'p':
490                                         if (t->tm.tm_hour != 12)
491                                                 t->tm.tm_hour += 12;
492                                         break;
493
494                                 default: return 0;
495                         }
496                         switch (*s) {
497                                 case 'M': case 'm': s++; break;
498                         }
499                         if (isalnum (*s))
500                                 return 0;
501                         break;
502
503                 case 'D': /* day of month [01-31] */
504                         s = parse_ranged (s, 2, 1, 31, &t->tm.tm_mday);
505                         break;
506
507                 case 'd': /* day of year [001-366] */
508                         s = parse_ranged (s, 3, 1, 366, &t->tm.tm_yday);
509                         t->tm.tm_yday--;
510                         break;
511
512                 case 'E': /* extended day of month [1-9, 01-31] */
513                         s = parse_ranged (s, (
514                                 isdigit ((unsigned char) s[0]) &&
515                                 isdigit ((unsigned char) s[1])
516                         ) + 1, 1, 31, &t->tm.tm_mday);
517                         break;
518
519                 case 'h': /* hour [00-23 followed by optional fraction] */
520                         {
521                                 int frac;
522                                 s = parse_decimal (s, 2, 0, 23, 60*60, &t->tm.tm_hour, &frac);
523                                 t->tm.tm_min = frac / 60;
524                                 t->tm.tm_sec = frac % 60;
525                         }
526                         break;
527
528                 case 'm': /* minute [00-59 followed by optional fraction] */
529                         s = parse_decimal (s, 2, 0, 59, 60, &t->tm.tm_min, &t->tm.tm_sec);
530                         break;
531
532                 case 'n': /* month name [e.g. "Jan"] */
533                         if (!TM_DEFINED (t->tm.tm_mon = lookup (s, month_names)))
534                                 return 0;
535                         /* Don't bother to check rest of spelling.  */
536                         while (isalpha ((unsigned char) *s))
537                                 s++;
538                         break;
539
540                 case 'N': /* month [01-12] */
541                         s = parse_ranged (s, 2, 1, 12, &t->tm.tm_mon);
542                         t->tm.tm_mon--;
543                         break;
544
545                 case 'r': /* year % 10 (remainder in origin-0 decade) [0-9] */
546                         s = parse_fixed (s, 1, &t->tm.tm_year);
547                         t->ymodulus = 10;
548                         break;
549
550                 case_R:
551                 case 'R': /* year % 100 (remainder in origin-0 century) [00-99] */
552                         s = parse_fixed (s, 2, &t->tm.tm_year);
553                         t->ymodulus = 100;
554                         break;
555
556                 case 's': /* second [00-60 followed by optional fraction] */
557                         {
558                                 int frac;
559                                 s = parse_decimal (s, 2, 0, 60, 1, &t->tm.tm_sec, &frac);
560                                 t->tm.tm_sec += frac;
561                         }
562                         break;
563
564                 case 'T': /* 'T' or 't' */
565                         switch (*s++) {
566                                 case 'T': case 't': break;
567                                 default: return 0;
568                         }
569                         break;
570
571                 case 't': /* traditional hour [1-9 or 01-12] */
572                         s = parse_ranged (s, (
573                                 isdigit ((unsigned char) s[0]) && isdigit ((unsigned char) s[1])
574                         ) + 1, 1, 12, &t->tm.tm_hour);
575                         break;
576
577                 case 'w': /* 'W' or 'w' only (stands for current week) */
578                         switch (*s++) {
579                                 case 'W': case 'w': break;
580                                 default: return 0;
581                         }
582                         break;
583
584                 case 'W': /* 'W' or 'w', followed by a week of year [00-53] */
585                         switch (*s++) {
586                                 case 'W': case 'w': break;
587                                 default: return 0;
588                         }
589                         s = parse_ranged (s, 2, 0, 53, &t->yweek);
590                         break;
591
592                 case 'X': /* weekday (1=Mon ... 7=Sun) [1-7] */
593                         s = parse_ranged (s, 1, 1, 7, &t->tm.tm_wday);
594                         t->tm.tm_wday--;
595                         break;
596
597                 case 'x': /* weekday name [e.g. "Sun"] */
598                         if (!TM_DEFINED (t->tm.tm_wday = lookup (s, weekday_names)))
599                                 return 0;
600                         /* Don't bother to check rest of spelling.  */
601                         while (isalpha ((unsigned char) *s))
602                                 s++;
603                         break;
604
605                 case 'y': /* either R or Y */
606                         if (
607                                 isdigit ((unsigned char) s[0]) &&
608                                 isdigit ((unsigned char) s[1]) &&
609                                 !isdigit ((unsigned char) s[2])
610                         )
611                                 goto case_R;
612                         /* fall into */
613                 case 'Y': /* year in full [4 or more digits] */
614                         {
615                                 int len = 0;
616                                 while (isdigit ((unsigned char) s[len]))
617                                         len++;
618                                 if (len < 4)
619                                         return 0;
620                                 s = parse_fixed (s, len, &t->tm.tm_year);
621                         }
622                         break;
623
624                 case 'Z': /* time zone */
625                         s = parzone (s, &t->zone);
626                         break;
627
628                 case '_': /* possibly empty sequence of non-alphanumerics */
629                         while (!isalnum (*s)  &&  *s)
630                                 s++;
631                         break;
632
633                 default: /* bad pattern */
634                         return 0;
635         }
636         return s;
637 }
638
639         static int
640 merge_partime (t, u) struct partime *t; struct partime const *u;
641 /*
642 * If there is no conflict, merge into *T the additional information in *U
643 * and return 0.  Otherwise do nothing and return -1.
644 */
645 {
646 #       define conflict(a,b) ((a) != (b)  &&  TM_DEFINED (a)  &&  TM_DEFINED (b))
647         if (
648                 conflict (t->tm.tm_sec, u->tm.tm_sec) ||
649                 conflict (t->tm.tm_min, u->tm.tm_min) ||
650                 conflict (t->tm.tm_hour, u->tm.tm_hour) ||
651                 conflict (t->tm.tm_mday, u->tm.tm_mday) ||
652                 conflict (t->tm.tm_mon, u->tm.tm_mon) ||
653                 conflict (t->tm.tm_year, u->tm.tm_year) ||
654                 conflict (t->tm.tm_wday, u->tm.tm_yday) ||
655                 conflict (t->ymodulus, u->ymodulus) ||
656                 conflict (t->yweek, u->yweek) ||
657                 (
658                         t->zone != u->zone &&
659                         t->zone != TM_UNDEFINED_ZONE &&
660                         u->zone != TM_UNDEFINED_ZONE
661                 )
662         )
663                 return -1;
664 #       undef conflict
665 #       define merge_(a,b) if (TM_DEFINED (b)) (a) = (b);
666         merge_ (t->tm.tm_sec, u->tm.tm_sec)
667         merge_ (t->tm.tm_min, u->tm.tm_min)
668         merge_ (t->tm.tm_hour, u->tm.tm_hour)
669         merge_ (t->tm.tm_mday, u->tm.tm_mday)
670         merge_ (t->tm.tm_mon, u->tm.tm_mon)
671         merge_ (t->tm.tm_year, u->tm.tm_year)
672         merge_ (t->tm.tm_wday, u->tm.tm_yday)
673         merge_ (t->ymodulus, u->ymodulus)
674         merge_ (t->yweek, u->yweek)
675 #       undef merge_
676         if (u->zone != TM_UNDEFINED_ZONE) t->zone = u->zone;
677         return 0;
678 }
679
680         char *
681 partime (s, t) char const *s; struct partime *t;
682 /*
683 * Parse a date/time prefix of S, putting the parsed result into *T.
684 * Return the first character after the prefix.
685 * The prefix may contain no useful information;
686 * in that case, *T will contain only undefined values.
687 */
688 {
689         struct partime p;
690
691         undefine (t);
692         while (*s) {
693                 int i = 0;
694                 char const *s1;
695                 do {
696                         if (!(s1 = parse_prefix (s, &p, &i)))
697                                 return (char *) s;
698                 } while (merge_partime (t, &p) != 0);
699                 s = s1;
700         }
701         return (char *) s;
702 }