xref: /onnv-gate/usr/src/cmd/perl/5.8.4/distrib/lib/Time/Local.pm (revision 0:68f95e015346)
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