home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 11 Util / 11-Util.zip / dyltswch.lzh / DYLTSWCH.PLI < prev    next >
Text File  |  1995-04-06  |  20KB  |  444 lines

  1.  /*  Program to switch between daylight savings and standard time.
  2.  
  3.      This program sleeps in the background like a daemon. It wakes up
  4.      twice a year when it is time to switch between standard time
  5.      and daylight savings time. Apart from its initialization and those
  6.      two instances, it consumes no CPU cycles.
  7.  
  8.      It can be started automatically when the system boots by means
  9.      of a RUN= statement in CONFIG.SYS or a DETACH command in
  10.      STARTUP.CMD.
  11.  
  12.      E.g.
  13.      In CONFIG.SYS
  14.           RUN=D:\UTILS\DYLTSWCH.EXE
  15.      In STARTUP.CMD
  16.           DETACH D:\UTILS\DYLTSWCH.EXE
  17.  
  18.      In either case, you need to set the TZ environment variable, unless
  19.      you are in the eastern time zone of the United States of America.
  20.      For complete documentation on the TZ environment variable refer to
  21.      the OS/2 Online Bookshelf CD or the C Set ++ Programming Guide,
  22.      section 3.1.7.
  23.  
  24.      This program is distributed as is, with no warranties implied or
  25.      expressed. It is freely distributable and may be copied without
  26.      obtaining permission from its original author, provided that
  27.      it is unaltered and no charge is levied beyond the price of the
  28.      medium of its distribution. The author does not relinquish the
  29.      rights of this intellectual property.
  30.  
  31.      Copyright (C) 1995, David W. Noon
  32.  
  33.      */
  34.  
  35.  Daylight_savings_switch:
  36.  PROC(PARM) OPTIONS(MAIN NOEXECOPS REENTRANT) REORDER;
  37.  
  38.      DCL  PARM                          CHAR(100) VAR;
  39.  
  40.      DEFINE    /* Local data types */
  41.           STRUCTURE /* This reflects the TZ environment variable */
  42.           1    TIMEZONE,
  43.                2    TZ_std_id                CHAR(3),
  44.                2    TZ_offset_from_GMT       DEC FIXED(3,1),
  45.                2    TZ_daylight_savings_id   CHAR(3),
  46.                2    TZ_month_DST_starts      BIN FIXED(16,0) UNSIGNED,
  47.                2    TZ_week_DST_starts       DEC FIXED(1,0),
  48.                2    TZ_day_DST_starts        DEC FIXED(1,0),
  49.                2    TZ_time_DST_starts       BIN FIXED(31,0) UNSIGNED,
  50.                2    TZ_month_DST_ends        BIN FIXED(16,0) UNSIGNED,
  51.                2    TZ_week_DST_ends         DEC FIXED(1,0),
  52.                2    TZ_day_DST_ends          DEC FIXED(1,0),
  53.                2    TZ_time_DST_ends         BIN FIXED(31,0) UNSIGNED,
  54.                2    TZ_amount_of_shift       BIN FIXED(16,0) UNSIGNED;
  55.  
  56.           /* Named constants */
  57.      DCL  Seconds_in_a_day              BIN FIXED(31,0) UNSIGNED
  58.                                         VALUE(24*60*60),
  59.  
  60.           Current_date_and_time         CHAR(17),
  61.           (Current_year                 PIC '9999' POS(1),
  62.           Current_month                 PIC '99' POS(5),
  63.           Current_day                   PIC '99' POS(7),
  64.           Current_hour                  PIC '99' POS(9),
  65.           Current_min                   PIC '99' POS(11),
  66.           Current_sec                   PIC '99' POS(13),
  67.           Current_msec                  PIC '999' POS(15))
  68.                                         DEF Current_date_and_time,
  69.  
  70.           (Next_switch_in_date_time,Next_switch_out_date_time) CHAR(17),
  71.  
  72.           (Now,Today,Switch_in_day,Switch_out_day,Time_to_wait)
  73.                                         BIN FIXED(31,0) UNSIGNED,
  74.  
  75.           Time_zone                     TYPE TIMEZONE,
  76.  
  77.           (ADDR,DATETIME,DAYS,DAYSTODATE,STG,SUBSTR,TRIM) BUILTIN;
  78.  
  79.      CALL Parse_time_zone(Time_zone,TRIM(PARM)); /* Time zone details */
  80.  
  81.      DO LOOP; /* Keep going around until the system shuts down */
  82.           Current_date_and_time = DATETIME; /* Get date & time now */
  83.           Today = DAYS(Current_date_and_time); /* Lilian format date */
  84.           Now = ((Current_hour*60) + Current_min)*60 + Current_sec;
  85.           IF Current_msec >= 500 THEN /* Round up to next second */
  86.                Now += 1;
  87.  
  88.           /* We now need to determine when/what the next event will be:
  89.              switching from standard to daylight savings or
  90.              switching from daylight savings to standard.
  91.  
  92.              Convert the dates/times to switch each way into a Lilian
  93.              number. If either is less than today's Lilian number then
  94.              that event has expired so we need to obtain next year's
  95.              corresponding event's Lilian number. Whichever has the
  96.              lower Lilian number is the next event to occur.
  97.  
  98.              We then need to sleep until then, at which time we will
  99.              change the TOD clock, as appropriate. */
  100.  
  101.           PUT STRING(Next_switch_in_date_time) EDIT
  102.           (Current_year,Time_zone.TZ_month_DST_starts,
  103.                Day_of_month(Current_year,Time_zone.TZ_month_DST_starts,
  104.                     Time_zone.TZ_week_DST_starts,
  105.                     Time_zone.TZ_day_DST_starts),
  106.                Time_zone.TZ_time_DST_starts/3600,
  107.                MOD(Time_zone.TZ_time_DST_starts,3600)/60,
  108.                MOD(Time_zone.TZ_time_DST_starts,60),'000')
  109.           (P'9999',5 P'99',A);
  110.           /* Convert date to a Lilian number */
  111.           Switch_in_day = DAYS(Next_switch_in_date_time);
  112.           IF Today > Switch_in_day | (Today = Switch_in_day &
  113.                Now >= Time_zone.TZ_time_DST_starts) THEN
  114.           DO;  /* We are past this year's switch to DST */
  115.                PUT STRING(Next_switch_in_date_time) EDIT
  116.                (Current_year+1,Time_zone.TZ_month_DST_starts,
  117.                     Day_of_month(Current_year+1,
  118.                          Time_zone.TZ_month_DST_starts,
  119.                          Time_zone.TZ_week_DST_starts,
  120.                          Time_zone.TZ_day_DST_starts),
  121.                     SUBSTR(Next_switch_in_date_time,9))
  122.                (P'9999',2 P'99',A);
  123.                /* Convert date to a Lilian number */
  124.                Switch_in_day = DAYS(Next_switch_in_date_time);
  125.           END;
  126.  
  127.           PUT STRING(Next_switch_out_date_time) EDIT
  128.           (Current_year,Time_zone.TZ_month_DST_ends,
  129.                Day_of_month(Current_year,
  130.                     Time_zone.TZ_month_DST_ends,
  131.                     Time_zone.TZ_week_DST_ends,
  132.                     Time_zone.TZ_day_DST_ends),
  133.                Time_zone.TZ_time_DST_starts/3600,
  134.                MOD(Time_zone.TZ_time_DST_starts,3600)/60,
  135.                MOD(Time_zone.TZ_time_DST_starts,60),'000')
  136.           (P'9999',5 P'99',A);
  137.           /* Convert date to a Lilian number */
  138.           Switch_out_day = DAYS(Next_switch_out_date_time);
  139.           IF Today > Switch_out_day | (Today = Switch_out_day &
  140.                Now >= Time_zone.TZ_time_DST_ends) THEN
  141.           DO;  /* We are past this year's switch back to standard time */
  142.                PUT STRING(Next_switch_out_date_time) EDIT
  143.                (Current_year+1,Time_zone.TZ_month_DST_ends,
  144.                     Day_of_month(Current_year+1,
  145.                          Time_zone.TZ_month_DST_ends,
  146.                          Time_zone.TZ_week_DST_ends,
  147.                          Time_zone.TZ_day_DST_ends),
  148.                     SUBSTR(Next_switch_out_date_time,9))
  149.                (P'9999',2 P'99',A);
  150.                /* Convert date to a Lilian number */
  151.                Switch_out_day = DAYS(Next_switch_out_date_time);
  152.           END;
  153.  
  154.           /* Now see which switching event occurs first */
  155.           IF Switch_in_day < Switch_out_day THEN
  156.           DO;  /* Next switch is TO daylight savings */
  157.                Time_to_wait = (Switch_in_day - Today)*Seconds_in_a_day +
  158.                          Time_zone.TZ_time_DST_starts - Now;
  159.                CALL Sleep_for_seconds(Time_to_wait);
  160.                /* Now we'll spring forward. */
  161.                CALL Change_time(Time_zone.TZ_amount_of_shift);
  162.           END;
  163.           ELSE
  164.           DO;  /* Next switch is FROM daylight savings */
  165.                Time_to_wait = (Switch_out_day - Today)*Seconds_in_a_day
  166.                          + Time_zone.TZ_time_DST_ends - Now;
  167.                CALL Sleep_for_seconds(Time_to_wait);
  168.                /* Now we'll fall back. */
  169.                CALL Change_time(-Time_zone.TZ_amount_of_shift);
  170.           END;
  171.           /* To avoid ambiguity in selecting the next time to switch,
  172.              we will sleep for 24 hours. */
  173.           DELAY(Seconds_in_a_day*1000);
  174.      END; /* DO LOOP */
  175.      %PAGE;
  176.      /* The subroutine Parse_time_zone examines the PARM field passed
  177.         from the calling program and if it is non-blank uses it as
  178.         time-zone source, otherwise it reads the environment variable
  179.         TZ into a buffer (Var_buffer), and then splits it into its
  180.         constituent fields. These are returned to the caller in the
  181.         parameter Time_zone.
  182.  
  183.         If the variable has not been defined the values for AT&T
  184.         laboratories in New Jersey are returned, i.e. U.S. eastern
  185.         standard time. These are IBM's defaults. */
  186.  Parse_time_zone:
  187.      PROC(Time_zone,TZ_from_pgm) REORDER;
  188.           DCL  Time_zone                     TYPE TIMEZONE BYADDR,
  189.                TZ_from_pgm                   CHAR(*) VARZ NONASGN BYADDR;
  190.  
  191.           DCL  Var_buffer_ptr                PTR,
  192.                Var_buffer                    CHAR(256) VARZ
  193.                                              BASED(Var_buffer_ptr),
  194.                (l,p)                         BIN FIXED(16,0) UNSIGNED,
  195.                Minutes                       DEC FIXED(2,0),
  196.  
  197.                DosScanEnv                    ENTRY(CHAR(*) VARZ NONASGN,
  198.                                                   PTR)
  199.                                              OPTIONS(LINKAGE(SYSTEM)
  200.                                                   NODESCRIPTOR BYADDR)
  201.                                              RETURNS(BIN FIXED(31,0)
  202.                                                   OPTIONAL),
  203.  
  204.                (ADDR,DIVIDE,LEFT,LENGTH,RIGHT,SEARCH,SUBSTR) BUILTIN;
  205.  
  206.           /* Establish defaults */
  207.           Time_zone.TZ_std_id = 'EST';
  208.           Time_zone.TZ_offset_from_GMT = +5.0;
  209.           Time_zone.TZ_daylight_savings_id = 'EDT';
  210.           Time_zone.TZ_month_DST_starts = 4; /* April */
  211.           Time_zone.TZ_week_DST_starts = 1; /* 1st week */
  212.           Time_zone.TZ_day_DST_starts = 0; /* Sunday */
  213.           Time_zone.TZ_time_DST_starts = 3600; /* 1:00 AM */
  214.           Time_zone.TZ_month_DST_ends = 10; /* October */
  215.           Time_zone.TZ_week_DST_ends = -1; /* last week */
  216.           Time_zone.TZ_day_DST_ends = 0; /* Sunday */
  217.           Time_zone.TZ_time_DST_ends = 7200; /* 2:00 AM */
  218.           Time_zone.TZ_amount_of_shift = 3600; /* 1:00 hour */
  219.  
  220.           IF TZ_from_pgm ¬= '' THEN /* Use the PARM field */
  221.                Var_buffer_ptr = ADDR(TZ_from_pgm);
  222.           ELSE /* Obtain the TZ environment variable */
  223.                IF DosScanEnv('TZ',Var_buffer_ptr) ¬= 0 THEN
  224.                     RETURN; /* Not there - use defaults */
  225.  
  226.           Time_zone.TZ_std_id = LEFT(Var_buffer,3);
  227.           l = SEARCH(Var_buffer,',');
  228.           IF l = 0 THEN
  229.                l = LENGTH(Var_buffer) + 1;
  230.           Time_zone.TZ_daylight_savings_id = SUBSTR(Var_buffer,l-3,3);
  231.  
  232.           p = SEARCH(SUBSTR(Var_buffer,4,l-7),':');
  233.           IF p = 0 THEN /* Only hours - no minutes */
  234.                GET STRING(SUBSTR(Var_buffer,4,l-7))
  235.                     LIST(Time_zone.TZ_offset_from_GMT);
  236.           ELSE
  237.           DO;  /* Offset is +hh:mm or -hh:mm */
  238.                /* Get hours - perhaps with a sign */
  239.                GET STRING(SUBSTR(Var_buffer,4,p-1))
  240.                     LIST(Time_zone.TZ_offset_from_GMT);
  241.                /* Get minutes, without a sign */
  242.                GET STRING(SUBSTR(Var_buffer,4+p+1,l-p-8))
  243.                     LIST(Minutes);
  244.                /* Add on minutes, allowing for sign */
  245.                IF SUBSTR(Var_buffer,4,1) = '-' THEN
  246.                     Time_zone.TZ_offset_from_GMT -=
  247.                          ROUND(DIVIDE(Minutes,60.000,5,3),1);
  248.                ELSE
  249.                     Time_zone.TZ_offset_from_GMT +=
  250.                          ROUND(DIVIDE(Minutes,60.000,5,3),1);
  251.           END;
  252.           /* Extract the remaining numbers - default if absent */
  253.           IF l < LENGTH(Var_buffer) THEN
  254.                GET STRING(SUBSTR(Var_buffer,l+1))
  255.                     LIST(Time_zone.TZ_month_DST_starts,
  256.                     Time_zone.TZ_week_DST_starts,
  257.                     Time_zone.TZ_day_DST_starts,
  258.                     Time_zone.TZ_time_DST_starts,
  259.                     Time_zone.TZ_month_DST_ends,
  260.                     Time_zone.TZ_week_DST_ends,
  261.                     Time_zone.TZ_day_DST_ends,
  262.                     Time_zone.TZ_time_DST_ends,
  263.                     Time_zone.TZ_amount_of_shift);
  264.           RETURN;
  265.      END Parse_time_zone;
  266.      %PAGE;
  267.      /* Subroutine to encapsulate the DELAY statement with elongation */
  268.  Sleep_for_seconds:
  269.      PROC(Time_to_wait) REORDER;
  270.  
  271.           DCL  Time_to_wait                  BIN FIXED(31,0) UNSIGNED
  272.                                              BYVALUE;
  273.  
  274.           DCL  Max_wait_time                 BIN FIXED(31,0)
  275.                                              VALUE(2147483);
  276.  
  277.           DO WHILE(Time_to_wait > Max_wait_time);
  278.                /* We need to do it in steps */
  279.                DELAY(Max_wait_time*1000);
  280.                Time_to_wait -= Max_wait_time;
  281.           END;
  282.           DELAY(Time_to_wait*1000); /* in milliseconds */
  283.  
  284.           RETURN;
  285.      END Sleep_for_seconds;
  286.      %PAGE;
  287.      /* Function to calculate the day of the month defined by the
  288.         specified day of the week of the given week of the month.
  289.  
  290.         If the week is zero, the day is returned as is. */
  291.  Day_of_month:
  292.      PROC(Year,Month,Week,Day) OPTIONS(BYVALUE)
  293.                RETURNS(BIN FIXED(16,0) UNSIGNED) REORDER;
  294.           DCL  (Year,Month,Week,Day)         BIN FIXED(15,0);
  295.  
  296.           DCL  (Result,Days_in_this_month)   BIN FIXED(16,0) UNSIGNED,
  297.                Days_in_month(12)             BIN FIXED(16,0) UNSIGNED
  298.                                              STATIC NONASGN
  299.                                              INIT(31,28,31,30,31,30,31,
  300.                                              31,30,31,30,31),
  301.                Five_weeks_in_month           BIT(1),
  302.                (DIVIDE,MOD)                  BUILTIN;
  303.  
  304.           IF Week = 0 THEN
  305.                Result = Day;
  306.           ELSE
  307.           DO;
  308.                Days_in_this_month = Days_in_month(Month);
  309.                /* Check for February in a leap year */
  310.                IF Month = 2 & (MOD(Year,4) = 0 & (MOD(Year,100) ¬= 0 |
  311.                          MOD(Year,400) = 0)) THEN
  312.                      Days_in_this_month = 29;
  313.  
  314.                /* Use Zeller's congruence to determine what day the 1st
  315.                   of the month is. */
  316.                IF Month <= 2 THEN
  317.                DO;  /* Since these are passed by value, the caller's
  318.                        copy should not be updated. */
  319.                     Year -= 1;
  320.                     Month += 12;
  321.                END;
  322.  
  323.                Result = MOD(DIVIDE(Year,400,31,0) -
  324.                          DIVIDE(Year,100,31,0) + DIVIDE(Year,4,31,0) +
  325.                          Year + DIVIDE((Month + 1)*3,5,31,0) +
  326.                          Month + Month + 2,7);
  327.                          /* 0=Sun 1=Mon 2=Tue ... */
  328.  
  329.                /* Now calculate how many days into the month the
  330.                   first occurrence of that day of the week is. */
  331.                Result = MOD(Day-Result,7) + 1;
  332.                /* See if we are determining the "nth" or "nth last"
  333.                   occurrence of this day of the week in this month. */
  334.                IF Week > 0 THEN /* nth */
  335.                     Result +=  (Week - 1)*7;
  336.                ELSE
  337.                DO;  /* nth last */
  338.                     /* See if there are 5 occurrences of this
  339.                        day of the week in this month. */
  340.                     Five_weeks_in_month =
  341.                               Result < (Days_in_this_month - 28);
  342.  
  343.                     Result += (Week + 4)*7;
  344.                     IF Five_weeks_in_month THEN
  345.                          Result += 7;
  346.                END;
  347.           END;
  348.           RETURN(Result);
  349.      END Day_of_month;
  350.      %PAGE;
  351.  Change_time:
  352.      PROC(Increment) REORDER;
  353.           DCL  Increment                     BIN FIXED(15,0) BYVALUE;
  354.  
  355.           /* Note that the type DATETIME conflicts with the name of a
  356.              PL/I BUILTIN function and so, to avoid ambiguity, should
  357.              remain encapsulated inside this subroutine. */
  358.           DEFINE
  359.                STRUCTURE
  360.                1    DATETIME, /* System date and time structure */
  361.                     2    hours                    BIN FIXED(8,0)
  362.                                                   UNSIGNED,
  363.                     2    minutes                  BIN FIXED(8,0)
  364.                                                   UNSIGNED,
  365.                     2    seconds                  BIN FIXED(8,0)
  366.                                                   UNSIGNED,
  367.                     2    hundredths               BIN FIXED(8,0)
  368.                                                   UNSIGNED,
  369.                     2    day                      BIN FIXED(8,0)
  370.                                                   UNSIGNED,
  371.                     2    month                    BIN FIXED(8,0)
  372.                                                   UNSIGNED,
  373.                     2    year                     BIN FIXED(16,0)
  374.                                                   UNSIGNED,
  375.                     2    timezone                 BIN FIXED(15,0),
  376.                     2    day_of_week              BIN FIXED(8,0)
  377.                                                   UNSIGNED;
  378.  
  379.           DCL  Date_Time                     TYPE DATETIME,
  380.                Amount                        BIN FIXED(15,0),
  381.  
  382.                (DosGetDateTime,DosSetDateTime) ENTRY(TYPE DATETIME)
  383.                                              OPTIONS(LINKAGE(SYSTEM)
  384.                                                   NODESCRIPTOR BYADDR)
  385.                                              RETURNS(BIN FIXED(31,0)
  386.                                                   OPTIONAL),
  387.  
  388.                REM                           BUILTIN;
  389.  
  390.           IF DosGetDateTime(Date_Time) ¬= 0 THEN
  391.                SIGNAL ERROR;
  392.  
  393.           Amount = REM(Increment,60);
  394.           IF Amount ¬= 0 THEN
  395.           SELECT; /* Allow for overflow into the next minute */
  396.                WHEN((Amount + Date_Time.seconds) < 0)
  397.                DO;
  398.                     Date_Time.minutes -= 1;
  399.                     IF Date_time.minutes < 0 THEN
  400.                     DO;
  401.                          Date_Time.minutes += 60;
  402.                          Date_Time.hours -= 1;
  403.                     END;
  404.                     Date_Time.seconds += Amount + 60;
  405.                END;
  406.                WHEN((Amount + Date_Time.seconds) > 60)
  407.                DO;
  408.                     Date_Time.minutes += 1;
  409.                     IF Date_time.minutes = 60 THEN
  410.                     DO;
  411.                          Date_Time.minutes = 0;
  412.                          Date_Time.hours += 1;
  413.                     END;
  414.                     Date_Time.seconds += Amount - 60;
  415.                END;
  416.                OTHERWISE
  417.                     Date_Time.seconds += Amount;
  418.           END;
  419.  
  420.           Amount = REM(Increment,3600)/60;
  421.           IF Amount ¬= 0 THEN
  422.           SELECT; /* Allow for overflow into the next hour */
  423.                WHEN((Amount + Date_Time.minutes) < 0)
  424.                DO;
  425.                     Date_Time.hours -= 1;
  426.                     Date_Time.minutes += Amount + 60;
  427.                END;
  428.                WHEN((Amount + Date_Time.minutes) > 60)
  429.                DO;
  430.                     Date_Time.hours += 1;
  431.                     Date_Time.minutes += Amount - 60;
  432.                END;
  433.                OTHERWISE
  434.                     Date_Time.minutes += Amount;
  435.           END;
  436.  
  437.           Date_Time.hours += Increment/3600;
  438.  
  439.           IF DosSetDateTime(Date_Time) ¬= 0 THEN
  440.                SIGNAL ERROR;
  441.           RETURN;
  442.      END Change_time;
  443.  END Daylight_savings_switch;
  444.