1#!./perl 2 3use strict; 4use warnings; 5 6use Config; 7use Test::More 0.96; 8use Time::Local 9 qw( timegm timelocal timegm_modern timelocal_modern timegm_nocheck timelocal_nocheck ); 10 11# Use 3 days before the start of the epoch because with Borland on 12# Win32 it will work for -3600 _if_ your time zone is +01:00 (or 13# greater). 14my $neg_epoch_ok 15 = $^O eq 'VMS' ? 0 : defined( ( localtime(-259200) )[0] ) ? 1 : 0; 16 17my $large_epoch_ok = eval { ( gmtime 2**40 )[5] == 34912 }; 18 19{ 20 my %tests = _valid_time_tests(); 21 for my $group ( sort keys %tests ) { 22 subtest( 23 $group, 24 sub { _test_group( $tests{$group} ) }, 25 ); 26 } 27} 28 29sub _valid_time_tests { 30 my %tests = ( 31 'simple times' => [ 32 [ 1970, 1, 2, 0, 0, 0 ], 33 [ 1980, 2, 28, 12, 0, 0 ], 34 [ 1980, 2, 29, 12, 0, 0 ], 35 [ 1999, 12, 31, 23, 59, 59 ], 36 [ 2000, 1, 1, 0, 0, 0 ], 37 [ 2010, 10, 12, 14, 13, 12 ], 38 ], 39 'leap days' => [ 40 [ 2020, 2, 29, 12, 59, 59 ], 41 [ 2030, 7, 4, 17, 7, 6 ], 42 ], 43 'non-integer seconds' => [ 44 [ 2010, 10, 12, 14, 13, 12.1 ], 45 [ 2010, 10, 12, 14, 13, 59.1 ], 46 ], 47 ); 48 49 # The following test fails on a surprising number of systems 50 # so it is commented out. The end of the Epoch for a 32-bit signed 51 # implementation of time_t should be Jan 19, 2038 03:14:07 UTC. 52 # [2038, 1, 17, 23, 59, 59], # last full day in any tz 53 54 # more than 2**31 time_t - requires a 64bit safe localtime/gmtime 55 $tests{'greater than 2**31 seconds'} = [ [ 2258, 8, 11, 1, 49, 17 ] ] 56 if $] >= 5.012000; 57 58 # use vmsish 'time' makes for oddness around the Unix epoch 59 $tests{'simple times'}[0][2]++ 60 if $^O eq 'VMS'; 61 62 $tests{'negative epoch'} = [ 63 [ 1969, 12, 31, 16, 59, 59 ], 64 [ 1950, 4, 12, 9, 30, 31 ], 65 ] if $neg_epoch_ok; 66 67 return %tests; 68} 69 70sub _test_group { 71 my $group = shift; 72 73 for my $vals ( @{$group} ) { 74 my ( $year, $mon, $mday, $hour, $min, $sec ) = @{$vals}; 75 $mon--; 76 77 # 1970 test on VOS fails 78 next if $^O eq 'vos' && $year == 1970; 79 80 for my $sub (qw( timelocal timelocal_nocheck timelocal_modern )) { 81 subtest( 82 $sub, 83 sub { 84 my $time = __PACKAGE__->can($sub) 85 ->( $sec, $min, $hour, $mday, $mon, $year ); 86 87 is_deeply( 88 [ ( localtime($time) )[ 0 .. 5 ] ], 89 [ int($sec), $min, $hour, $mday, $mon, $year - 1900 ], 90 "timelocal for @{$vals}" 91 ); 92 }, 93 ); 94 } 95 96 for my $sub (qw( timegm timegm_nocheck timegm_modern )) { 97 subtest( 98 $sub, 99 sub { 100 my $time = __PACKAGE__->can($sub) 101 ->( $sec, $min, $hour, $mday, $mon, $year ); 102 103 is_deeply( 104 [ ( gmtime($time) )[ 0 .. 5 ] ], 105 [ int($sec), $min, $hour, $mday, $mon, $year - 1900 ], 106 "timegm for @{$vals}" 107 ); 108 }, 109 ); 110 } 111 } 112} 113 114subtest( 115 'bad times', 116 sub { 117 my %bad = ( 118 'month too large' => [ 1995, 13, 1, 1, 1, 1 ], 119 'day too large' => [ 1995, 2, 30, 1, 1, 1 ], 120 'hour too large' => [ 1995, 2, 10, 25, 1, 1 ], 121 'minute too large' => [ 1995, 2, 10, 1, 60, 1 ], 122 'second too large' => [ 1995, 2, 10, 1, 1, 60 ], 123 ); 124 125 for my $key ( sort keys %bad ) { 126 subtest( 127 $key, 128 sub { 129 my ( $year, $mon, $mday, $hour, $min, $sec ) 130 = @{ $bad{$key} }; 131 $mon--; 132 133 local $@ = undef; 134 eval { timegm( $sec, $min, $hour, $mday, $mon, $year ) }; 135 136 like( 137 $@, qr/.*out of range.*/, 138 "invalid time caused an error - @{$bad{$key}}" 139 ); 140 } 141 ); 142 } 143 }, 144); 145 146subtest( 147 'diff between two calls', 148 sub { 149 is( 150 timelocal( 0, 0, 1, 1, 0, 90 ) - timelocal( 0, 0, 0, 1, 0, 90 ), 151 3600, 152 'one hour difference between two calls to timelocal' 153 ); 154 155 is( 156 timelocal( 1, 2, 3, 1, 0, 100 ) 157 - timelocal( 1, 2, 3, 31, 11, 99 ), 158 24 * 3600, 159 'one day difference between two calls to timelocal' 160 ); 161 162 # Diff beween Jan 1, 1980 and Mar 1, 1980 = (31 + 29 = 60 days) 163 is( 164 timegm( 0, 0, 0, 1, 2, 80 ) - timegm( 0, 0, 0, 1, 0, 80 ), 165 60 * 24 * 3600, 166 '60 day difference between two calls to timegm' 167 ); 168 }, 169); 170 171subtest( 172 'DST transition bug - https://rt.perl.org/Ticket/Display.html?id=19393', 173 sub { 174 # At a DST transition, the clock skips forward, eg from 01:59:59 to 175 # 03:00:00. In this case, 02:00:00 is an invalid time, and should be 176 # treated like 03:00:00 rather than 01:00:00 - negative zone offsets 177 # used to do the latter. 178 { 179 my $hour = ( localtime( timelocal( 0, 0, 2, 7, 3, 102 ) ) )[2]; 180 181 # testers in US/Pacific should get 3, 182 # other testers should get 2 183 ok( $hour == 2 || $hour == 3, 'hour should be 2 or 3' ); 184 } 185 }, 186); 187 188subtest( 189 'Time::Local::_is_leap_year', 190 sub { 191 my @years = ( 192 [ 1900 => 0 ], 193 [ 1947 => 0 ], 194 [ 1996 => 1 ], 195 [ 2000 => 1 ], 196 [ 2100 => 0 ], 197 ); 198 199 for my $p (@years) { 200 my ( $year, $is_leap_year ) = @$p; 201 202 my $string = $is_leap_year ? 'is' : 'is not'; 203 ## no critic (Subroutines::ProtectPrivateSubs) 204 is( 205 Time::Local::_is_leap_year($year), $is_leap_year, 206 "$year $string a leap year" 207 ); 208 } 209 } 210); 211 212subtest( 213 'negative epochs', 214 sub { 215 plan skip_all => 'this platform does not support negative epochs.' 216 unless $neg_epoch_ok; 217 218 local $@ = undef; 219 eval { timegm( 0, 0, 0, 29, 1, 1900 ) }; 220 like( 221 $@, qr/Day '29' out of range 1\.\.28/, 222 'does not accept leap day in 1900' 223 ); 224 225 local $@ = undef; 226 eval { timegm( 0, 0, 0, 29, 1, 200 ) }; 227 like( 228 $@, qr/Day '29' out of range 1\.\.28/, 229 'does not accept leap day in 2100 (year passed as 200)' 230 ); 231 232 local $@ = undef; 233 eval { timegm( 0, 0, 0, 29, 1, 0 ) }; 234 is( 235 $@, q{}, 236 'no error with leap day of 2000 (year passed as 0)' 237 ); 238 239 local $@ = undef; 240 eval { timegm( 0, 0, 0, 29, 1, 1904 ) }; 241 is( $@, q{}, 'no error with leap day of 1904' ); 242 243 local $@ = undef; 244 eval { timegm( 0, 0, 0, 29, 1, 4 ) }; 245 is( 246 $@, q{}, 247 'no error with leap day of 2004 (year passed as 4)' 248 ); 249 250 local $@ = undef; 251 eval { timegm( 0, 0, 0, 29, 1, 96 ) }; 252 is( 253 $@, q{}, 254 'no error with leap day of 1996 (year passed as 96)' 255 ); 256 }, 257); 258 259subtest( 260 'Large epoch values', 261 sub { 262 plan skip_all => 'These tests require support for large epoch values' 263 unless $large_epoch_ok; 264 265 is( 266 timegm( 8, 14, 3, 19, 0, 2038 ), 2**31, 267 'can call timegm for 2**31 epoch seconds' 268 ); 269 is( 270 timegm( 16, 28, 6, 7, 1, 2106 ), 2**32, 271 'can call timegm for 2**32 epoch seconds (on a 64-bit system)' 272 ); 273 is( 274 timegm( 16, 36, 0, 20, 1, 36812 ), 2**40, 275 'can call timegm for 2**40 epoch seconds (on a 64-bit system)' 276 ); 277 }, 278); 279 280subtest( 281 '2-digit years', 282 sub { 283 my $current_year = ( localtime() )[5]; 284 my $pre_break = ( $current_year + 49 ) - 100; 285 my $break = ( $current_year + 50 ) - 100; 286 my $post_break = ( $current_year + 51 ) - 100; 287 288 subtest( 289 'legacy year munging', 290 sub { 291 plan skip_all => 'Requires support for an large epoch values' 292 unless $large_epoch_ok; 293 294 is( 295 ( 296 ( 297 localtime( 298 timelocal( 0, 0, 0, 1, 1, $pre_break ) 299 ) 300 )[5] 301 ), 302 $pre_break + 100, 303 "year $pre_break is treated as next century", 304 ); 305 is( 306 ( 307 ( localtime( timelocal( 0, 0, 0, 1, 1, $break ) ) )[5] 308 ), 309 $break + 100, 310 "year $break is treated as next century", 311 ); 312 is( 313 ( 314 ( 315 localtime( 316 timelocal( 0, 0, 0, 1, 1, $post_break ) 317 ) 318 )[5] 319 ), 320 $post_break, 321 "year $post_break is treated as current century", 322 ); 323 } 324 ); 325 326 subtest( 327 'modern', 328 sub { 329 plan skip_all => 330 'Requires negative epoch support and large epoch support' 331 unless $neg_epoch_ok && $large_epoch_ok; 332 333 is( 334 ( 335 ( 336 localtime( 337 timelocal_modern( 0, 0, 0, 1, 1, $pre_break ) 338 ) 339 )[5] 340 ) + 1900, 341 $pre_break, 342 "year $pre_break is treated as year $pre_break", 343 ); 344 is( 345 ( 346 ( 347 localtime( 348 timelocal_modern( 0, 0, 0, 1, 1, $break ) 349 ) 350 )[5] 351 ) + 1900, 352 $break, 353 "year $break is treated as year $break", 354 ); 355 is( 356 ( 357 ( 358 localtime( 359 timelocal_modern( 360 0, 0, 0, 1, 1, $post_break 361 ) 362 ) 363 )[5] 364 ) + 1900, 365 $post_break, 366 "year $post_break is treated as year $post_break", 367 ); 368 }, 369 ); 370 }, 371); 372 373done_testing(); 374