home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 11 Util / 11-Util.zip / gettz11.zip / gettz.cpp < prev    next >
Text File  |  1998-01-09  |  18KB  |  678 lines

  1. /*****************************************************************************
  2.  
  3.   Author:     Steve Marvin
  4.   Program:    GetTZ.cpp
  5.   Version:    1.1
  6.  
  7.   Created:    1.0 December 1997
  8.  
  9.   Purpose:    Parse the environment string TZ= and set the correct timezone
  10.           in the INI file of a "Time-Zone-Challenged" program. It's only
  11.           reason for existence is to eliminate the requirement of editing
  12.           a bunch of INI files and property pages every time the timezone
  13.           changes.
  14.  
  15.   LastMod:    1.1 January 1998
  16.  
  17.          Changed setup function calculation for start and stop times
  18.          to use a more strict interpretation of the TZ string. When
  19.          the week/weekday mode is used it is now interpreted as
  20.          the n'th wday of the month, eg:
  21.  
  22.              1'st Sunday, 3'rd Saturday from beginning or end.
  23.  
  24.          Replaced borlands tzset() function with tzvalid(), tzset()
  25.          was not capable of correctly parsing the AAAhh:mm:ssBBB
  26.          format of the TZ string as specified by IBM.
  27.  
  28.   Arguments:  1. IniFileSpecification      (fully qualified path)
  29.           2. Application name for ini file entry
  30.           3. Key name for ini file entry
  31.  
  32.           GetTz /?  prints a line explaining the syntax
  33.  
  34.   Returns:    The current time zone value (int)
  35.  
  36.   Written for Borland C++ version 2.0.
  37.  
  38.   Assumes:   1. global long variable "timezone" set by the runtime from
  39.         the timezone number in the string using the runtime
  40.         function tzset().
  41.  
  42.          2. Syntax of the timezone string is as follows:
  43.  
  44.         SET TZ=IWT-2IST,3,3,5,0000,9,2,6,0000,3600
  45.  
  46.         where the various parts are:
  47.  
  48.         IST and IDT are arbitrary labels for the winter and summer
  49.         clocks of your locality; the -2 between them means winter time
  50.         here is two hours ahead of GMT.
  51.  
  52.         The next four numbers are respectively the month, week of the
  53.         month, and day of the week on which the winter to summer
  54.         transition takes place and the 24-hour time at which this
  55.         happens. (Sunday is day 0 for this purpose.)
  56.  
  57.         The following four numbers are the same thing for the summer
  58.         to winter transition.
  59.  
  60.         The last number is the number of seconds that the summer clock
  61.         is advanced past the winter clock, here 3600 seconds -- one hour.
  62.  
  63.         You can omit any of the numbers that you don't need,
  64.         e.g. if you don't need the summer-time advance, only hold
  65.         the places with commas.
  66.  
  67.          3. The default string here is EST5EDT,4,1,0,7200,10,-1,0,7200,3600
  68.  
  69.   Notes: This code was kept very simple, no fancy features of C used to make it
  70.      readable by anyone (so that you can see what is being done).
  71.  
  72.      This is a one-time function, to implement this correctly one needs to
  73.      do the following:
  74.  
  75.          1.  static struct {
  76.              long      dst_offset=3600;
  77.              long      base_zone=5*60*60;
  78.              long      curr_zone=5*60*60;
  79.              time_t    start_summer_t;
  80.              struct tm start_summer_blk;
  81.              time_t    stop_summer_t;
  82.              struct tm stop_summer_blk;
  83.              }
  84.  
  85.           2. Init this from the environment string.
  86.           3. Replace all functions in the runtime that handle gmt
  87.          conversion and/or dst offset - in borland c these
  88.          occur in the following modules (TZSet, GMTime, TimeCvt
  89.          and UTime), eliminate any dst offset because the
  90.          curr_zone will always be correct.
  91.           4. Each call to localtime or gmtime should do the following:
  92.  
  93.              If current time >= stop_summer_t
  94.              {
  95.             if (curr_zone != base_zone)
  96.             {
  97.                 set curr_zone to base_zone
  98.                 if start_summer_blk.tm_year == stop_summer_blk
  99.                 {
  100.                   start_summer_blk.tm_year++
  101.                   recalc using mktime the start_summer_t;
  102.                 }
  103.             }
  104.              }
  105.              else if current time >= start_summer_t
  106.              {
  107.             if (curr_zone == base_zone)
  108.             {
  109.                set curr_zone = base_zone - dst_offset
  110.                if (start_summer_blk.tm_year != stop_summer_blk.tm_year)
  111.                {
  112.                   stop_summer_blk.tm_year++;
  113.                   recalc using mktime the stop_summer_t;
  114.                }
  115.             }
  116.              }
  117.  
  118.  
  119. ******************************************************************************/
  120.  
  121.  // comment this out for final build, used for printf string output
  122.  // #define DEBUG_OUT 1
  123.  
  124.  #include <ctype.h>
  125.  
  126.  #define INCL_WINSHELLDATA   /* Or use INCL_WIN or INCL_PM */
  127.  #include <os2.h>
  128.  
  129.  #include <stdlib.h>
  130.  #include <stdio.h>
  131.  #include <string.h>
  132.  #include <time.h>
  133.  #include <mem.h>
  134.  
  135. #define  issign(c)   (((c) == '-') || ((c) == '+'))
  136.  
  137. #define  nextweekday(c) (c = (c==6) ? 0 : c+1)
  138. #define  prevweekday(c) (c = (c==0) ? 6 : c-1)
  139.  
  140. /*  #define  _RUN_TIME_HANDLES_TZ   since the borland runtime does not
  141.                     process the full TZ= line, this must
  142.                     be commented out
  143. */
  144.  
  145. /* default to 1 hour the difference between summer and winter time           */
  146. static int dst_offset=3600;
  147.  
  148. #ifndef _RUN_TIME_HANDLES_TZ
  149.  
  150. static char Days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
  151. /* leap year difference in february is handled at run time below             */
  152.  
  153. /*****************************************************************************
  154.  
  155.   Get the next token from the environment string (delimited by a comma) and
  156.   verify that it IS numeric.
  157.  
  158. ******************************************************************************/
  159. int next_numeric_token (int* pRet)
  160. {
  161.    int     vRet=FALSE, gotsign=FALSE;
  162.    char    *pb;
  163.  
  164.    pb = strtok(NULL, ",");
  165.    if (pb != NULL)
  166.    {
  167.        for (int i=0; i<strlen(pb); i++)
  168.        {
  169.       if (!isdigit(pb[i]))
  170.       {
  171.          if (gotsign)
  172.          {
  173.         return vRet;
  174.          }
  175.          else if (issign(pb[i]))
  176.          {
  177.         gotsign = TRUE;
  178.          }
  179.          else
  180.          {
  181.         return vRet;
  182.          }
  183.       }
  184.        }
  185.        *pRet = atoi(pb);
  186.        vRet = TRUE;
  187.    }
  188.  
  189.    return vRet;
  190. }
  191.  
  192. /*****************************************************************************
  193.  
  194.   Extract one set of time information from the environment string in the
  195.   format MONTH,WEEK,DAY,SECS.
  196.  
  197.      MONTH         Month number 1 to 11, January is 1
  198.      WEEK          Week of the month to use
  199.               1,2,3,4    First, Second, Third, Fourth week
  200.               -1..-4     Last to First week
  201.               0          DAY is the number of day in month
  202.      DAY           If WEEK is 0 this is the day number (1..31) to use
  203.            otherwise it is 0..6 with 0=Sunday and 6=Saturday
  204.      SECS          Number of seconds since midnight.
  205.  
  206.   Args      year        Current year MOD 100 (eg 97 for 1997)
  207.         fday        Day (0..6) of first day of year (0=Sunday)
  208.         month       Default month
  209.         week        Default week
  210.         day         Default day
  211.         secs        Default seconds
  212.  
  213.   Returns   -1 or a valid time
  214.  
  215. ******************************************************************************/
  216. time_t check_one_set (int year, int fday, int month, int week, int day, int secs)
  217. {
  218.    time_t     retV=-1;
  219.    struct tm  tblock;
  220.    int        i, n;
  221.  
  222.    memset (&tblock, 0, sizeof(tblock));
  223.    tblock.tm_year = year;
  224.  
  225.    if ( (next_numeric_token (&n)) && (n > 0) && (n < 13) )
  226.    {
  227.       month = n;
  228.    }
  229.    if ( (next_numeric_token (&n)) && (n > -5) && (n < 5) )
  230.    {
  231.       week = n;
  232.    }
  233.    if ( (next_numeric_token (&n)) && (n >= 0) && (n < 7) )
  234.    {
  235.       day = n;
  236.    }
  237.    if (next_numeric_token (&n))
  238.    {
  239.       secs = n;
  240.    }
  241.  
  242.    tblock.tm_mon = month-1;
  243.  
  244.    #ifdef DEBUG_OUT
  245.       printf ("check_one_set year=%d, month=%d, week=%d, day=%d, secs=%d\n", year, month, week, day, secs);
  246.    #endif
  247.  
  248.    /* now find first DAY of this month */
  249.    n = 0;
  250.    for (i=0; i<tblock.tm_mon; i++)
  251.    {
  252.       n += Days[i];
  253.    }
  254.    n = n % 7;
  255.    while (n != 0)
  256.    {
  257.       nextweekday(fday);
  258.       n--;
  259.    }
  260.  
  261.    #ifdef DEBUG_OUT
  262.       printf ("              first day number=%d\n", fday);
  263.    #endif
  264.  
  265.    if (week == 0)
  266.    {
  267.       /* use day as the absolute day number                        */
  268.       tblock.tm_mday = day;
  269.    }
  270.    else
  271.    {
  272.  
  273. /* original method found 'week' week and then 'day' day number
  274.        if (week > 0)
  275.        {
  276.       // 1..4 first second ... week in month
  277.       tblock.tm_mday = ((week-1)*7)+1;
  278.        }
  279.        else
  280.        {
  281.       // -1..-4 first second ... week from end
  282.       week = -week;
  283.       tblock.tm_mday = ((4-week)*7)+1;
  284.        }
  285.  
  286.        // tblock.tm_mday is now the first day of the week
  287.        // use day to get the exact day number
  288.        while (fday != day)
  289.        {
  290.       tblock.tm_mday++;
  291.       nextweekday(fday);
  292.        }
  293. */
  294. // new method at version 1.1
  295.        if (week > 0)
  296.        {
  297.       // 1..4 first second ... week in month
  298.       // start at first day of month == 1
  299.       tblock.tm_mday = 1;
  300.  
  301.       // Now move forward till the week'th day number
  302.       while ( (fday != day) && (week != 0) )
  303.       {
  304.          tblock.tm_mday++;
  305.          nextweekday(fday);
  306.          if (fday == day)
  307.          {
  308.         week--;
  309.          }
  310.       }
  311.        }
  312.        else
  313.        {
  314.       // -1..-4 first second ... week from end
  315.       // start at last day of the month
  316.       tblock.tm_mday = Days[tblock.tm_mon];
  317.  
  318.       // get day of week of last day of month
  319.       n = tblock.tm_mday-1; // zero based not 1..31
  320.       while (n != 0)
  321.       {
  322.          nextweekday(fday);
  323.          n--;
  324.       }
  325.    #ifdef DEBUG_OUT
  326.       printf ("New fday=%d, tm_mday=%d\n", fday, tblock.tm_mday);
  327.    #endif
  328.       // Now move backward till the week'th day number
  329.       while ( (fday != day) && (week != 0) )
  330.       {
  331.          tblock.tm_mday--;
  332.          prevweekday(fday);
  333.          if (fday == day)
  334.          {
  335.         week++;
  336.          }
  337.       }
  338.        }
  339.    } // end else week != 0
  340.  
  341.    while (secs >= 60*60)
  342.    {
  343.       tblock.tm_hour++;
  344.       secs -= (60*60);
  345.    }
  346.    while (secs >= 60)
  347.    {
  348.       tblock.tm_min++;
  349.       secs -= 60;
  350.    }
  351.    tblock.tm_sec = secs;
  352.  
  353.    tblock.tm_isdst = -1;
  354.  
  355.    /* mktime fills in the remainder of the fields and returns the system time */
  356.    retV = mktime(&tblock);
  357.  
  358.    #ifdef DEBUG_OUT
  359.       printf ("       tm_*   mon=%d, mday=%d, wday=%d, yday=%d, hour=%d, min=%d, sec=%d, isdst?=%s\n",
  360.           tblock.tm_mon, tblock.tm_mday, tblock.tm_wday, tblock.tm_yday,
  361.           tblock.tm_hour, tblock.tm_min, tblock.tm_sec,
  362.           (tblock.tm_isdst)?"Yes":"No");
  363.    #endif
  364.    return retV;
  365. }
  366.  
  367.  
  368. /*****************************************************************************
  369.  
  370.   This function substitutes for the tzset() in the runtime, because borland
  371.   does not correctly parse the TZ string missing the +/-hh:mm:ss option
  372.  
  373.   Returns   TRUE if the AAAnnBBB portion of the environment string is
  374.         valid (also sets timezone variable).
  375.  
  376. ******************************************************************************/
  377. int tzvalid(char* env){
  378.    int   iret=0, n;
  379.    char  c, str[_MAX_PATH], *pb;
  380.  
  381.    timezone=0;
  382.  
  383.    if ( (strlen(env) > 3) && isalpha(env[0]) && isalpha(env[1]) && isalpha(env[2]) )
  384.    {
  385.       /* leading part of string is valid three character standard time abbreviation */
  386.       strcpy (str, env);
  387.       str[2]=',';
  388.  
  389.       pb=&str[3];
  390.       while ((c=pb[0]) != 0)
  391.       {
  392.      if (isalpha(c))
  393.      {
  394.  
  395.          pb[0] = 0;
  396.          pb++;
  397.          if ( (strlen(pb) < 2) || (!isalpha(pb[0])) || (!isalpha(pb[1])) )
  398.          {
  399.          #ifdef DEBUG_OUT
  400.             printf ("Trailing ascii grouping missing\n");
  401.          #endif
  402.          return iret;
  403.          }
  404.          break;
  405.      }
  406.      if (c == ':')
  407.      {
  408.         pb[0] = ',';
  409.      }
  410.      else if ( !isdigit(c) && !issign(c) )
  411.      {
  412.         #ifdef DEBUG_OUT
  413.         printf ("Time zone number is not numeric\n");
  414.          #endif
  415.          return iret;     }
  416.      pb++;
  417.       }
  418.       strtok (str, ",");
  419.  
  420.       /* string tokens now in format
  421.         nn
  422.         +nn
  423.         -nn
  424.         +/-hh,mm,ss
  425.       */
  426.  
  427.      if (next_numeric_token(&n))
  428.      {
  429.      timezone = (long)n*60L*60L;          /* hours converted to seconds   */
  430.      if (next_numeric_token(&n))
  431.      {
  432.         timezone += (long)n*60L;          /* minutes converted to seconds */
  433.         if (next_numeric_token(&n))
  434.         {
  435.            timezone += (long)n;           /* seconds                      */
  436.         }
  437.      }
  438.      iret = 1;
  439.       }
  440.       #ifdef DEBUG_OUT
  441.     else
  442.     {
  443.        printf ("Time zone number is not numeric\n");
  444.     }
  445.       #endif
  446.  
  447.    }
  448.    #ifdef DEBUG_OUT
  449.      else
  450.      {
  451.     printf ("First three character ascii grouping missing\n");
  452.      }
  453.      if (iret==1)
  454.        printf ("TimeZone variable set to %ld\n", timezone);
  455.    #endif
  456.  
  457.    return iret;
  458. }
  459.  
  460.  
  461. /*****************************************************************************
  462.  
  463.   This function substitutes for the _IsDst() in the runtime, because borland
  464.   does not parse the entire TZ string and uses hard coded values.
  465.  
  466.   Returns   TRUE if the current local time is within the summer time
  467.         block, FALSE if it is outside (before or after).
  468.  
  469. ******************************************************************************/
  470. int is_it_dst (){
  471.    time_t      currtime;
  472.    struct tm   *ptm, tblock;
  473.    char        str[_MAX_PATH], *pb, *env;
  474.    int         n, fday;
  475.    time_t      start, stop;
  476.  
  477.    /* first disable dst checking so that mktime calls work
  478.       correctly - runtime was resetting 02:00 to 01:00 when
  479.       on the dst boundary
  480.    */
  481.    daylight = FALSE;
  482.  
  483.    /* gets time of day                                    */
  484.    currtime = time(NULL);
  485.  
  486.    /* converts date/time to a structure                   */
  487.    ptm = localtime(&currtime);
  488.  
  489.    /* I have to parse the part of the string past EST5EDT */
  490.    env = getenv("TZ");
  491.    #ifdef DEBUG_OUT
  492.        printf ("Raw TZ string=%s\n", env);
  493.    #endif
  494.  
  495.    // parse check string and set timezone variable in time.h
  496.    if (tzvalid(env))
  497.    {
  498.        strcpy (str, env);
  499.    }
  500.    else
  501.    {
  502.        /* default is first Sunday in April at 0200 start  */
  503.        /* and last Sunday in October to end, 1hr offset   */
  504.        strcpy (str,"EST5EDT,4,1,0,7200,10,-1,0,7200,3600");
  505.        #ifdef DEBUG_OUT
  506.       printf ("Environment TZ incorrect, using default=%s\n", str);
  507.        #endif
  508.        timezone = 5*60L*60L;
  509.    }   /* discard the first token "EST5EDT,"                  */
  510.    strtok (str, ",");
  511.  
  512.    /* if ptm->tm_year is a leap year adjust Days[1] to 29 */
  513.    n = ptm->tm_year + 1900;
  514.    if ( ( ((ptm->tm_year % 4) == 0) && ((ptm->tm_year % 100) != 0) )
  515.     || ((ptm->tm_year % 400) == 0) )
  516.    {
  517.       Days[1]++;
  518.    }
  519.  
  520.    /* find what day the first day of this year is                             */
  521.  
  522.    fday = ptm->tm_wday;
  523.    n = ptm->tm_yday % 7;
  524.    while (n != 0)
  525.    {
  526.       prevweekday(fday);
  527.       n--;
  528.    }
  529.    #ifdef DEBUG_OUT
  530.       printf ("Day number of first day of this year is %d\n", fday);
  531.    #endif
  532.    start = check_one_set (ptm->tm_year, fday, 4, 1, 0, 7200);
  533.    if (start != -1)
  534.    {
  535.       /* valid dst start time, now parse and check stop  */
  536.       stop = check_one_set(ptm->tm_year, fday, 10, -1, 0, 7200);
  537.       if (stop != -1)
  538.       {
  539.      /* start and stop are valid, get the dst offset */
  540.      /* it is already preset to the default 3600     */
  541.      if (next_numeric_token(&n))
  542.      {
  543.         dst_offset = n;
  544.      }
  545.  
  546.      #ifdef DEBUG_OUT
  547.         printf ("Time zone offset in seconds=%lu\n", dst_offset);
  548.         printf ("Start time    %lu\n", start);
  549.         printf ("Current time  %lu\n", currtime);
  550.         printf ("Stop time     %lu\n", stop);
  551.         if ( (currtime >= start) && (currtime <= stop) )
  552.            printf ("In DST\n");
  553.         else
  554.            printf ("Not in DST\n");
  555.      #endif
  556.      return ( (currtime >= start) && (currtime <= stop) );
  557.       }
  558.    }
  559.  
  560.    return ptm->tm_isdst;
  561. }
  562.  
  563. #else
  564. /*****************************************************************************
  565.  
  566.   If the runtime correctly handles the entire TZ= string, this code is
  567.   enough to tell if we are within summer time.
  568.  
  569.   Returns   TRUE if the current local time is within the summer time
  570.         block, FALSE if it is outside (before or after).
  571.  
  572. ******************************************************************************/
  573. int is_it_dst ()
  574. {
  575.    time_t      currtime;
  576.    struct tm   *ptm;
  577.  
  578.    /* Set up timezone from environment string TZ=         */
  579.    tzset();
  580.  
  581.    /* gets time of day                                    */
  582.    currtime = time(NULL);
  583.  
  584.    /* converts date/time to a structure                   */
  585.    ptm = localtime(&currtime);
  586.  
  587.    return ptm->tm_isdst;
  588. }
  589. #endif // _RUN_TIME_HANDLES_TZ
  590.  
  591.  
  592. int main(int argc, char* argv[])
  593. {
  594.    long        lt;
  595.    char           str[_MAX_PATH], inifile[_MAX_PATH], *pb;
  596.    int         s, isdst=FALSE;
  597.    HAB         hab;         /* Anchor-block handle        */
  598.    HINI        hini;        /* Initialization-file handle */
  599.    FILE*       fp;
  600.  
  601.  
  602.    /* adjust timezone if in daylight savings time         */
  603.    if (is_it_dst())
  604.    {
  605.      isdst = TRUE;
  606.      lt = dst_offset - timezone;
  607.    }
  608.    else
  609.    {
  610.       lt = -timezone;
  611.    }
  612.  
  613.    /* convert it from seconds to hours                    */
  614.    lt /= (60L*60L);
  615.  
  616.    switch (argc)
  617.    {
  618.       case 0 :
  619.       case 1 :
  620.     printf ("SET TZ=%s\n", getenv("TZ"));
  621.     printf ("TimeZone is %d, ", (int)lt);
  622.     printf ("%s Time\n", (isdst) ? "Summer(Daylight Savings)" : "Winter(Standard)");
  623.     printf ("GetTZ /h for more help\n");
  624.  
  625.     break;
  626.  
  627.       case 2 :
  628.      pb = argv[1];
  629.      if ( (pb[0] == '/') || (pb[0] == '-') )
  630.      {
  631.          pb++;
  632.      }
  633.      if ( (pb[0] == '?') || (toupper(pb[0]) == 'H') )
  634.      {
  635.          printf ("GetTZ version 1.1\n");
  636.          printf ("  Syntax:  'GetTZ IniFileSpec Application KeyName'\n");
  637.          printf ("  Example: 'GetTZ C:\\SOUTHSIDE\\PMMAIL\\PMMAIL.Ini GLOBAL TZOFFSET'\n");
  638.      }
  639.      break;
  640.  
  641.       // this was erroneously case 3 : in 1.0 preventing any ini file from being updated!
  642.       default :
  643.      /* argv[1] is ini filespecification */
  644.      strcpy (inifile, argv[1]);
  645.      if ( ((s=strlen(inifile)) > 4) && (memicmp(&inifile[s-4], ".INI", 4)) )
  646.      {
  647.         if (inifile[strlen(inifile)-1] != '.')
  648.           strcat (inifile, ".");
  649.         strcat (inifile, "Ini");
  650.      }
  651.      if ((fp = fopen(inifile, "r")) == NULL)
  652.      {
  653.         sprintf (str, "File '%s' ", inifile);
  654.         perror(str);
  655.         break;
  656.      }
  657.      else
  658.      {
  659.         fclose(fp);
  660.      }
  661.      hini = PrfOpenProfile(hab, inifile);
  662.  
  663.      if (hini != NULLHANDLE)
  664.      {
  665.          /* argv[2] is the application and argv[3] is the key */
  666.          PrfWriteProfileData(hini, argv[2], argv[3], <, sizeof(long));
  667.          PrfCloseProfile(hini);
  668.      }
  669.      break;
  670.    }
  671.  
  672.    #ifdef DEBUG_OUT
  673.       printf ("Time zone = %ld\n", lt);
  674.    #endif
  675.  
  676.    return (int)lt;
  677.  
  678. }