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 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 77char const partime_id[] = 78 "$Id: partime.c 8008 2004-06-16 21:22:10Z korli $"; 79 80 81/* Lookup tables for names of months, weekdays, time zones. */ 82 83#define NAME_LENGTH_MAXIMUM 4 84 85struct name_val 86 { 87 char name[NAME_LENGTH_MAXIMUM]; 88 int val; 89 }; 90 91 92static char const *parse_decimal P ((char const *, int, int, int, int, int *, int *)); 93static char const *parse_fixed P ((char const *, int, int *)); 94static char const *parse_pattern_letter P ((char const *, int, struct partime *)); 95static char const *parse_prefix P ((char const *, char const **, struct partime *)); 96static char const *parse_ranged P ((char const *, int, int, int, int *)); 97static char const *parse_varying P ((char const *, int *)); 98static int lookup P ((char const *, struct name_val const[])); 99static int merge_partime P ((struct partime *, struct partime const *)); 100static void undefine P ((struct partime *)); 101 102 103static 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 120static 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)) 136static 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 149static struct name_val const ago[] = 150{ 151 {"ago", 0}, 152 {"", TM_UNDEFINED} 153}; 154 155static 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 166static 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. */ 222static int 223lookup (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. */ 253static void 254undefine (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. */ 270static 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. */ 352static char const * 353parse_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. */ 403static char const * 404parse_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. */ 424static char const * 425parse_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. */ 446static char const * 447parse_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. */ 461static char const * 462parse_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. */ 498char * 499parzone (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. */ 607static char const * 608parse_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)) 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. */ 883static int 884merge_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. */ 931char * 932partime (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} 957