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