home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 22 gnu / 22-gnu.zip / rcs567s.zip / rcs / src / partime.c < prev    next >
C/C++ Source or Header  |  1994-03-17  |  19KB  |  690 lines

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