home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / elm / elm2.4 / lib / date_util.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-01-27  |  13.0 KB  |  502 lines

  1.  
  2. static char rcsid[] = "@(#)$Id: date_util.c,v 5.2 1993/01/27 20:54:24 syd Exp $";
  3.  
  4. /*******************************************************************************
  5.  *  The Elm Mail System  -  $Revision: 5.2 $   $State: Exp $
  6.  *
  7.  *             Copyright (c) 1993 USENET Community Trust
  8.  *******************************************************************************
  9.  * Bug reports, patches, comments, suggestions should be sent to:
  10.  *
  11.  *    Syd Weinstein, Elm Coordinator
  12.  *    elm@DSI.COM            dsinc!elm
  13.  *
  14.  *******************************************************************************
  15.  * $Log: date_util.c,v $
  16.  * Revision 5.2  1993/01/27  20:54:24  syd
  17.  * There is a small bug in this routine in Chip's PL21 patch.  The code
  18.  * calls atonum to convert the 4 digit timezone field.  However this field
  19.  * is actually of the form HHMM, not a decimal number, so for example
  20.  * -0500 was being converted to -500 rather than the correct -300.
  21.  * From: Larry Philps <larryp@sco.COM>
  22.  *
  23.  * Revision 5.1  1993/01/19  04:46:21  syd
  24.  * Initial Checkin
  25.  *
  26.  *
  27.  ******************************************************************************/
  28.  
  29. #include "headers.h"
  30. #include <ctype.h>
  31.  
  32. /*
  33.  * Date processing functions:
  34.  *
  35.  * cvt_dayname_to_daynum() - Convert day of week name to a number.
  36.  * cvt_monthname_to_monthnum() - Convert month name to a number.
  37.  * cvt_yearstr_to_yearnum() - Convert year from string to a number.
  38.  * cvt_mmddyy_to_dayofyear() - Convert numeric day/month/year to day of year.
  39.  * cvt_timezone_to_offset() - Convert timezone string to an offset in mins.
  40.  * cvt_timestr_to_hhmmss() - Convert an HH:MM:SS str to numeric hours/mins/secs.
  41.  * make_gmttime() - Calculate number of seconds since the epoch.
  42.  */
  43.  
  44.  
  45. #define IsLeapYear(yr)    ((yr % 4 == 0) && ((yr % 100 != 0) || (yr % 400 == 0)))
  46.  
  47.  
  48. /*
  49.  * The following time zones are taken from a variety of sources.  They
  50.  * are by no means exhaustive, but seem to include most of those in
  51.  * common usage.  A comprehensive list is impossible, since the same
  52.  * abbreviation is sometimes used to mean different things in different
  53.  * parts of the world.
  54.  */
  55. static struct tzone {
  56.     char *str;        /* time zone name */
  57.     int offset;        /* offset, in minutes, EAST of GMT */
  58. } tzone_info[] = {
  59.  
  60.     /* the following are from RFC-822 */
  61.     { "ut", 0 },
  62.     { "gmt", 0 },
  63.     { "est", -5*60 },    { "edt", -4*60 },    /* USA eastern standard */
  64.     { "cst", -6*60 },    { "cdt", -5*60 },    /* USA central standard */
  65.     { "mst", -7*60 },    { "mdt", -6*60 },    /* USA mountain standard */
  66.     { "pst", -8*60 },    { "pdt", -7*60 },    /* USA pacific standard */
  67.     { "z", 0 }, /* zulu time (the rest of the military codes are bogus) */
  68.  
  69.     /* popular European timezones */
  70.     { "wet", 0*60 },                /* western european */
  71.     { "met", 1*60 },                /* middle european */
  72.     { "eet", 2*60 },                /* eastern european */
  73.     { "bst", 1*60 },                /* ??? british summer time */
  74.  
  75.     /* Canadian timezones */
  76.     { "ast", -4*60 },    { "adt", -3*60 },    /* atlantic */
  77.     { "nst", -3*60-30 },{ "ndt", -2*60-30 },    /* newfoundland */
  78.     { "yst", -9*60 },    { "ydt", -8*60 },    /* yukon */
  79.     { "hst", -10*60 },                /* hawaii (not really canada) */
  80.  
  81.     /* Asian timezones */
  82.     { "jst", 9*60 },                /* japan */
  83.     { "sst", 8*60 },                /* singapore */
  84.  
  85.     /* South-Pacific timezones */
  86.     { "nzst", 12*60 },    { "nzdt", 13*60 },    /* new zealand */
  87.     { "wst", 8*60 },    { "wdt", 9*60 },    /* western australia */
  88.  
  89.     /*
  90.      * Daylight savings modifiers.  These are not real timezones.
  91.      * They are used for things like "met dst".  The "met" timezone
  92.      * is 1*60, and applying the "dst" modifier makes it 2*60.
  93.      */
  94.     { "dst", 1*60 },
  95.     { "dt", 1*60 },
  96.     { "st", 1*60 },
  97.  
  98.     /*
  99.      * There's also central and eastern australia, but they insist on using
  100.      * cst, est, etc., which would be indistinguishable for the USA zones.
  101.      */
  102.  
  103.      { NULL, 0 },
  104. };
  105.  
  106. static char *month_name[13] = {
  107.     "jan", "feb", "mar", "apr", "may", "jun",
  108.     "jul", "aug", "sep", "oct", "nov", "dec", NULL
  109. };
  110.  
  111. static char *day_name[8] = {
  112.     "sun", "mon", "tue", "wed", "thu", "fri", "sat", 0
  113. };
  114.  
  115. static int month_len[13] = {
  116.     31, 99, 31, 30, 31, 30,
  117.     31, 31, 30, 31, 30, 31, 0
  118. };
  119.  
  120.  
  121. int cvt_dayname_to_daynum(str, day_p)
  122. char *str;
  123. int *day_p;
  124. {
  125.     /*
  126.      * Convert a day name to number (Sun = 1).  Only the first three
  127.      * characters are significant and comparison is case insensitive.
  128.      * That is, "Saturday", "sat", and "SATxyzfoobar" all return 7.
  129.      * Returns TRUE if a valid day name is found, otherwise FALSE.
  130.      */
  131.  
  132.     int i;
  133.  
  134.     for (i = 0 ; day_name[i] != NULL ; i++) {
  135.     if (strincmp(day_name[i], str, 3) == 0) {
  136.         *day_p = i+1;
  137.         return TRUE;
  138.     }
  139.     }
  140.  
  141.     dprint(4, (debugfile, "cvt_dayname_to_daynum failed at \"%s\"\n", str));
  142.     return FALSE;
  143. }
  144.  
  145.  
  146.  
  147. int cvt_monthname_to_monthnum(str, month_p)
  148. char *str;
  149. int *month_p;
  150. {
  151.     /*
  152.      * Convert a month name to number (Jan = 1).  Only the first three
  153.      * characters are significant and comparison is case insensitive.
  154.      * That is, "December", "dec", and "DECxyzfoobar" all return 12.
  155.      * Returns TRUE if a valid month name is found, otherwise FALSE.
  156.      */
  157.  
  158.     int i;
  159.  
  160.     for (i = 0 ; month_name[i] != NULL ; i++) {
  161.     if (strincmp(month_name[i], str, 3) == 0) {
  162.         *month_p = i+1;
  163.         return TRUE;
  164.     }
  165.     }
  166.  
  167.     dprint(4, (debugfile, "cvt_monthname_to_monthnum failed at \"%s\"\n", str));
  168.     return FALSE;
  169. }
  170.  
  171.  
  172. int cvt_yearstr_to_yearnum(str, year_p)
  173. char *str;
  174. int *year_p;
  175. {
  176.     /*
  177.      * Convert a year from a string to a number.  We will add the century
  178.      * into two-digit strings, e.g. "91" becomes "1991".  Returns TRUE
  179.      * if a reasonable year is specified, else FALSE;
  180.      */
  181.  
  182.     int year;
  183.  
  184.     if ((year = atonum(str)) > 0) {
  185.     if (year < 70) {
  186.         *year_p = 2000 + year;
  187.         return TRUE;
  188.     }
  189.     if (year < 100) {
  190.         *year_p = 1900 + year;
  191.         return TRUE;
  192.     }
  193.     if (year >= 1900 && year <= 2099) {
  194.         *year_p = year;
  195.         return TRUE;
  196.     }
  197.     }
  198.  
  199.     dprint(4, (debugfile, "cvt_yearstr_to_yearnum failed at \"%s\"\n", str));
  200.     return FALSE;
  201. }
  202.  
  203.  
  204. int cvt_mmddyy_to_dayofyear(month, dayofmon, year, dayofyear_p)
  205. int month, dayofmon, year, *dayofyear_p;
  206. {
  207.     /*
  208.      * Convert numeric month, day of month, and year to day of year
  209.      * (Jan 1 = 0).  Always returns TRUE.
  210.      */
  211.  
  212.     int dayofyear, i;
  213.  
  214.     dayofyear = dayofmon-1;
  215.     for (i = 0 ; i < month-1 ; ++i)
  216.     dayofyear += (i != 1 ? month_len[i] : (IsLeapYear(year) ? 29 : 28));
  217.     *dayofyear_p = dayofyear;
  218.     return TRUE;
  219. }
  220.  
  221.  
  222. int cvt_timezone_to_offset(str, mins_p)
  223. char *str;
  224. int *mins_p;
  225. {
  226.     /*
  227.      * Convert a timezone to a number of minutes *east* of gmt.  The
  228.      * timezone can either be a name or an offset, e.g. "+0600".  We also
  229.      * handle two-digit numeric timezones, e.g. "+06", even though they
  230.      * are bogus.  IMPORTANT:  If we are given a two-digit numeric timezone
  231.      * we will rewrite the string into a legal timezone by appending
  232.      * "00".  Returns TRUE if a valid timezone is found, otherwise FALSE.
  233.      */
  234.  
  235.     struct tzone *p; 
  236.     int tz;
  237.  
  238.     /*
  239.      * Check for two-digit or four-digit numeric timezone.
  240.      */
  241.     if ((*str == '+' || *str == '-') && (tz = cvt_numtz_to_mins(str+1)) >= 0) {
  242.     switch (strlen(str)) {
  243.     case 3:                    /* +NN        */
  244.         (void) strcat(str, "00");        /*  make +NN00    */
  245.         tz *= 60;
  246.         break;
  247.     case 5:                    /* +NNNN    */
  248.         break;
  249.     default:                /* eh?        */
  250.         goto failed;
  251.     }
  252.     *mins_p = (*str == '-' ? -tz : tz);
  253.     return TRUE;
  254.     }
  255.  
  256.     /*
  257.      * Check for timezone name.  I'm told some brain damaged systems
  258.      * can put a "-" before a tz name.
  259.      */
  260.     if (*str == '-') {
  261.     tz = -1;
  262.     ++str;
  263.     } else {
  264.     tz = 1;
  265.     }
  266.     for (p = tzone_info; p->str; p++) {
  267.     if (istrcmp(p->str, str) == 0) {
  268.         *mins_p = tz * p->offset;
  269.         return TRUE;
  270.     }
  271.     }
  272.  
  273. failed:
  274.     /*
  275.      * We parse a lot of stuff where the timezone is optional, and this
  276.      * routine gets a lot of fields that are actually year numbers.  The
  277.      * debug message is an annoying distraction in these cases.
  278.      */
  279.     if (!isdigit(*str)) {
  280.     dprint(4, (debugfile,
  281.         "cvt_timezone_to_offset failed at \"%s\"\n", str));
  282.     }
  283.     return FALSE;
  284. }
  285.  
  286.  
  287. int cvt_numtz_to_mins(str)
  288. char *str;
  289. {
  290.     /*
  291.      * Convert an HHMM string to minutes.  Check to make sure that the
  292.      * string is exactly 4 characters long, and contains all digits.
  293.      * Return -1 if it is not a valid string.
  294.      */
  295.     register int tz;
  296.  
  297.     if (strlen(str) != 4)
  298.     return -1;
  299.  
  300.     /* Process the first 2 characters, ie. the HH part */
  301.     if (!isdigit(str[0]))
  302.     goto bad_tz_str;
  303.     tz = (str[0] - '0') * 10;
  304.     if (!isdigit(str[1]))
  305.     goto bad_tz_str;
  306.     tz += (str[1] - '0');
  307.  
  308.     /* That takes care of the hours, multiple by 60 to get minutes */
  309.     tz *= 60;
  310.  
  311.     /* Process the second 2 characters, ie. the MM part */
  312.     if (!isdigit(str[2]))
  313.     goto bad_tz_str;
  314.     tz += (str[2] - '0') * 10;
  315.     if (!isdigit(str[3]))
  316.     goto bad_tz_str;
  317.     tz += (str[3] - '0');
  318.  
  319.     /* Conversion succeeded */
  320.  
  321.     return tz;
  322.  
  323. bad_tz_str:
  324.     dprint(7,(debugfile,"ridiculous numeric timezone: %s\n",str));
  325.     return -1;
  326. }
  327.  
  328.  
  329. int cvt_timestr_to_hhmmss(str, hours_p, mins_p, secs_p)
  330. char *str;
  331. int *hours_p, *mins_p, *secs_p;
  332. {
  333.     /*
  334.      * Convert a HH:MM[:SS] time specification to hours, minutes, seconds.
  335.      * We will also handle a couple of (bogus) variations:  a simple "HHMM"
  336.      * as well as an "am/pm" suffix (thank BITNET for the latter).
  337.      */
  338.  
  339.     char tmp[STRING], *s;
  340.     int add_hrs, i;
  341.  
  342.     /*
  343.      * Make a copy so we can step on it.
  344.      */
  345.     str = strfcpy(tmp, str, sizeof(tmp));
  346.  
  347.     /*
  348.      * Yank any AM/PM off the end.
  349.      */
  350.     add_hrs = 0;
  351.     if ((i = strlen(str)) > 3) {
  352.     if (istrcmp(str+i-2, "am") == 0) {
  353.         str[i-2] = '\0';
  354.     } else if (istrcmp(str+i-2, "pm") == 0) {
  355.         str[i-2] = '\0';
  356.         add_hrs = 12;
  357.     }
  358.     }
  359.  
  360.     /*
  361.      * It ain't legal, but accept "HHMM".
  362.      */
  363.     if (strlen(str) == 4 && (i = atonum(str)) > 0) {
  364.     *hours_p = i/60 + add_hrs;
  365.     *mins_p = i%60;
  366.     *secs_p = 0;
  367.     return TRUE;
  368.     }
  369.  
  370.     /*
  371.      * Break it up as HH:MM[:SS].
  372.      */
  373.     for (s = str ; isdigit(*s) ; ++s)        /* At end of loop: */
  374.     ;                    /*    HH:MM:SS     */
  375.     if (*s == ':') {                /* str^ ^s         */
  376.     *s++ = '\0';
  377.     *hours_p = atoi(str) + add_hrs;
  378.     str = s;
  379.     for (s = str ; isdigit(*s) ; ++s)    /* At end of loop: */
  380.         ;                    /*    HH:MM:SS     */
  381.     if (*s == '\0') {            /*    str^ ^s      */
  382.         *mins_p = atoi(str);
  383.         *secs_p = 0;
  384.         return TRUE;
  385.     }
  386.     if (*s == ':') {
  387.         *s++ = '\0';
  388.         *mins_p = atoi(str);
  389.         *secs_p = atoi(s);
  390.         return TRUE;
  391.     }
  392.     }
  393.  
  394.     dprint(4, (debugfile, "cvt_timestr_to_hhmmss failed at \"%s\"\n", str));
  395.     return FALSE;
  396. }
  397.  
  398.  
  399. long make_gmttime(year, month, day, hours, mins, secs)
  400. int year, month, day, hours, mins, secs;
  401. {
  402.     /*
  403.      * Convert date specification to seconds since epoch (1 Jan 1970 00:00).
  404.      */
  405.  
  406.     long days_since_epoch, secs_since_midnight;
  407.     int y1, d1;
  408.  
  409.     /*
  410.      * Rationalize year to the epoch.
  411.      */
  412.     y1 = year - 1970;
  413.  
  414.     /*
  415.      * Calculate number of days since the epoch.
  416.      */
  417.     (void) cvt_mmddyy_to_dayofyear(month, day, year, &d1);
  418.     days_since_epoch = y1*365 + (y1+1)/4 + d1;
  419.  
  420.     /*
  421.      * Calculate number of seconds since midnight.
  422.      */
  423.     secs_since_midnight = ((hours*60) + mins)*60 + secs;
  424.  
  425.     /*
  426.      * Calculate seconds since epoch.
  427.      */
  428.     return days_since_epoch*(24*60*60) + secs_since_midnight;
  429. }
  430.  
  431.  
  432. #ifdef _TEST /*{*/
  433.  
  434. int getitem(prompt, item)
  435. char *prompt, *item;
  436. {
  437.     char buf[1024];
  438.     printf("%s [%s] > ", prompt, item);
  439.     fflush(stdout);
  440.     if (gets(buf) == NULL)
  441.         return -1;
  442.     if (buf[0] != '\0')
  443.         (void) strcpy(item, buf);
  444.     return 0;
  445. }
  446.  
  447. main()
  448. {
  449.     char dowstr[256], domstr[256], monstr[256], yrstr[256],
  450.         tzstr[256], timestr[256];
  451.     int dow, dom, mon, yr, tzmins, dayofyr, hrs, mins, secs;
  452.     long gmttime;
  453.     extern char *ctime();
  454.  
  455.     (void) strcpy(dowstr, "Monday");
  456.     (void) strcpy(domstr, "1");
  457.     (void) strcpy(monstr, "January");
  458.     (void) strcpy(yrstr, "1980");
  459.     (void) strcpy(tzstr, "GMT");
  460.     (void) strcpy(timestr, "00:00:00");
  461.  
  462.     for (;;) {
  463.         if (getitem("day of week", dowstr) != 0)
  464.             break;
  465.         if (getitem("month", monstr) != 0)
  466.             break;
  467.         if (getitem("day", domstr) != 0)
  468.             break;
  469.         if (getitem("year", yrstr) != 0)
  470.             break;
  471.         if (getitem("timezone", tzstr) != 0)
  472.             break;
  473.         if (getitem("time", timestr) != 0)
  474.             break;
  475.         if (!cvt_dayname_to_daynum(dowstr, &dow))
  476.             fputs("cvt_dayname_to_daynum failed\n", stderr);
  477.         if (!cvt_monthname_to_monthnum(monstr, &mon))
  478.             fputs("cvt_monthname_to_monthnum failed\n", stderr);
  479.         dom = atoi(domstr);
  480.         if (!cvt_yearstr_to_yearnum(yrstr, &yr))
  481.             fputs("cvt_yearstr_to_yearnum failed\n", stderr);
  482.         if (!cvt_timezone_to_offset(tzstr, &tzmins))
  483.             fputs("cvt_timezone_to_offset failed\n", stderr);
  484.         if (!cvt_mmddyy_to_dayofyear(mon, dom, yr, &dayofyr))
  485.             fputs("cvt_mmddyy_to_dayofyear failed\n", stderr);
  486.         if (!cvt_timestr_to_hhmmss(timestr, &hrs, &mins, &secs))
  487.             fputs("cvt_timestr_to_hhmmss failed\n", stderr);
  488.         gmttime = make_gmttime(yr, mon, dom, hrs, mins+tzmins, secs);
  489.         printf("date=%04d/%02d/%02d  time=%02d:%02d:%02d  tzmins=%d\n",
  490.             yr, mon, dom, hrs, mins, secs, tzmins);
  491.         printf("day-of-week=%d day-of-year=%d gmttime=%ld gmtdate=%s",
  492.             dow, dayofyr, gmttime, ctime(&gmttime));
  493.         putchar('\n');
  494.     }
  495.  
  496.     putchar('\n');
  497.     exit(0);
  498. }
  499.  
  500. #endif /*}_TEST*/
  501.  
  502.