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