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