home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 11 Util
/
11-Util.zip
/
dyltswch.lzh
/
DYLTSWCH.PLI
< prev
next >
Wrap
Text File
|
1995-04-06
|
20KB
|
444 lines
/* Program to switch between daylight savings and standard time.
This program sleeps in the background like a daemon. It wakes up
twice a year when it is time to switch between standard time
and daylight savings time. Apart from its initialization and those
two instances, it consumes no CPU cycles.
It can be started automatically when the system boots by means
of a RUN= statement in CONFIG.SYS or a DETACH command in
STARTUP.CMD.
E.g.
In CONFIG.SYS
RUN=D:\UTILS\DYLTSWCH.EXE
In STARTUP.CMD
DETACH D:\UTILS\DYLTSWCH.EXE
In either case, you need to set the TZ environment variable, unless
you are in the eastern time zone of the United States of America.
For complete documentation on the TZ environment variable refer to
the OS/2 Online Bookshelf CD or the C Set ++ Programming Guide,
section 3.1.7.
This program is distributed as is, with no warranties implied or
expressed. It is freely distributable and may be copied without
obtaining permission from its original author, provided that
it is unaltered and no charge is levied beyond the price of the
medium of its distribution. The author does not relinquish the
rights of this intellectual property.
Copyright (C) 1995, David W. Noon
*/
Daylight_savings_switch:
PROC(PARM) OPTIONS(MAIN NOEXECOPS REENTRANT) REORDER;
DCL PARM CHAR(100) VAR;
DEFINE /* Local data types */
STRUCTURE /* This reflects the TZ environment variable */
1 TIMEZONE,
2 TZ_std_id CHAR(3),
2 TZ_offset_from_GMT DEC FIXED(3,1),
2 TZ_daylight_savings_id CHAR(3),
2 TZ_month_DST_starts BIN FIXED(16,0) UNSIGNED,
2 TZ_week_DST_starts DEC FIXED(1,0),
2 TZ_day_DST_starts DEC FIXED(1,0),
2 TZ_time_DST_starts BIN FIXED(31,0) UNSIGNED,
2 TZ_month_DST_ends BIN FIXED(16,0) UNSIGNED,
2 TZ_week_DST_ends DEC FIXED(1,0),
2 TZ_day_DST_ends DEC FIXED(1,0),
2 TZ_time_DST_ends BIN FIXED(31,0) UNSIGNED,
2 TZ_amount_of_shift BIN FIXED(16,0) UNSIGNED;
/* Named constants */
DCL Seconds_in_a_day BIN FIXED(31,0) UNSIGNED
VALUE(24*60*60),
Current_date_and_time CHAR(17),
(Current_year PIC '9999' POS(1),
Current_month PIC '99' POS(5),
Current_day PIC '99' POS(7),
Current_hour PIC '99' POS(9),
Current_min PIC '99' POS(11),
Current_sec PIC '99' POS(13),
Current_msec PIC '999' POS(15))
DEF Current_date_and_time,
(Next_switch_in_date_time,Next_switch_out_date_time) CHAR(17),
(Now,Today,Switch_in_day,Switch_out_day,Time_to_wait)
BIN FIXED(31,0) UNSIGNED,
Time_zone TYPE TIMEZONE,
(ADDR,DATETIME,DAYS,DAYSTODATE,STG,SUBSTR,TRIM) BUILTIN;
CALL Parse_time_zone(Time_zone,TRIM(PARM)); /* Time zone details */
DO LOOP; /* Keep going around until the system shuts down */
Current_date_and_time = DATETIME; /* Get date & time now */
Today = DAYS(Current_date_and_time); /* Lilian format date */
Now = ((Current_hour*60) + Current_min)*60 + Current_sec;
IF Current_msec >= 500 THEN /* Round up to next second */
Now += 1;
/* We now need to determine when/what the next event will be:
switching from standard to daylight savings or
switching from daylight savings to standard.
Convert the dates/times to switch each way into a Lilian
number. If either is less than today's Lilian number then
that event has expired so we need to obtain next year's
corresponding event's Lilian number. Whichever has the
lower Lilian number is the next event to occur.
We then need to sleep until then, at which time we will
change the TOD clock, as appropriate. */
PUT STRING(Next_switch_in_date_time) EDIT
(Current_year,Time_zone.TZ_month_DST_starts,
Day_of_month(Current_year,Time_zone.TZ_month_DST_starts,
Time_zone.TZ_week_DST_starts,
Time_zone.TZ_day_DST_starts),
Time_zone.TZ_time_DST_starts/3600,
MOD(Time_zone.TZ_time_DST_starts,3600)/60,
MOD(Time_zone.TZ_time_DST_starts,60),'000')
(P'9999',5 P'99',A);
/* Convert date to a Lilian number */
Switch_in_day = DAYS(Next_switch_in_date_time);
IF Today > Switch_in_day | (Today = Switch_in_day &
Now >= Time_zone.TZ_time_DST_starts) THEN
DO; /* We are past this year's switch to DST */
PUT STRING(Next_switch_in_date_time) EDIT
(Current_year+1,Time_zone.TZ_month_DST_starts,
Day_of_month(Current_year+1,
Time_zone.TZ_month_DST_starts,
Time_zone.TZ_week_DST_starts,
Time_zone.TZ_day_DST_starts),
SUBSTR(Next_switch_in_date_time,9))
(P'9999',2 P'99',A);
/* Convert date to a Lilian number */
Switch_in_day = DAYS(Next_switch_in_date_time);
END;
PUT STRING(Next_switch_out_date_time) EDIT
(Current_year,Time_zone.TZ_month_DST_ends,
Day_of_month(Current_year,
Time_zone.TZ_month_DST_ends,
Time_zone.TZ_week_DST_ends,
Time_zone.TZ_day_DST_ends),
Time_zone.TZ_time_DST_starts/3600,
MOD(Time_zone.TZ_time_DST_starts,3600)/60,
MOD(Time_zone.TZ_time_DST_starts,60),'000')
(P'9999',5 P'99',A);
/* Convert date to a Lilian number */
Switch_out_day = DAYS(Next_switch_out_date_time);
IF Today > Switch_out_day | (Today = Switch_out_day &
Now >= Time_zone.TZ_time_DST_ends) THEN
DO; /* We are past this year's switch back to standard time */
PUT STRING(Next_switch_out_date_time) EDIT
(Current_year+1,Time_zone.TZ_month_DST_ends,
Day_of_month(Current_year+1,
Time_zone.TZ_month_DST_ends,
Time_zone.TZ_week_DST_ends,
Time_zone.TZ_day_DST_ends),
SUBSTR(Next_switch_out_date_time,9))
(P'9999',2 P'99',A);
/* Convert date to a Lilian number */
Switch_out_day = DAYS(Next_switch_out_date_time);
END;
/* Now see which switching event occurs first */
IF Switch_in_day < Switch_out_day THEN
DO; /* Next switch is TO daylight savings */
Time_to_wait = (Switch_in_day - Today)*Seconds_in_a_day +
Time_zone.TZ_time_DST_starts - Now;
CALL Sleep_for_seconds(Time_to_wait);
/* Now we'll spring forward. */
CALL Change_time(Time_zone.TZ_amount_of_shift);
END;
ELSE
DO; /* Next switch is FROM daylight savings */
Time_to_wait = (Switch_out_day - Today)*Seconds_in_a_day
+ Time_zone.TZ_time_DST_ends - Now;
CALL Sleep_for_seconds(Time_to_wait);
/* Now we'll fall back. */
CALL Change_time(-Time_zone.TZ_amount_of_shift);
END;
/* To avoid ambiguity in selecting the next time to switch,
we will sleep for 24 hours. */
DELAY(Seconds_in_a_day*1000);
END; /* DO LOOP */
%PAGE;
/* The subroutine Parse_time_zone examines the PARM field passed
from the calling program and if it is non-blank uses it as
time-zone source, otherwise it reads the environment variable
TZ into a buffer (Var_buffer), and then splits it into its
constituent fields. These are returned to the caller in the
parameter Time_zone.
If the variable has not been defined the values for AT&T
laboratories in New Jersey are returned, i.e. U.S. eastern
standard time. These are IBM's defaults. */
Parse_time_zone:
PROC(Time_zone,TZ_from_pgm) REORDER;
DCL Time_zone TYPE TIMEZONE BYADDR,
TZ_from_pgm CHAR(*) VARZ NONASGN BYADDR;
DCL Var_buffer_ptr PTR,
Var_buffer CHAR(256) VARZ
BASED(Var_buffer_ptr),
(l,p) BIN FIXED(16,0) UNSIGNED,
Minutes DEC FIXED(2,0),
DosScanEnv ENTRY(CHAR(*) VARZ NONASGN,
PTR)
OPTIONS(LINKAGE(SYSTEM)
NODESCRIPTOR BYADDR)
RETURNS(BIN FIXED(31,0)
OPTIONAL),
(ADDR,DIVIDE,LEFT,LENGTH,RIGHT,SEARCH,SUBSTR) BUILTIN;
/* Establish defaults */
Time_zone.TZ_std_id = 'EST';
Time_zone.TZ_offset_from_GMT = +5.0;
Time_zone.TZ_daylight_savings_id = 'EDT';
Time_zone.TZ_month_DST_starts = 4; /* April */
Time_zone.TZ_week_DST_starts = 1; /* 1st week */
Time_zone.TZ_day_DST_starts = 0; /* Sunday */
Time_zone.TZ_time_DST_starts = 3600; /* 1:00 AM */
Time_zone.TZ_month_DST_ends = 10; /* October */
Time_zone.TZ_week_DST_ends = -1; /* last week */
Time_zone.TZ_day_DST_ends = 0; /* Sunday */
Time_zone.TZ_time_DST_ends = 7200; /* 2:00 AM */
Time_zone.TZ_amount_of_shift = 3600; /* 1:00 hour */
IF TZ_from_pgm ¬= '' THEN /* Use the PARM field */
Var_buffer_ptr = ADDR(TZ_from_pgm);
ELSE /* Obtain the TZ environment variable */
IF DosScanEnv('TZ',Var_buffer_ptr) ¬= 0 THEN
RETURN; /* Not there - use defaults */
Time_zone.TZ_std_id = LEFT(Var_buffer,3);
l = SEARCH(Var_buffer,',');
IF l = 0 THEN
l = LENGTH(Var_buffer) + 1;
Time_zone.TZ_daylight_savings_id = SUBSTR(Var_buffer,l-3,3);
p = SEARCH(SUBSTR(Var_buffer,4,l-7),':');
IF p = 0 THEN /* Only hours - no minutes */
GET STRING(SUBSTR(Var_buffer,4,l-7))
LIST(Time_zone.TZ_offset_from_GMT);
ELSE
DO; /* Offset is +hh:mm or -hh:mm */
/* Get hours - perhaps with a sign */
GET STRING(SUBSTR(Var_buffer,4,p-1))
LIST(Time_zone.TZ_offset_from_GMT);
/* Get minutes, without a sign */
GET STRING(SUBSTR(Var_buffer,4+p+1,l-p-8))
LIST(Minutes);
/* Add on minutes, allowing for sign */
IF SUBSTR(Var_buffer,4,1) = '-' THEN
Time_zone.TZ_offset_from_GMT -=
ROUND(DIVIDE(Minutes,60.000,5,3),1);
ELSE
Time_zone.TZ_offset_from_GMT +=
ROUND(DIVIDE(Minutes,60.000,5,3),1);
END;
/* Extract the remaining numbers - default if absent */
IF l < LENGTH(Var_buffer) THEN
GET STRING(SUBSTR(Var_buffer,l+1))
LIST(Time_zone.TZ_month_DST_starts,
Time_zone.TZ_week_DST_starts,
Time_zone.TZ_day_DST_starts,
Time_zone.TZ_time_DST_starts,
Time_zone.TZ_month_DST_ends,
Time_zone.TZ_week_DST_ends,
Time_zone.TZ_day_DST_ends,
Time_zone.TZ_time_DST_ends,
Time_zone.TZ_amount_of_shift);
RETURN;
END Parse_time_zone;
%PAGE;
/* Subroutine to encapsulate the DELAY statement with elongation */
Sleep_for_seconds:
PROC(Time_to_wait) REORDER;
DCL Time_to_wait BIN FIXED(31,0) UNSIGNED
BYVALUE;
DCL Max_wait_time BIN FIXED(31,0)
VALUE(2147483);
DO WHILE(Time_to_wait > Max_wait_time);
/* We need to do it in steps */
DELAY(Max_wait_time*1000);
Time_to_wait -= Max_wait_time;
END;
DELAY(Time_to_wait*1000); /* in milliseconds */
RETURN;
END Sleep_for_seconds;
%PAGE;
/* Function to calculate the day of the month defined by the
specified day of the week of the given week of the month.
If the week is zero, the day is returned as is. */
Day_of_month:
PROC(Year,Month,Week,Day) OPTIONS(BYVALUE)
RETURNS(BIN FIXED(16,0) UNSIGNED) REORDER;
DCL (Year,Month,Week,Day) BIN FIXED(15,0);
DCL (Result,Days_in_this_month) BIN FIXED(16,0) UNSIGNED,
Days_in_month(12) BIN FIXED(16,0) UNSIGNED
STATIC NONASGN
INIT(31,28,31,30,31,30,31,
31,30,31,30,31),
Five_weeks_in_month BIT(1),
(DIVIDE,MOD) BUILTIN;
IF Week = 0 THEN
Result = Day;
ELSE
DO;
Days_in_this_month = Days_in_month(Month);
/* Check for February in a leap year */
IF Month = 2 & (MOD(Year,4) = 0 & (MOD(Year,100) ¬= 0 |
MOD(Year,400) = 0)) THEN
Days_in_this_month = 29;
/* Use Zeller's congruence to determine what day the 1st
of the month is. */
IF Month <= 2 THEN
DO; /* Since these are passed by value, the caller's
copy should not be updated. */
Year -= 1;
Month += 12;
END;
Result = MOD(DIVIDE(Year,400,31,0) -
DIVIDE(Year,100,31,0) + DIVIDE(Year,4,31,0) +
Year + DIVIDE((Month + 1)*3,5,31,0) +
Month + Month + 2,7);
/* 0=Sun 1=Mon 2=Tue ... */
/* Now calculate how many days into the month the
first occurrence of that day of the week is. */
Result = MOD(Day-Result,7) + 1;
/* See if we are determining the "nth" or "nth last"
occurrence of this day of the week in this month. */
IF Week > 0 THEN /* nth */
Result += (Week - 1)*7;
ELSE
DO; /* nth last */
/* See if there are 5 occurrences of this
day of the week in this month. */
Five_weeks_in_month =
Result < (Days_in_this_month - 28);
Result += (Week + 4)*7;
IF Five_weeks_in_month THEN
Result += 7;
END;
END;
RETURN(Result);
END Day_of_month;
%PAGE;
Change_time:
PROC(Increment) REORDER;
DCL Increment BIN FIXED(15,0) BYVALUE;
/* Note that the type DATETIME conflicts with the name of a
PL/I BUILTIN function and so, to avoid ambiguity, should
remain encapsulated inside this subroutine. */
DEFINE
STRUCTURE
1 DATETIME, /* System date and time structure */
2 hours BIN FIXED(8,0)
UNSIGNED,
2 minutes BIN FIXED(8,0)
UNSIGNED,
2 seconds BIN FIXED(8,0)
UNSIGNED,
2 hundredths BIN FIXED(8,0)
UNSIGNED,
2 day BIN FIXED(8,0)
UNSIGNED,
2 month BIN FIXED(8,0)
UNSIGNED,
2 year BIN FIXED(16,0)
UNSIGNED,
2 timezone BIN FIXED(15,0),
2 day_of_week BIN FIXED(8,0)
UNSIGNED;
DCL Date_Time TYPE DATETIME,
Amount BIN FIXED(15,0),
(DosGetDateTime,DosSetDateTime) ENTRY(TYPE DATETIME)
OPTIONS(LINKAGE(SYSTEM)
NODESCRIPTOR BYADDR)
RETURNS(BIN FIXED(31,0)
OPTIONAL),
REM BUILTIN;
IF DosGetDateTime(Date_Time) ¬= 0 THEN
SIGNAL ERROR;
Amount = REM(Increment,60);
IF Amount ¬= 0 THEN
SELECT; /* Allow for overflow into the next minute */
WHEN((Amount + Date_Time.seconds) < 0)
DO;
Date_Time.minutes -= 1;
IF Date_time.minutes < 0 THEN
DO;
Date_Time.minutes += 60;
Date_Time.hours -= 1;
END;
Date_Time.seconds += Amount + 60;
END;
WHEN((Amount + Date_Time.seconds) > 60)
DO;
Date_Time.minutes += 1;
IF Date_time.minutes = 60 THEN
DO;
Date_Time.minutes = 0;
Date_Time.hours += 1;
END;
Date_Time.seconds += Amount - 60;
END;
OTHERWISE
Date_Time.seconds += Amount;
END;
Amount = REM(Increment,3600)/60;
IF Amount ¬= 0 THEN
SELECT; /* Allow for overflow into the next hour */
WHEN((Amount + Date_Time.minutes) < 0)
DO;
Date_Time.hours -= 1;
Date_Time.minutes += Amount + 60;
END;
WHEN((Amount + Date_Time.minutes) > 60)
DO;
Date_Time.hours += 1;
Date_Time.minutes += Amount - 60;
END;
OTHERWISE
Date_Time.minutes += Amount;
END;
Date_Time.hours += Increment/3600;
IF DosSetDateTime(Date_Time) ¬= 0 THEN
SIGNAL ERROR;
RETURN;
END Change_time;
END Daylight_savings_switch;