1*0Sstevel@tonic-gatepackage Time::Local; 2*0Sstevel@tonic-gate 3*0Sstevel@tonic-gaterequire Exporter; 4*0Sstevel@tonic-gateuse Carp; 5*0Sstevel@tonic-gateuse Config; 6*0Sstevel@tonic-gateuse strict; 7*0Sstevel@tonic-gateuse integer; 8*0Sstevel@tonic-gate 9*0Sstevel@tonic-gateuse vars qw( $VERSION @ISA @EXPORT @EXPORT_OK ); 10*0Sstevel@tonic-gate$VERSION = '1.07'; 11*0Sstevel@tonic-gate@ISA = qw( Exporter ); 12*0Sstevel@tonic-gate@EXPORT = qw( timegm timelocal ); 13*0Sstevel@tonic-gate@EXPORT_OK = qw( timegm_nocheck timelocal_nocheck ); 14*0Sstevel@tonic-gate 15*0Sstevel@tonic-gatemy @MonthDays = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); 16*0Sstevel@tonic-gate 17*0Sstevel@tonic-gate# Determine breakpoint for rolling century 18*0Sstevel@tonic-gatemy $ThisYear = (localtime())[5]; 19*0Sstevel@tonic-gatemy $Breakpoint = ($ThisYear + 50) % 100; 20*0Sstevel@tonic-gatemy $NextCentury = $ThisYear - $ThisYear % 100; 21*0Sstevel@tonic-gate $NextCentury += 100 if $Breakpoint < 50; 22*0Sstevel@tonic-gatemy $Century = $NextCentury - 100; 23*0Sstevel@tonic-gatemy $SecOff = 0; 24*0Sstevel@tonic-gate 25*0Sstevel@tonic-gatemy (%Options, %Cheat); 26*0Sstevel@tonic-gate 27*0Sstevel@tonic-gatemy $MaxInt = ((1<<(8 * $Config{intsize} - 2))-1)*2 + 1; 28*0Sstevel@tonic-gatemy $MaxDay = int(($MaxInt-43200)/86400)-1; 29*0Sstevel@tonic-gate 30*0Sstevel@tonic-gate# Determine the EPOC day for this machine 31*0Sstevel@tonic-gatemy $Epoc = 0; 32*0Sstevel@tonic-gateif ($^O eq 'vos') { 33*0Sstevel@tonic-gate# work around posix-977 -- VOS doesn't handle dates in 34*0Sstevel@tonic-gate# the range 1970-1980. 35*0Sstevel@tonic-gate $Epoc = _daygm((0, 0, 0, 1, 0, 70, 4, 0)); 36*0Sstevel@tonic-gate} 37*0Sstevel@tonic-gateelsif ($^O eq 'MacOS') { 38*0Sstevel@tonic-gate no integer; 39*0Sstevel@tonic-gate 40*0Sstevel@tonic-gate $MaxDay *=2 if $^O eq 'MacOS'; # time_t unsigned ... quick hack? 41*0Sstevel@tonic-gate # MacOS time() is seconds since 1 Jan 1904, localtime 42*0Sstevel@tonic-gate # so we need to calculate an offset to apply later 43*0Sstevel@tonic-gate $Epoc = 693901; 44*0Sstevel@tonic-gate $SecOff = timelocal(localtime(0)) - timelocal(gmtime(0)); 45*0Sstevel@tonic-gate $Epoc += _daygm(gmtime(0)); 46*0Sstevel@tonic-gate} 47*0Sstevel@tonic-gateelse { 48*0Sstevel@tonic-gate $Epoc = _daygm(gmtime(0)); 49*0Sstevel@tonic-gate} 50*0Sstevel@tonic-gate 51*0Sstevel@tonic-gate%Cheat=(); # clear the cache as epoc has changed 52*0Sstevel@tonic-gate 53*0Sstevel@tonic-gatesub _daygm { 54*0Sstevel@tonic-gate $_[3] + ($Cheat{pack("ss",@_[4,5])} ||= do { 55*0Sstevel@tonic-gate my $month = ($_[4] + 10) % 12; 56*0Sstevel@tonic-gate my $year = $_[5] + 1900 - $month/10; 57*0Sstevel@tonic-gate 365*$year + $year/4 - $year/100 + $year/400 + ($month*306 + 5)/10 - $Epoc 58*0Sstevel@tonic-gate }); 59*0Sstevel@tonic-gate} 60*0Sstevel@tonic-gate 61*0Sstevel@tonic-gate 62*0Sstevel@tonic-gatesub _timegm { 63*0Sstevel@tonic-gate my $sec = $SecOff + $_[0] + 60 * $_[1] + 3600 * $_[2]; 64*0Sstevel@tonic-gate 65*0Sstevel@tonic-gate no integer; 66*0Sstevel@tonic-gate 67*0Sstevel@tonic-gate $sec + 86400 * &_daygm; 68*0Sstevel@tonic-gate} 69*0Sstevel@tonic-gate 70*0Sstevel@tonic-gate 71*0Sstevel@tonic-gatesub timegm { 72*0Sstevel@tonic-gate my ($sec,$min,$hour,$mday,$month,$year) = @_; 73*0Sstevel@tonic-gate 74*0Sstevel@tonic-gate if ($year >= 1000) { 75*0Sstevel@tonic-gate $year -= 1900; 76*0Sstevel@tonic-gate } 77*0Sstevel@tonic-gate elsif ($year < 100 and $year >= 0) { 78*0Sstevel@tonic-gate $year += ($year > $Breakpoint) ? $Century : $NextCentury; 79*0Sstevel@tonic-gate } 80*0Sstevel@tonic-gate 81*0Sstevel@tonic-gate unless ($Options{no_range_check}) { 82*0Sstevel@tonic-gate if (abs($year) >= 0x7fff) { 83*0Sstevel@tonic-gate $year += 1900; 84*0Sstevel@tonic-gate croak "Cannot handle date ($sec, $min, $hour, $mday, $month, $year)"; 85*0Sstevel@tonic-gate } 86*0Sstevel@tonic-gate 87*0Sstevel@tonic-gate croak "Month '$month' out of range 0..11" if $month > 11 or $month < 0; 88*0Sstevel@tonic-gate 89*0Sstevel@tonic-gate my $md = $MonthDays[$month]; 90*0Sstevel@tonic-gate ++$md unless $month != 1 or $year % 4 or !($year % 400); 91*0Sstevel@tonic-gate 92*0Sstevel@tonic-gate croak "Day '$mday' out of range 1..$md" if $mday > $md or $mday < 1; 93*0Sstevel@tonic-gate croak "Hour '$hour' out of range 0..23" if $hour > 23 or $hour < 0; 94*0Sstevel@tonic-gate croak "Minute '$min' out of range 0..59" if $min > 59 or $min < 0; 95*0Sstevel@tonic-gate croak "Second '$sec' out of range 0..59" if $sec > 59 or $sec < 0; 96*0Sstevel@tonic-gate } 97*0Sstevel@tonic-gate 98*0Sstevel@tonic-gate my $days = _daygm(undef, undef, undef, $mday, $month, $year); 99*0Sstevel@tonic-gate 100*0Sstevel@tonic-gate unless ($Options{no_range_check} or abs($days) < $MaxDay) { 101*0Sstevel@tonic-gate $year += 1900; 102*0Sstevel@tonic-gate croak "Cannot handle date ($sec, $min, $hour, $mday, $month, $year)"; 103*0Sstevel@tonic-gate } 104*0Sstevel@tonic-gate 105*0Sstevel@tonic-gate $sec += $SecOff + 60*$min + 3600*$hour; 106*0Sstevel@tonic-gate 107*0Sstevel@tonic-gate no integer; 108*0Sstevel@tonic-gate 109*0Sstevel@tonic-gate $sec + 86400*$days; 110*0Sstevel@tonic-gate} 111*0Sstevel@tonic-gate 112*0Sstevel@tonic-gate 113*0Sstevel@tonic-gatesub timegm_nocheck { 114*0Sstevel@tonic-gate local $Options{no_range_check} = 1; 115*0Sstevel@tonic-gate &timegm; 116*0Sstevel@tonic-gate} 117*0Sstevel@tonic-gate 118*0Sstevel@tonic-gate 119*0Sstevel@tonic-gatesub timelocal { 120*0Sstevel@tonic-gate no integer; 121*0Sstevel@tonic-gate my $ref_t = &timegm; 122*0Sstevel@tonic-gate my $loc_t = _timegm(localtime($ref_t)); 123*0Sstevel@tonic-gate 124*0Sstevel@tonic-gate # Is there a timezone offset from GMT or are we done 125*0Sstevel@tonic-gate my $zone_off = $ref_t - $loc_t 126*0Sstevel@tonic-gate or return $loc_t; 127*0Sstevel@tonic-gate 128*0Sstevel@tonic-gate # Adjust for timezone 129*0Sstevel@tonic-gate $loc_t = $ref_t + $zone_off; 130*0Sstevel@tonic-gate 131*0Sstevel@tonic-gate # Are we close to a DST change or are we done 132*0Sstevel@tonic-gate my $dst_off = $ref_t - _timegm(localtime($loc_t)) 133*0Sstevel@tonic-gate or return $loc_t; 134*0Sstevel@tonic-gate 135*0Sstevel@tonic-gate # Adjust for DST change 136*0Sstevel@tonic-gate $loc_t += $dst_off; 137*0Sstevel@tonic-gate 138*0Sstevel@tonic-gate # for a negative offset from GMT, and if the original date 139*0Sstevel@tonic-gate # was a non-extent gap in a forward DST jump, we should 140*0Sstevel@tonic-gate # now have the wrong answer - undo the DST adjust; 141*0Sstevel@tonic-gate 142*0Sstevel@tonic-gate return $loc_t if $zone_off <= 0; 143*0Sstevel@tonic-gate 144*0Sstevel@tonic-gate my ($s,$m,$h) = localtime($loc_t); 145*0Sstevel@tonic-gate $loc_t -= $dst_off if $s != $_[0] || $m != $_[1] || $h != $_[2]; 146*0Sstevel@tonic-gate 147*0Sstevel@tonic-gate $loc_t; 148*0Sstevel@tonic-gate} 149*0Sstevel@tonic-gate 150*0Sstevel@tonic-gate 151*0Sstevel@tonic-gatesub timelocal_nocheck { 152*0Sstevel@tonic-gate local $Options{no_range_check} = 1; 153*0Sstevel@tonic-gate &timelocal; 154*0Sstevel@tonic-gate} 155*0Sstevel@tonic-gate 156*0Sstevel@tonic-gate1; 157*0Sstevel@tonic-gate 158*0Sstevel@tonic-gate__END__ 159*0Sstevel@tonic-gate 160*0Sstevel@tonic-gate=head1 NAME 161*0Sstevel@tonic-gate 162*0Sstevel@tonic-gateTime::Local - efficiently compute time from local and GMT time 163*0Sstevel@tonic-gate 164*0Sstevel@tonic-gate=head1 SYNOPSIS 165*0Sstevel@tonic-gate 166*0Sstevel@tonic-gate $time = timelocal($sec,$min,$hour,$mday,$mon,$year); 167*0Sstevel@tonic-gate $time = timegm($sec,$min,$hour,$mday,$mon,$year); 168*0Sstevel@tonic-gate 169*0Sstevel@tonic-gate=head1 DESCRIPTION 170*0Sstevel@tonic-gate 171*0Sstevel@tonic-gateThese routines are the inverse of built-in perl functions localtime() 172*0Sstevel@tonic-gateand gmtime(). They accept a date as a six-element array, and return 173*0Sstevel@tonic-gatethe corresponding time(2) value in seconds since the system epoch 174*0Sstevel@tonic-gate(Midnight, January 1, 1970 UTC on Unix, for example). This value can 175*0Sstevel@tonic-gatebe positive or negative, though POSIX only requires support for 176*0Sstevel@tonic-gatepositive values, so dates before the system's epoch may not work on 177*0Sstevel@tonic-gateall operating systems. 178*0Sstevel@tonic-gate 179*0Sstevel@tonic-gateIt is worth drawing particular attention to the expected ranges for 180*0Sstevel@tonic-gatethe values provided. The value for the day of the month is the actual day 181*0Sstevel@tonic-gate(ie 1..31), while the month is the number of months since January (0..11). 182*0Sstevel@tonic-gateThis is consistent with the values returned from localtime() and gmtime(). 183*0Sstevel@tonic-gate 184*0Sstevel@tonic-gateThe timelocal() and timegm() functions perform range checking on the 185*0Sstevel@tonic-gateinput $sec, $min, $hour, $mday, and $mon values by default. If you'd 186*0Sstevel@tonic-gaterather they didn't, you can explicitly import the timelocal_nocheck() 187*0Sstevel@tonic-gateand timegm_nocheck() functions. 188*0Sstevel@tonic-gate 189*0Sstevel@tonic-gate use Time::Local 'timelocal_nocheck'; 190*0Sstevel@tonic-gate 191*0Sstevel@tonic-gate { 192*0Sstevel@tonic-gate # The 365th day of 1999 193*0Sstevel@tonic-gate print scalar localtime timelocal_nocheck 0,0,0,365,0,99; 194*0Sstevel@tonic-gate 195*0Sstevel@tonic-gate # The twenty thousandth day since 1970 196*0Sstevel@tonic-gate print scalar localtime timelocal_nocheck 0,0,0,20000,0,70; 197*0Sstevel@tonic-gate 198*0Sstevel@tonic-gate # And even the 10,000,000th second since 1999! 199*0Sstevel@tonic-gate print scalar localtime timelocal_nocheck 10000000,0,0,1,0,99; 200*0Sstevel@tonic-gate } 201*0Sstevel@tonic-gate 202*0Sstevel@tonic-gateYour mileage may vary when trying these with minutes and hours, 203*0Sstevel@tonic-gateand it doesn't work at all for months. 204*0Sstevel@tonic-gate 205*0Sstevel@tonic-gateStrictly speaking, the year should also be specified in a form consistent 206*0Sstevel@tonic-gatewith localtime(), i.e. the offset from 1900. 207*0Sstevel@tonic-gateIn order to make the interpretation of the year easier for humans, 208*0Sstevel@tonic-gatehowever, who are more accustomed to seeing years as two-digit or four-digit 209*0Sstevel@tonic-gatevalues, the following conventions are followed: 210*0Sstevel@tonic-gate 211*0Sstevel@tonic-gate=over 4 212*0Sstevel@tonic-gate 213*0Sstevel@tonic-gate=item * 214*0Sstevel@tonic-gate 215*0Sstevel@tonic-gateYears greater than 999 are interpreted as being the actual year, 216*0Sstevel@tonic-gaterather than the offset from 1900. Thus, 1963 would indicate the year 217*0Sstevel@tonic-gateMartin Luther King won the Nobel prize, not the year 2863. 218*0Sstevel@tonic-gate 219*0Sstevel@tonic-gate=item * 220*0Sstevel@tonic-gate 221*0Sstevel@tonic-gateYears in the range 100..999 are interpreted as offset from 1900, 222*0Sstevel@tonic-gateso that 112 indicates 2012. This rule also applies to years less than zero 223*0Sstevel@tonic-gate(but see note below regarding date range). 224*0Sstevel@tonic-gate 225*0Sstevel@tonic-gate=item * 226*0Sstevel@tonic-gate 227*0Sstevel@tonic-gateYears in the range 0..99 are interpreted as shorthand for years in the 228*0Sstevel@tonic-gaterolling "current century," defined as 50 years on either side of the current 229*0Sstevel@tonic-gateyear. Thus, today, in 1999, 0 would refer to 2000, and 45 to 2045, 230*0Sstevel@tonic-gatebut 55 would refer to 1955. Twenty years from now, 55 would instead refer 231*0Sstevel@tonic-gateto 2055. This is messy, but matches the way people currently think about 232*0Sstevel@tonic-gatetwo digit dates. Whenever possible, use an absolute four digit year instead. 233*0Sstevel@tonic-gate 234*0Sstevel@tonic-gate=back 235*0Sstevel@tonic-gate 236*0Sstevel@tonic-gateThe scheme above allows interpretation of a wide range of dates, particularly 237*0Sstevel@tonic-gateif 4-digit years are used. 238*0Sstevel@tonic-gate 239*0Sstevel@tonic-gatePlease note, however, that the range of dates that can be actually be handled 240*0Sstevel@tonic-gatedepends on the size of an integer (time_t) on a given platform. 241*0Sstevel@tonic-gateCurrently, this is 32 bits for most systems, yielding an approximate range 242*0Sstevel@tonic-gatefrom Dec 1901 to Jan 2038. 243*0Sstevel@tonic-gate 244*0Sstevel@tonic-gateBoth timelocal() and timegm() croak if given dates outside the supported 245*0Sstevel@tonic-gaterange. 246*0Sstevel@tonic-gate 247*0Sstevel@tonic-gate=head1 IMPLEMENTATION 248*0Sstevel@tonic-gate 249*0Sstevel@tonic-gateThese routines are quite efficient and yet are always guaranteed to agree 250*0Sstevel@tonic-gatewith localtime() and gmtime(). We manage this by caching the start times 251*0Sstevel@tonic-gateof any months we've seen before. If we know the start time of the month, 252*0Sstevel@tonic-gatewe can always calculate any time within the month. The start times 253*0Sstevel@tonic-gateare calculated using a mathematical formula. Unlike other algorithms 254*0Sstevel@tonic-gatethat do multiple calls to gmtime(). 255*0Sstevel@tonic-gate 256*0Sstevel@tonic-gatetimelocal() is implemented using the same cache. We just assume that we're 257*0Sstevel@tonic-gatetranslating a GMT time, and then fudge it when we're done for the timezone 258*0Sstevel@tonic-gateand daylight savings arguments. Note that the timezone is evaluated for 259*0Sstevel@tonic-gateeach date because countries occasionally change their official timezones. 260*0Sstevel@tonic-gateAssuming that localtime() corrects for these changes, this routine will 261*0Sstevel@tonic-gatealso be correct. 262*0Sstevel@tonic-gate 263*0Sstevel@tonic-gate=head1 BUGS 264*0Sstevel@tonic-gate 265*0Sstevel@tonic-gateThe whole scheme for interpreting two-digit years can be considered a bug. 266*0Sstevel@tonic-gate 267*0Sstevel@tonic-gateThe proclivity to croak() is probably a bug. 268*0Sstevel@tonic-gate 269*0Sstevel@tonic-gate=head1 SUPPORT 270*0Sstevel@tonic-gate 271*0Sstevel@tonic-gateSupport for this module is provided via the perl5-porters@perl.org 272*0Sstevel@tonic-gateemail list. See http://lists.perl.org/ for more details. 273*0Sstevel@tonic-gate 274*0Sstevel@tonic-gatePlease submit bugs using the RT system at bugs.perl.org, the perlbug 275*0Sstevel@tonic-gatescript, or as a last resort, to the perl5-porters@perl.org list. 276*0Sstevel@tonic-gate 277*0Sstevel@tonic-gate=head1 AUTHOR 278*0Sstevel@tonic-gate 279*0Sstevel@tonic-gateThis module is based on a Perl 4 library, timelocal.pl, that was 280*0Sstevel@tonic-gateincluded with Perl 4.036, and was most likely written by Tom 281*0Sstevel@tonic-gateChristiansen. 282*0Sstevel@tonic-gate 283*0Sstevel@tonic-gateThe current version was written by Graham Barr. 284*0Sstevel@tonic-gate 285*0Sstevel@tonic-gateIt is now being maintained separately from the Perl core by Dave 286*0Sstevel@tonic-gateRolsky, <autarch@urth.org>. 287*0Sstevel@tonic-gate 288*0Sstevel@tonic-gate=cut 289*0Sstevel@tonic-gate 290