home *** CD-ROM | disk | FTP | other *** search
/ Unix System Administration Handbook 1997 October / usah_oct97.iso / news / cnews.tar / libc / getreldate.c < prev    next >
C/C++ Source or Header  |  1994-11-30  |  7KB  |  271 lines

  1. /*
  2.  * getreldate - parse relative dates
  3.  *
  4.  * relative dates are of the form
  5.  *    <timeunits> before <date>
  6.  *    <timeunits> after|from <date>
  7.  *    <timeunits> ago
  8.  *    <timeunits> hence
  9.  *    <timeunits>
  10.  *    <qualifier> <unit>|<day>|<month> ["in"|"of" <month>|<year>]
  11.  * where <timeunits> is a sequence of the form { [<number>] <unit> } +
  12.  * <unit> is one of second, minute, hour, day, week, month or year
  13.  *    (plurals are okay too).
  14.  * <date> is "now" or an absolute date (see getabsdate(3)).
  15.  * <qualifier> is "this", "first", "last", "next", "second", "third",
  16.  *    ..., "twelfth"
  17.  * <day> is a day-of-the-week name
  18.  * <month> is a month name.
  19.  * <year> is a numeric year.
  20.  *
  21.  * note: 1 month ago means the same day last month, not 30 or 31 days ago.
  22.  * If this day didn't exist in that month, then it's the last day.  so "1
  23.  * month before May 31" means April 30.  This system may be non-intuitive
  24.  * -- blame the bozos who thought up the calendar system.  1 year ago means
  25.  * this day and month last year.  Same caveats apply.  We first rewind years,
  26.  * then months.  This has subtle effects on leap years.
  27.  */
  28.  
  29. #include <stdio.h>
  30. #include <ctype.h>
  31. #include <string.h>
  32. #include <time.h>
  33. #include <sys/types.h>
  34. #include <sys/timeb.h>
  35. #include <stdlib.h>
  36.  
  37. #include "datetok.h"
  38. #include "dateconv.h"
  39.  
  40. #define STREQ(a, b)    (*(a) == *(b) && strcmp(a, b) == 0)
  41.  
  42. #define MAXDATEFIELDS 50
  43.  
  44. extern time_t time();
  45. extern datetkn datereltoks[];
  46. extern unsigned int szdatereltoks;
  47.  
  48. struct parsestate {
  49.     time_t    secs;
  50.     time_t    months;
  51.     time_t    before;        /* 0 means after, 1 means before */
  52.     time_t    lastnum;
  53. };
  54.  
  55. /*
  56.  * parse and convert relative date in timestr (the normal interface)
  57.  */
  58. time_t
  59. getreldate(timestr, now)
  60. char *timestr;
  61. struct timeb *now;
  62. {
  63.     int tz = 0;
  64.     struct tm date;
  65.  
  66.     return prsreldate(timestr, now, &date, &tz) < 0? -1:
  67.         dateconv(&date, tz);
  68. }
  69.  
  70. /* try to parse "now" or an absolute date */
  71. static int
  72. trydate(fields, nf, now, tm, tzp)
  73. register char **fields;
  74. int nf;
  75. struct timeb *now;
  76. struct tm *tm;
  77. int *tzp;
  78. {
  79.     if (nf == 1 &&
  80.         (STREQ(fields[0], "now") || STREQ(fields[0], "today") ||
  81.          STREQ(fields[0], "tomorrow") || STREQ(fields[0], "yesterday"))) {
  82.         struct timeb fakenow;
  83.  
  84.         if (now == NULL)
  85.             now = &fakenow;
  86.         (void) ftime(now);
  87.         if (STREQ(fields[0], "tomorrow"))
  88.             now->time += 24L*60L*60L;
  89.         else if (STREQ(fields[0], "yesterday"))
  90.             now->time -= 24L*60L*60L;
  91.         *tm = *gmtime(&now->time);
  92.         *tzp = 0;
  93.         return 0;
  94.     } else
  95.         return tryabsdate(fields, nf, now, tm, tzp);
  96. }
  97.  
  98. /* TODO: absdate may not be DEC date */
  99.  
  100. static int            /* token type, 0 for done, -1 for failure */
  101. prsrdtoken(fields, nf, i, now, tm, tzp, psp)
  102. register char **fields;
  103. int nf;
  104. register int i;
  105. struct timeb *now;
  106. register struct tm *tm;
  107. int *tzp;
  108. register struct parsestate *psp;
  109. {
  110.     register char c;
  111.  
  112.     if (i >= nf)
  113.         return -1;    /* index out of range */
  114.     c = fields[i][0];
  115.     if (isascii(c) && isdigit(c)) {
  116.         if (psp->lastnum != -1)
  117.             return -1;
  118.         psp->lastnum = atol(fields[i]);
  119.         return NUMBER;
  120.     } else {
  121.         register datetkn *tp =
  122.             datebsearch(fields[i], datereltoks, szdatereltoks);
  123.         register time_t val;
  124.  
  125.         if (tp == NULL)        /* probably an absolute date */
  126.             return trydate(fields, nf, now, tm, tzp);
  127.         val = tp->value;
  128.         switch (tp->type) {
  129.         case ORDINAL:
  130.             if (psp->lastnum != -1)
  131.                 return -1;
  132.             psp->lastnum = val;
  133.             break;
  134.         case YEARS:
  135.             val *= 12;
  136.             /* FALLTHROUGH */
  137.         case MONTHS:
  138.             psp->months +=
  139.                 (psp->lastnum == -1? val: val * psp->lastnum);
  140.             psp->lastnum = -1;
  141.             break;
  142.         case DAYS:
  143.             val *= 24;
  144.             /* FALLTHROUGH */
  145.         case HOURS:
  146.             val *= 3600;
  147.             /* FALLTHROUGH */
  148.         case SECONDS:
  149.             psp->secs +=
  150.                 (psp->lastnum == -1? val: val * psp->lastnum);
  151.             psp->lastnum = -1;
  152.             break;
  153.         case NUMBER:
  154.             if (psp->lastnum != -1)
  155.                 if (trydate(fields, nf, now, tm, tzp) == -1)
  156.                     return -1;
  157.                 else
  158.                     return NUMBER;
  159.             psp->lastnum = 1;
  160.             break;
  161.         case BEFORE:
  162.         case AFTER:
  163.         case AGO:        /* and hence */
  164.             psp->before = val;
  165.             break;
  166.         case IGNORE:
  167.             break;
  168.         default:
  169.             return -1;    /* CANTHAPPEN */
  170.         }
  171.         return tp->type;
  172.     }
  173. }
  174.  
  175. /*
  176.  * just parse the relative date in timestr and get back a broken-out date.
  177.  *
  178.  * Numbers are positive.  If we parse a number, we stick it in lastnum.
  179.  * Two numbers in succession is an error.  A seconds or months token is
  180.  * associated with the preceding lastnum (no lastnum means 1, even if this
  181.  * renders plurals counterintuitive!!).  If we hit one of BEFORE, AFTER or
  182.  * AGO, we assume date follows BEFORE or AFTER (AGO == BEFORE now) and
  183.  * parse it, then hop back appropriate number of months and secs.  Note
  184.  * that we first hop back by months, then by secs.
  185.  */
  186. int
  187. prsreldate(timestr, now, tm, tzp)
  188. char *timestr;
  189. struct timeb *now;
  190. register struct tm *tm;
  191. int *tzp;
  192. {
  193.     register int i;
  194.     struct parsestate ps;
  195.     register struct parsestate *psp = &ps;
  196.     register int type;
  197.     int nf;
  198.     time_t dt, modifier;
  199.     char *cp;
  200.     char *fields[MAXDATEFIELDS];
  201.     static char delims[] = "- \t\n/,";
  202.  
  203.     nf = split(timestr, fields, MAXDATEFIELDS, delims+1);
  204.     if (nf > MAXDATEFIELDS)
  205.         return -1;
  206.     cp = strchr(fields[nf - 1], '\n');
  207.     if (cp != NULL)
  208.         *cp = '\0';
  209.  
  210.     psp->secs = psp->months = 0;
  211.     psp->before = psp->lastnum = -1;
  212.     /* try to parse <qualifier> ... syntax */
  213.     type = prsrdtoken(fields, nf, 0, now, tm, tzp, psp);
  214.     if (type == ORDINAL) {
  215.         /* TODO: finish this up */
  216.         int ord = psp->lastnum;
  217.  
  218.         type = prsrdtoken(fields, nf, 1, now, tm, tzp, psp);
  219.         switch (type) {
  220.         case -1:
  221.         default:
  222.             return -1;
  223.         case 0 /* UNIT */: case DAY: case MONTH:
  224.             break;
  225.         }
  226.         /* TODO: ignore noise word "in" or "of" */
  227.         /* TODO: parse month name or year */
  228.         /* TODO: relativistic computation */
  229.     }
  230.     /* parse <timeunits> [<keyword>] */
  231.     for (i = 0; i < nf && psp->before < 0; i++) {
  232.         type = prsrdtoken(fields, nf, i, now, tm, tzp, psp);
  233.         if (type == -1 || type == 0)
  234.             return type;    /* utter success or utter failure */
  235.     }
  236.  
  237.     /* parse <date> and modify it according to <timeunits> <keyword> */
  238.     if (psp->before >= 0 && type == AGO) {
  239.         if (i < nf)    /* trailing noise after "ago"|"hence"? */
  240.             return -1;
  241.         /* no <date> after "ago"|"hence"; <timeunits> before/after now */
  242.         dt = time(&dt);
  243.     } else if (psp->before < 0 && i >= nf) {
  244.         /* no trailing <keyword> nor <date>; <timeunits> after now */
  245.         psp->before = 0;
  246.         dt = time(&dt);
  247.     } else if (psp->before >= 0 && i >= nf)
  248.         /* <keyword> but no trailing <date> */
  249.         return -1;
  250.     else {
  251.         /* <timeunits> <keyword> <date> */
  252.         struct timeb *ft = NULL;
  253.  
  254.         dt = trydate(fields + i, nf - i, ft, tm, tzp);
  255.         if (dt == -1)
  256.             dt = trydate(fields, nf, ft, tm, tzp);
  257.         if (dt == -1)
  258.             return -1;
  259.         dt = dateconv(tm, *tzp);
  260.     }
  261.     /* TODO: get the subtleties right here instead of approximating */
  262.     modifier = psp->secs + psp->months*30.5*24L*60L*60L;
  263.     if (psp->before)
  264.         dt -= modifier;
  265.     else
  266.         dt += modifier;
  267.     *tm = *gmtime(&dt);
  268.     *tzp = 0;
  269.     return 0;
  270. }
  271.