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 0 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 } 743