Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

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