dst.pl
1 #!/usr/bin/perl -w
2
3 # Display DST transition times
4
5 use English;
6 use strict;
7 use Time::Local;
8 use POSIX qw(strftime);
9 use Getopt::Std;
10
11 use constant VRSN => '[1.04]';
12
13 # 1.0 09/14/1998 acs
14 # 1.01 01/03/2001 acs changed '%e' strftime format specifier to '%d' because
15 # stupid Windows does not understand real strftime
16 # 1.02 09/10/2003 acs minor change; subtract 1900 from $year command-line arg;
17 # the code works w/o this change but this is more proper
18 # 1.03 09/12/2003 acs added -n option to print only the next time change
19 # 1.04 10/01/2003 acs added -d option to output days (integer) until next time
20 # change
21
22 use constant SECONDS_PER_DAY => ( 24 * 60 * 60 );
23 use constant FALSE => 0;
24 use constant TRUE => 1;
25
26 use constant TWO_DATES_MODE => 0;
27 use constant NEXT_MODE => 1;
28 use constant DAYS_LEFT_MODE => 2;
29
30 # ---------------------------------------------------------------
31
32 sub problem {
33 my $msg = $_[0];
34 my $err = $_[1];
35
36 printf STDERR ( "%s: %s (%d).\n", $PROGRAM_NAME, $msg, $err );
37 printf STDERR ("\n");
38 return ($err);
39 } # problem
40
41 sub usage {
42 printf STDERR ( "\nUsage: %s [-y year | -n | -d] [-e] [-u]\n\n",
43 $PROGRAM_NAME );
44 printf STDERR (
45 " -y year - determine time changes for year; by default, the current\n"
46 );
47 printf STDERR (" year is used.\n");
48 printf STDERR (" -n - display only the next time change.\n");
49 printf STDERR (
50 " -d - display remaining days until next time change.\n");
51 printf STDERR (
52 " -u - print this usage message on stderr and exit.\n");
53 printf STDERR (
54 " -e - print the epoch seconds rather than the formatted times\n"
55 );
56 printf STDERR (
57 " if used with -d, display integer rather than fractional\n"
58 );
59 printf STDERR (" days remaining until next time change.\n\n");
60 printf STDERR (
61 "If successful, %s returns a zero result and writes two lines on\n",
62 $PROGRAM_NAME
63 );
64 printf STDERR ("stdout of this form:\n");
65 printf STDERR (
66 " Sun Apr 5 01:59:59 CST 1998 --> Sun Apr 5 03:00:00 CDT 1998\n");
67 printf STDERR (
68 " Sun Oct 25 01:59:59 CDT 1998 --> Sun Oct 25 01:00:00 CST 1998\n");
69 printf STDERR (" OR (if using -e option)\n");
70 printf STDERR (" 891763199 --> 891763200\n");
71 printf STDERR (" 909298799 --> 909298800\n\n");
72 printf STDERR (
73 "These indicate the time displayed at the transition time and that\n");
74 printf STDERR ("displayed 1 second later.\n\n");
75 printf STDERR (
76 "A non-zero result is returned if no TZ is known or no time transition\n"
77 );
78 printf STDERR ("occurs.\n\n");
79 printf STDERR ( "Vrsn %s\n", VRSN );
80 return (1);
81 } # usage
82
83 sub find_dst # returns the last epoch second $target_isdst is in effect
84 {
85 use integer;
86
87 my $lo = $_[0];
88 my $max_hi = $_[1];
89 my $initial_interval = $_[2];
90 my $target_isdst = $_[3];
91 my ( $begin_seconds, $next_seconds ) = ( -1, -1 );
92 my $isdst = FALSE;
93
94 # advance by one interval until DST changes
95 my $hi = $lo;
96 my $fnd = FALSE;
97 my $hi_knt = 0;
98 while ( !($fnd) && ( $hi <= $max_hi ) && ( $hi_knt < 2 ) ) {
99 $isdst = ( localtime($hi) )[8];
100 if ( $isdst != $target_isdst ) {
101 $fnd = TRUE;
102 }
103 else {
104 $lo = $hi;
105 $hi += $initial_interval;
106 if ( $hi > $max_hi ) {
107 $hi = $max_hi;
108 ++$hi_knt;
109 }
110 }
111 }
112 if ($fnd) # now start looking within $interval
113 {
114 my $go_down = TRUE;
115 my $tmp_seconds = $hi;
116 my $interval = ( $hi - $lo ) / 2;
117 while ( $interval > 0 ) {
118 if ($go_down) {
119 $tmp_seconds = $hi - $interval;
120 $isdst = ( localtime($tmp_seconds) )[8];
121 if ( $isdst == $target_isdst ) {
122 $go_down = FALSE;
123 $lo = $tmp_seconds;
124 }
125 else {
126 $hi = $tmp_seconds;
127 }
128 }
129 else {
130 $tmp_seconds = $lo + $interval;
131 $isdst = ( localtime($tmp_seconds) )[8];
132 if ( $isdst != $target_isdst ) {
133 $go_down = TRUE;
134 $hi = $tmp_seconds;
135 }
136 else {
137 $lo = $tmp_seconds;
138 }
139 }
140 $interval = ( $hi - $lo ) / 2;
141 }
142 if ( ( ( localtime($tmp_seconds) )[8] ) !=
143 ( ( localtime( $tmp_seconds + 1 ) )[8] ) )
144 {
145 $begin_seconds = $tmp_seconds;
146 $next_seconds = $tmp_seconds + 1;
147 }
148 else {
149 $begin_seconds = $tmp_seconds - 1;
150 $next_seconds = $tmp_seconds;
151 }
152 }
153 return ( $begin_seconds, $next_seconds );
154 } # find_dst
155
156 my $year = -1;
157 my $cc = 0;
158 my $do_format = TRUE;
159 my $yr_arg_knt = 0;
160 my $mode = TWO_DATES_MODE;
161 my $now = 0;
162
163 our ( $opt_y, $opt_n, $opt_d, $opt_u, $opt_e );
164
165 if ( !getopts('y:ndue') ) {
166 $cc = 252;
167 usage();
168 exit($cc);
169 }
170 if ( defined($opt_y) ) {
171 $year = $opt_y - 1900;
172 ++$yr_arg_knt;
173 }
174 if ( defined($opt_n) ) {
175 $mode = NEXT_MODE;
176 ++$yr_arg_knt;
177 }
178 if ( defined($opt_d) ) {
179 $mode = DAYS_LEFT_MODE;
180 ++$yr_arg_knt;
181 }
182 if ( defined($opt_e) ) {
183 $do_format = FALSE;
184 }
185 if ( defined($opt_u) ) {
186 $cc = 251;
187 usage();
188 exit($cc);
189 }
190 if ( $yr_arg_knt > 1 && $cc == 0 ) {
191 $cc = 255;
192 problem( "Only one -n or -y argument allowed", $cc );
193 }
194 if ( $year <= 0 ) {
195 $now = time();
196 $year = ( localtime($now) )[5];
197 }
198 if ( $cc != 0 ) {
199 exit($cc);
200 }
201 $cc = 2;
202
203 if ( ( $mode == NEXT_MODE ) || ( $mode == DAYS_LEFT_MODE ) ) {
204 my $isdst_now = ( localtime($now) )[8];
205 my $seconds_Dec31 = timelocal( 59, 59, 23, 31, 11, $year + 1 );
206 my ( $start1, $start2 ) =
207 find_dst( $now, $seconds_Dec31, SECONDS_PER_DAY, $isdst_now );
208 if ( $start1 > 0 ) {
209 if ( $mode == NEXT_MODE ) {
210 if ($do_format) {
211 print strftime( "%a %b %d %H:%M:%S %Z %Y", localtime($start1) ),
212 " --> ",
213 strftime( "%a %b %d %H:%M:%S %Z %Y", localtime($start2) ),
214 "\n";
215 }
216 else {
217 print $start1, " --> ", $start2, "\n";
218 }
219 }
220 else {
221 if ($do_format) {
222 printf( "%.4f\n", ( ( $start2 - $now ) / SECONDS_PER_DAY ) );
223 }
224 else {
225 printf( "%d\n", int( ( $start2 - $now ) / SECONDS_PER_DAY ) );
226 }
227 }
228 $cc = 0;
229 }
230 }
231 else {
232 my $seconds_Jan1 = timelocal( 0, 0, 0, 1, 0, $year );
233 my $isdst_Jan1 = ( localtime($seconds_Jan1) )[8];
234 my $seconds_Dec31 = timelocal( 59, 59, 23, 31, 11, $year );
235
236 my ( $start1, $start2 ) =
237 find_dst( $seconds_Jan1, $seconds_Dec31, SECONDS_PER_DAY, $isdst_Jan1 );
238 if ( $start1 > 0 ) {
239 $cc = 1;
240 if ($do_format) {
241 print strftime( "%a %b %d %H:%M:%S %Z %Y", localtime($start1) ),
242 " --> ",
243 strftime( "%a %b %d %H:%M:%S %Z %Y", localtime($start2) ), "\n";
244 }
245 else {
246 print $start1, " --> ", $start2, "\n";
247 }
248 my ( $end1, $end2 ) =
249 find_dst( $start2, $seconds_Dec31, SECONDS_PER_DAY, !($isdst_Jan1) );
250 if ( $end1 > 0 ) {
251 if ($do_format) {
252 print strftime( "%a %b %d %H:%M:%S %Z %Y", localtime($end1) ),
253 " --> ",
254 strftime( "%a %b %d %H:%M:%S %Z %Y", localtime($end2) ), "\n";
255 }
256 else {
257 print $end1, " --> ", $end2, "\n";
258 }
259 $cc = 0;
260 }
261 }
262 }
263 exit($cc);
264