1: /* 2: * PARTIME parse date/time string into a TM structure 3: * 4: * Usage: 5: * #include "time.h" -- expanded tm structure 6: * char *str; struct tm *tp; 7: * partime(str,tp); 8: * Returns: 9: * 0 if parsing failed 10: * else time values in specified TM structure (unspecified values 11: * set to TMNULL) 12: * Notes: 13: * This code is quasi-public; it may be used freely in like software. 14: * It is not to be sold, nor used in licensed software without 15: * permission of the author. 16: * For everyone's benefit, please report bugs and improvements! 17: * Copyright 1980 by Ken Harrenstien, SRI International. 18: * (ARPANET: KLH @ SRI) 19: */ 20: 21: /* Hacknotes: 22: * If parsing changed so that no backup needed, could perhaps modify 23: * to use a FILE input stream. Need terminator, though. 24: * Perhaps should return 0 on success, else a non-zero error val? 25: * Flush AMPM from TM structure and handle locally within PARTIME, 26: * like midnight/noon? 27: */ 28: 29: #ifndef lint 30: static char rcsid[]= 31: "$Header: /arthur/src/local/bin/rcs/src/RCS/partime.c,v 1.2 87/03/27 14:21:53 jenkins Exp $"; 32: #endif 33: 34: /* $Log: partime.c,v $ 35: * Revision 1.2 87/03/27 14:21:53 jenkins 36: * Port to suns 37: * 38: * Revision 1.1 84/01/23 14:50:07 kcs 39: * Initial revision 40: * 41: * Revision 1.1 82/05/06 11:38:26 wft 42: * Initial revision 43: * 44: */ 45: 46: #include <stdio.h> 47: #include <ctype.h> 48: #include "time.h" 49: 50: #ifndef lint 51: static char timeid[] = TIMEID; 52: #endif 53: 54: struct tmwent { 55: char *went; 56: union { 57: int wvint; 58: int (*wvfn)(); 59: } wval; /* must be big enough to hold pointer or integer */ 60: char wflgs; 61: char wtype; 62: }; 63: /* wflgs */ 64: #define TWSPEC 01 /* Word wants special processing */ 65: #define TWTIME 02 /* Word is a time value (absence implies date) */ 66: #define TWDST 04 /* Word is a DST-type timezone */ 67: #define TW1200 010 /* Word is NOON or MIDNIGHT (sigh) */ 68: 69: int pt12hack(); 70: int ptnoise(); 71: struct tmwent tmwords [] = { 72: {"january", 0, 0, TM_MON}, 73: {"february", 1, 0, TM_MON}, 74: {"march", 2, 0, TM_MON}, 75: {"april", 3, 0, TM_MON}, 76: {"may", 4, 0, TM_MON}, 77: {"june", 5, 0, TM_MON}, 78: {"july", 6, 0, TM_MON}, 79: {"august", 7, 0, TM_MON}, 80: {"september", 8, 0, TM_MON}, 81: {"october", 9, 0, TM_MON}, 82: {"november", 10, 0, TM_MON}, 83: {"december", 11, 0, TM_MON}, 84: 85: {"sunday", 0, 0, TM_WDAY}, 86: {"monday", 1, 0, TM_WDAY}, 87: {"tuesday", 2, 0, TM_WDAY}, 88: {"wednesday", 3, 0, TM_WDAY}, 89: {"thursday", 4, 0, TM_WDAY}, 90: {"friday", 5, 0, TM_WDAY}, 91: {"saturday", 6, 0, TM_WDAY}, 92: 93: {"gmt", 0*60, TWTIME, TM_ZON}, /* Greenwich */ 94: {"gst", 0*60, TWTIME, TM_ZON}, 95: {"gdt", 0*60, TWTIME+TWDST, TM_ZON}, /* ?? */ 96: 97: {"ast", 4*60, TWTIME, TM_ZON}, /* Atlantic */ 98: {"est", 5*60, TWTIME, TM_ZON}, /* Eastern */ 99: {"cst", 6*60, TWTIME, TM_ZON}, /* Central */ 100: {"mst", 7*60, TWTIME, TM_ZON}, /* Mountain */ 101: {"pst", 8*60, TWTIME, TM_ZON}, /* Pacific */ 102: {"yst", 9*60, TWTIME, TM_ZON}, /* Yukon */ 103: {"hst", 10*60, TWTIME, TM_ZON}, /* Hawaii */ 104: {"bst", 11*60, TWTIME, TM_ZON}, /* Bering */ 105: 106: {"adt", 4*60, TWTIME+TWDST, TM_ZON}, /* Atlantic */ 107: {"edt", 5*60, TWTIME+TWDST, TM_ZON}, /* Eastern */ 108: {"cdt", 6*60, TWTIME+TWDST, TM_ZON}, /* Central */ 109: {"mdt", 7*60, TWTIME+TWDST, TM_ZON}, /* Mountain */ 110: {"pdt", 8*60, TWTIME+TWDST, TM_ZON}, /* Pacific */ 111: {"ydt", 9*60, TWTIME+TWDST, TM_ZON}, /* Yukon */ 112: {"hdt", 10*60, TWTIME+TWDST, TM_ZON}, /* Hawaii */ 113: {"bdt", 11*60, TWTIME+TWDST, TM_ZON}, /* Bering */ 114: 115: {"daylight", 1, TWTIME+TWDST, TM_ZON}, /* Local Daylight */ 116: {"standard", 1, TWTIME, TM_ZON}, /* Local Standard */ 117: {"std", 1, TWTIME, TM_ZON}, /* " " */ 118: 119: {"am", 1, TWTIME, TM_AMPM}, 120: {"pm", 2, TWTIME, TM_AMPM}, 121: {"noon", 12,TWTIME+TW1200, 0}, /* Special frobs */ 122: {"midnight", 0, TWTIME+TW1200, 0}, 123: {"at", ptnoise, TWSPEC, 0}, /* Noise word */ 124: 125: {0, 0, 0, 0}, /* Zero entry to terminate searches */ 126: }; 127: 128: #define TMWILD (-2) /* Value meaning item specified as wild-card */ 129: /* (May use someday...) */ 130: 131: struct token { 132: char *tcp; /* pointer to string */ 133: int tcnt; /* # chars */ 134: char tbrk; /* "break" char */ 135: char tbrkl; /* last break char */ 136: char tflg; /* 0 = alpha, 1 = numeric */ 137: union { /* Resulting value; */ 138: int tnum;/* either a #, or */ 139: struct tmwent *ttmw;/* ptr to a tmwent. */ 140: } tval; 141: }; 142: 143: partime(astr, atm) 144: char *astr; 145: struct tm *atm; 146: { register int *tp; 147: register struct tmwent *twp; 148: register int i; 149: struct token btoken, atoken; 150: char *cp, ch; 151: int ord, midnoon; 152: int (*aproc)(); 153: 154: tp = (int *)atm; 155: zaptime(tp); /* Initialize the TM structure */ 156: midnoon = TMNULL; /* and our own temp stuff */ 157: btoken.tcnt = btoken.tbrkl = 0; 158: btoken.tcp = astr; 159: 160: domore: 161: if(!ptitoken(btoken.tcp+btoken.tcnt,&btoken)) /* Get a token */ 162: { if(btoken.tval.tnum) return(0); /* Read error? */ 163: if(midnoon != TMNULL) /* EOF, wrap up */ 164: return(pt12hack(tp, midnoon)); 165: return(1); /* Win return! */ 166: } 167: if(btoken.tflg == 0) /* Alpha? */ 168: { twp = btoken.tval.ttmw; /* Yes, get ptr to entry */ 169: if(twp->wflgs&TWSPEC) /* Special alpha crock */ 170: { aproc = (twp->wval.wvfn); 171: if(!(*aproc)(tp, twp, &btoken)) 172: return(0); /* ERR: special word err */ 173: goto domore; 174: } 175: if(twp->wflgs&TW1200) 176: if(ptstash(&midnoon,twp->wval.wvint)) 177: return(0); /* ERR: noon/midnite clash */ 178: else goto domore; 179: if(ptstash(&tp[twp->wtype],twp->wval.wvint)) 180: return(0); /* ERR: val already set */ 181: if(twp->wtype == TM_ZON) /* If was zone, hack DST */ 182: if(ptstash(&tp[TM_ISDST],(twp->wflgs&TWDST))) 183: return(0); /* ERR: DST conflict */ 184: goto domore; 185: } 186: 187: /* Token is number. Lots of hairy heuristics. */ 188: if(btoken.tcnt >= 7) /* More than 6 digits in string? */ 189: return(0); /* ERR: number too big */ 190: if(btoken.tcnt == 6) /* 6 digits = HHMMSS. Needs special crock */ 191: { /* since 6 digits are too big for integer! */ 192: i = (btoken.tcp[0]-'0')*10 /* Gobble 1st 2 digits */ 193: + btoken.tcp[1]-'0'; 194: btoken.tcnt = 2; /* re-read last 4 chars */ 195: goto coltime; 196: } 197: 198: i = btoken.tval.tnum; /* Value now known to be valid; get it. */ 199: if( btoken.tcnt == 5 /* 5 digits = HMMSS */ 200: || btoken.tcnt == 3) /* 3 digits = HMM */ 201: { if(btoken.tcnt != 3) 202: if(ptstash(&tp[TM_SEC], i%100)) 203: return(0); /* ERR: sec conflict */ 204: else i /= 100; 205: hhmm4: if(ptstash(&tp[TM_MIN], i%100)) 206: return(0); /* ERR: min conflict */ 207: i /= 100; 208: hh2: if(ptstash(&tp[TM_HOUR], i)) 209: return(0); /* ERR: hour conflict */ 210: goto domore; 211: } 212: 213: if(btoken.tcnt == 4) /* 4 digits = YEAR or HHMM */ 214: { if(tp[TM_YEAR] != TMNULL) goto hhmm4; /* Already got yr? */ 215: if(tp[TM_HOUR] != TMNULL) goto year4; /* Already got hr? */ 216: if((i%100) > 59) goto year4; /* MM >= 60? */ 217: if(btoken.tbrk == ':') /* HHMM:SS ? */ 218: if( ptstash(&tp[TM_HOUR],i/100) 219: || ptstash(&tp[TM_MIN], i%100)) 220: return(0); /* ERR: hr/min clash */ 221: else goto coltm2; /* Go handle SS */ 222: if(btoken.tbrk != ',' && btoken.tbrk != '/' 223: && ptitoken(btoken.tcp+btoken.tcnt,&atoken) /* Peek */ 224: && atoken.tflg == 0 /* alpha */ 225: && (atoken.tval.ttmw->wflgs&TWTIME)) /* HHMM-ZON */ 226: goto hhmm4; 227: if(btoken.tbrkl == '-' /* DD-Mon-YYYY */ 228: || btoken.tbrkl == ',' /* Mon DD, YYYY */ 229: || btoken.tbrkl == '/' /* MM/DD/YYYY */ 230: || btoken.tbrkl == '.' /* DD.MM.YYYY */ 231: || btoken.tbrk == '-' /* YYYY-MM-DD */ 232: ) goto year4; 233: goto hhmm4; /* Give up, assume HHMM. */ 234: } 235: 236: /* From this point on, assume tcnt == 1 or 2 */ 237: /* 2 digits = YY, MM, DD, or HH (MM and SS caught at coltime) */ 238: if(btoken.tbrk == ':') /* HH:MM[:SS] */ 239: goto coltime; /* must be part of time. */ 240: if(i > 31) goto yy2; /* If >= 32, only YY poss. */ 241: 242: /* Check for numerical-format date */ 243: for (cp = "/-."; ch = *cp++;) 244: { ord = (ch == '.' ? 0 : 1); /* n/m = D/M or M/D */ 245: if(btoken.tbrk == ch) /* "NN-" */ 246: { if(btoken.tbrkl != ch) 247: { if(ptitoken(btoken.tcp+btoken.tcnt,&atoken) 248: && atoken.tflg == 0 249: && atoken.tval.ttmw->wtype == TM_MON) 250: goto dd2; 251: if(ord)goto mm2; else goto dd2; /* "NN-" */ 252: } /* "-NN-" */ 253: if(tp[TM_DAY] == TMNULL 254: && tp[TM_YEAR] != TMNULL) /* If "YY-NN-" */ 255: goto mm2; /* then always MM */ 256: if(ord)goto dd2; else goto mm2; 257: } 258: if(btoken.tbrkl == ch /* "-NN" */ 259: && tp[ord ? TM_MON : TM_DAY] != TMNULL) 260: if(tp[ord ? TM_DAY : TM_MON] == TMNULL) /* MM/DD */ 261: if(ord)goto dd2; else goto mm2; 262: else goto yy2; /* "-YY" */ 263: } 264: 265: /* At this point only YY, DD, and HH are left. 266: * YY is very unlikely since value is <= 32 and there was 267: * no numerical format date. Make one last try at YY 268: * before dropping through to DD vs HH code. 269: */ 270: if(btoken.tcnt == 2 /* If 2 digits */ 271: && tp[TM_HOUR] != TMNULL /* and already have hour */ 272: && tp[TM_DAY] != TMNULL /* and day, but */ 273: && tp[TM_YEAR] == TMNULL) /* no year, then assume */ 274: goto yy2; /* that's what we have. */ 275: 276: /* Now reduced to choice between HH and DD */ 277: if(tp[TM_HOUR] != TMNULL) goto dd2; /* Have hour? Assume day. */ 278: if(tp[TM_DAY] != TMNULL) goto hh2; /* Have day? Assume hour. */ 279: if(i > 24) goto dd2; /* Impossible HH means DD */ 280: if(!ptitoken(btoken.tcp+btoken.tcnt, &atoken)) /* Read ahead! */ 281: if(atoken.tval.tnum) return(0); /* ERR: bad token */ 282: else goto dd2; /* EOF, assume day. */ 283: if( atoken.tflg == 0 /* If next token is an alpha */ 284: && atoken.tval.ttmw->wflgs&TWTIME) /* time-spec, assume hour */ 285: goto hh2; /* e.g. "3 PM", "11-EDT" */ 286: 287: dd2: if(ptstash(&tp[TM_DAY],i)) /* Store day (1 based) */ 288: return(0); 289: goto domore; 290: 291: mm2: if(ptstash(&tp[TM_MON], i-1)) /* Store month (make zero based) */ 292: return(0); 293: goto domore; 294: 295: yy2: i += 1900; 296: year4: if(ptstash(&tp[TM_YEAR],i)) /* Store year (full number) */ 297: return(0); /* ERR: year conflict */ 298: goto domore; 299: 300: /* Hack HH:MM[[:]SS] */ 301: coltime: 302: if(ptstash(&tp[TM_HOUR],i)) return(0); 303: if(!ptitoken(btoken.tcp+btoken.tcnt,&btoken)) 304: return(!btoken.tval.tnum); 305: if(!btoken.tflg) return(0); /* ERR: HH:<alpha> */ 306: if(btoken.tcnt == 4) /* MMSS */ 307: if(ptstash(&tp[TM_MIN],btoken.tval.tnum/100) 308: || ptstash(&tp[TM_SEC],btoken.tval.tnum%100)) 309: return(0); 310: else goto domore; 311: if(btoken.tcnt != 2 312: || ptstash(&tp[TM_MIN],btoken.tval.tnum)) 313: return(0); /* ERR: MM bad */ 314: if(btoken.tbrk != ':') goto domore; /* Seconds follow? */ 315: coltm2: if(!ptitoken(btoken.tcp+btoken.tcnt,&btoken)) 316: return(!btoken.tval.tnum); 317: if(!btoken.tflg || btoken.tcnt != 2 /* Verify SS */ 318: || ptstash(&tp[TM_SEC], btoken.tval.tnum)) 319: return(0); /* ERR: SS bad */ 320: goto domore; 321: } 322: 323: /* Store date/time value, return 0 if successful. 324: * Fails if entry already set to a different value. 325: */ 326: ptstash(adr,val) 327: int *adr; 328: { register int *a; 329: if( *(a=adr) != TMNULL) 330: return(*a != val); 331: *a = val; 332: return(0); 333: } 334: 335: /* This subroutine is invoked for NOON or MIDNIGHT when wrapping up 336: * just prior to returning from partime. 337: */ 338: pt12hack(atp, aval) 339: int *atp, aval; 340: { register int *tp, i, h; 341: tp = atp; 342: if (((i=tp[TM_MIN]) && i != TMNULL) /* Ensure mins, secs */ 343: || ((i=tp[TM_SEC]) && i != TMNULL)) /* are 0 or unspec'd */ 344: return(0); /* ERR: MM:SS not 00:00 */ 345: i = aval; /* Get 0 or 12 (midnite or noon) */ 346: if ((h = tp[TM_HOUR]) == TMNULL /* If hour unspec'd, win */ 347: || h == 12) /* or if 12:00 (matches either) */ 348: tp[TM_HOUR] = i; /* Then set time */ 349: else if(!(i == 0 /* Nope, but if midnight and */ 350: &&(h == 0 || h == 24))) /* time matches, can pass. */ 351: return(0); /* ERR: HH conflicts */ 352: tp[TM_AMPM] = TMNULL; /* Always reset this value if won */ 353: return(1); 354: } 355: 356: /* Null routine for no-op tokens */ 357: 358: ptnoise() { return(1); } 359: 360: /* Get a token and identify it to some degree. 361: * Returns 0 on failure; token.tval will be 0 for normal EOF, otherwise 362: * hit error of some sort 363: */ 364: 365: ptitoken(astr, tkp) 366: register struct token *tkp; 367: char *astr; 368: { 369: register char *cp; 370: register int i; 371: 372: tkp->tval.tnum = 0; 373: if(pttoken(astr,tkp) == 0) 374: #ifdef DEBUG 375: VOID printf("EOF\n"); 376: #endif DEBUG 377: return(0); 378: cp = tkp->tcp; 379: 380: #ifdef DEBUG 381: i = cp[tkp->tcnt]; 382: cp[tkp->tcnt] = 0; 383: VOID printf("Token: \"%s\" ",cp); 384: cp[tkp->tcnt] = i; 385: #endif DEBUG 386: 387: if(tkp->tflg) 388: for(i = tkp->tcnt; i > 0; i--) 389: tkp->tval.tnum = (int)tkp->tval.tnum*10 + ((*cp++)-'0'); 390: else 391: { i = ptmatchstr(cp, tkp->tcnt, tmwords); 392: tkp->tval.tnum = i ? i : -1; /* Set -1 for error */ 393: 394: #ifdef DEBUG 395: if(!i) VOID printf("Not found!\n"); 396: #endif DEBUG 397: 398: if(!i) return(0); 399: } 400: 401: #ifdef DEBUG 402: if(tkp->tflg) 403: VOID printf("Val: %d.\n",tkp->tval.tnum); 404: else VOID printf("Found: \"%s\", val: %d., type %d\n", 405: tkp->tval.ttmw->went,tkp->tval.ttmw->wval.wvint,tkp->tval.ttmw->wtype); 406: #endif DEBUG 407: 408: return(1); 409: } 410: 411: /* Read token from input string into token structure */ 412: pttoken(astr,tkp) 413: register struct token *tkp; 414: char *astr; 415: { 416: register char *cp; 417: register int c; 418: 419: tkp->tcp = cp = astr; 420: tkp->tbrkl = tkp->tbrk; /* Set "last break" */ 421: tkp->tcnt = tkp->tbrk = tkp->tflg = 0; 422: 423: while(c = *cp++) 424: { switch(c) 425: { case ' ': case '\t': /* Flush all whitespace */ 426: while((c = *cp++) && isspace(c)); 427: cp--; /* Drop thru to handle brk */ 428: case '(': case ')': /* Perhaps any non-alphanum */ 429: case '-': case ',': /* shd qualify as break? */ 430: case '/': case ':': case '.': /* Break chars */ 431: if(tkp->tcnt == 0) /* If no token yet */ 432: { tkp->tcp = cp; /* ignore the brk */ 433: tkp->tbrkl = c; 434: continue; /* and go on. */ 435: } 436: tkp->tbrk = c; 437: return(tkp->tcnt); 438: } 439: if(tkp->tcnt == 0) /* If first char of token, */ 440: tkp->tflg = isdigit(c); /* determine type */ 441: if(( isdigit(c) && tkp->tflg) /* If not first, make sure */ 442: ||(!isdigit(c) && !tkp->tflg)) /* char matches type */ 443: tkp->tcnt++; /* Win, add to token. */ 444: else { 445: cp--; /* Wrong type, back up */ 446: tkp->tbrk = c; 447: return(tkp->tcnt); 448: } 449: } 450: return(tkp->tcnt); /* When hit EOF */ 451: } 452: 453: 454: ptmatchstr(astr,cnt,astruc) 455: char *astr; 456: int cnt; 457: struct tmwent *astruc; 458: { register char *cp, *mp; 459: register int c; 460: struct tmwent *lastptr; 461: struct integ { int word; }; /* For getting at array ptr */ 462: int i; 463: 464: lastptr = 0; 465: for(;mp = (char *)((struct integ *)astruc)->word; astruc += 1) 466: { cp = astr; 467: for(i = cnt; i > 0; i--) 468: { switch((c = *cp++) ^ *mp++) /* XOR the chars */ 469: { case 0: continue; /* Exact match */ 470: case 040: if(isalpha(c)) 471: continue; 472: } 473: break; 474: } 475: if(i==0) 476: if(*mp == 0) return((unsigned int)astruc); /* Exact match */ 477: else if(lastptr) return(0); /* Ambiguous */ 478: else lastptr = astruc; /* 1st ambig */ 479: } 480: return((unsigned int)lastptr); 481: } 482: 483: 484: 485: zaptime(tp) 486: register int *tp; 487: /* clears tm structure pointed to by tp */ 488: { register int i; 489: i = (sizeof (struct tm))/(sizeof (int)); 490: do *tp++ = TMNULL; /* Set entry to "unspecified" */ 491: while(--i); /* Faster than FOR */ 492: }