1#!/usr/bin/perl -w 2 3use strict; 4 5use Test::More tests => 127; 6 7use DateTime; 8 9# These tests should be the final word on dt subtraction involving a 10# DST-changing time zone 11 12{ 13 my $dt1 = DateTime->new( year => 2003, month => 5, day => 6, 14 time_zone => 'America/Chicago', 15 ); 16 17 my $dt2 = DateTime->new( year => 2003, month => 11, day => 6, 18 time_zone => 'America/Chicago', 19 ); 20 21 my $dur1 = $dt2->subtract_datetime($dt1); 22 my %deltas1 = $dur1->deltas; 23 is( $deltas1{months}, 6, 'delta_months is 6' ); 24 is( $deltas1{days}, 0, 'delta_days is 0' ); 25 is( $deltas1{minutes}, 0, 'delta_minutes is 0' ); 26 is( $deltas1{seconds}, 0, 'delta_seconds is 0' ); 27 28 is( DateTime->compare( $dt1->clone->add_duration($dur1), $dt2 ), 0, 29 'subtract_datetime is reversible from start point' ); 30 is( DateTime->compare( $dt2->clone->subtract_duration($dur1), $dt1 ), 0, 31 'subtract_datetime is reversible from end point' ); 32 is( $deltas1{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 33 34 my $dur2 = $dt1->subtract_datetime($dt2); 35 my %deltas2 = $dur2->deltas; 36 is( $deltas2{months}, -6, 'delta_months is -6' ); 37 is( $deltas2{days}, 0, 'delta_days is 0' ); 38 is( $deltas2{minutes}, 0, 'delta_minutes is 0' ); 39 is( $deltas2{seconds}, 0, 'delta_seconds is 0' ); 40 is( $deltas2{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 41 42 my $dur3 = $dt2->delta_md($dt1); 43 my %deltas3 = $dur3->deltas; 44 is( $deltas3{months}, 6, 'delta_months is 6' ); 45 is( $deltas3{days}, 0, 'delta_days is 0' ); 46 is( $deltas3{minutes}, 0, 'delta_minutes is 0' ); 47 is( $deltas3{seconds}, 0, 'delta_seconds is 0' ); 48 is( $deltas3{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 49 50 is( DateTime->compare( $dt1->clone->add_duration($dur3), $dt2 ), 0, 51 'delta_md is reversible from start point' ); 52 is( DateTime->compare( $dt2->clone->subtract_duration($dur3), $dt1 ), 0, 53 'delta_md is reversible from end point' ); 54 55 my $dur4 = $dt2->delta_days($dt1); 56 my %deltas4 = $dur4->deltas; 57 is( $deltas4{months}, 0, 'delta_months is 0' ); 58 is( $deltas4{days}, 184, 'delta_days is 184' ); 59 is( $deltas4{minutes}, 0, 'delta_minutes is 0' ); 60 is( $deltas4{seconds}, 0, 'delta_seconds is 0' ); 61 is( $deltas4{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 62 63 is( DateTime->compare( $dt1->clone->add_duration($dur3), $dt2 ), 0, 64 'delta_days is reversible from start point' ); 65 is( DateTime->compare( $dt2->clone->subtract_duration($dur4), $dt1 ), 0, 66 'delta_days is reversible from end point' ); 67} 68 69# same as above, but now the UTC hour of the earlier datetime is 70# _greater_ than that of the later one. this checks that overflows 71# are handled correctly. 72{ 73 my $dt1 = DateTime->new( year => 2003, month => 5, day => 6, hour => 18, 74 time_zone => 'America/Chicago', 75 ); 76 77 my $dt2 = DateTime->new( year => 2003, month => 11, day => 6, hour => 18, 78 time_zone => 'America/Chicago', 79 ); 80 81 my $dur1 = $dt2->subtract_datetime($dt1); 82 my %deltas1 = $dur1->deltas; 83 is( $deltas1{months}, 6, 'delta_months is 6' ); 84 is( $deltas1{days}, 0, 'delta_days is 0' ); 85 is( $deltas1{minutes}, 0, 'delta_minutes is 0' ); 86 is( $deltas1{seconds}, 0, 'delta_seconds is 0' ); 87 is( $deltas1{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 88} 89 90# make sure delta_md and delta_days work in the face of DST change 91# where we lose an hour 92{ 93 my $dt1 = DateTime->new( year => 2003, month => 11, day => 6, 94 time_zone => 'America/Chicago', 95 ); 96 97 my $dt2 = DateTime->new( year => 2004, month => 5, day => 6, 98 time_zone => 'America/Chicago', 99 ); 100 101 my $dur1 = $dt2->delta_md($dt1); 102 my %deltas1 = $dur1->deltas; 103 is( $deltas1{months}, 6, 'delta_months is 6' ); 104 is( $deltas1{days}, 0, 'delta_days is 0' ); 105 is( $deltas1{minutes}, 0, 'delta_minutes is 0' ); 106 is( $deltas1{seconds}, 0, 'delta_seconds is 0' ); 107 is( $deltas1{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 108 109 my $dur2 = $dt2->delta_days($dt1); 110 my %deltas2 = $dur2->deltas; 111 is( $deltas2{months}, 0, 'delta_months is 0' ); 112 is( $deltas2{days}, 182, 'delta_days is 182' ); 113 is( $deltas2{minutes}, 0, 'delta_minutes is 0' ); 114 is( $deltas2{seconds}, 0, 'delta_seconds is 0' ); 115 is( $deltas2{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 116 117} 118 119# the docs say use UTC to guarantee reversibility 120{ 121 my $dt1 = DateTime->new( year => 2003, month => 5, day => 6, 122 time_zone => 'America/Chicago', 123 ); 124 125 my $dt2 = DateTime->new( year => 2003, month => 11, day => 6, 126 time_zone => 'America/Chicago', 127 ); 128 129 $dt1->set_time_zone('UTC'); 130 $dt2->set_time_zone('UTC'); 131 132 my $dur = $dt2->subtract_datetime($dt1); 133 134 is( DateTime->compare( $dt1->add_duration($dur), $dt2 ), 0, 135 'subtraction is reversible from start point with UTC' ); 136 is( DateTime->compare( $dt2->subtract_duration($dur), $dt2 ), 0, 137 'subtraction is reversible from start point with UTC' ); 138} 139 140# The important thing here is that after a subtraction, we can use the 141# duration to get from one date to the other, regardless of the type 142# of subtraction done. 143{ 144 my $dt1 = DateTime->new( year => 2003, month => 5, day => 6, 145 time_zone => 'America/Chicago', 146 ); 147 148 my $dt2 = DateTime->new( year => 2003, month => 11, day => 6, 149 time_zone => 'America/Chicago', 150 ); 151 152 my $dur1 = $dt2->subtract_datetime_absolute($dt1); 153 154 my %deltas1 = $dur1->deltas; 155 is( $deltas1{months}, 0, 'delta_months is 0' ); 156 is( $deltas1{days}, 0, 'delta_days is 0' ); 157 is( $deltas1{minutes}, 0, 'delta_minutes is 0' ); 158 is( $deltas1{seconds}, 15901200, 'delta_seconds is 15901200' ); 159 is( $deltas1{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 160 161 is( DateTime->compare( $dt1->clone->add_duration($dur1), $dt2 ), 0, 162 'subtraction is reversible' ); 163 is( DateTime->compare( $dt2->clone->subtract_duration($dur1), $dt1 ), 0, 164 'subtraction is doubly reversible' ); 165 166 my $dur2 = $dt1->subtract_datetime_absolute($dt2); 167 168 my %deltas2 = $dur2->deltas; 169 is( $deltas2{months}, 0, 'delta_months is 0' ); 170 is( $deltas2{days}, 0, 'delta_days is 0' ); 171 is( $deltas2{minutes}, 0, 'delta_minutes is 0' ); 172 is( $deltas2{seconds}, -15901200, 'delta_seconds is -15901200' ); 173 is( $deltas2{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 174 175 is( DateTime->compare( $dt2->clone->add_duration($dur2), $dt1 ), 0, 176 'subtraction is reversible' ); 177 is( DateTime->compare( $dt1->clone->subtract_duration($dur2), $dt2 ), 0, 178 'subtraction is doubly reversible' ); 179} 180 181{ 182 my $dt1 = DateTime->new( year => 2003, month => 4, day => 6, 183 hour => 1, minute => 58, 184 time_zone => 'America/Chicago', 185 ); 186 187 my $dt2 = DateTime->new( year => 2003, month => 4, day => 6, 188 hour => 3, minute => 1, 189 time_zone => 'America/Chicago', 190 ); 191 192 my $dur = $dt2->subtract_datetime($dt1); 193 194 my %deltas = $dur->deltas; 195 is( $deltas{months}, 0, 'delta_months is 0' ); 196 is( $deltas{days}, 0, 'delta_days is 0' ); 197 is( $deltas{minutes}, 3, 'delta_minutes is 3' ); 198 is( $deltas{seconds}, 0, 'delta_seconds is 0' ); 199 is( $deltas{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 200 201 is( DateTime->compare( $dt1->clone->add_duration($dur), $dt2 ), 0, 202 'subtraction is reversible' ); 203 is( DateTime->compare( $dt2->clone->subtract_duration($dur), $dt1), 0, 204 'subtraction is doubly reversible' ); 205} 206 207{ 208 my $dt1 = DateTime->new( year => 2003, month => 4, day => 5, 209 hour => 1, minute => 58, 210 time_zone => 'America/Chicago', 211 ); 212 213 my $dt2 = DateTime->new( year => 2003, month => 4, day => 6, 214 hour => 3, minute => 1, 215 time_zone => 'America/Chicago', 216 ); 217 218 my $dur = $dt2->subtract_datetime($dt1); 219 220 my %deltas = $dur->deltas; 221 is( $deltas{months}, 0, 'delta_months is 0' ); 222 is( $deltas{days}, 1, 'delta_days is 1' ); 223 is( $deltas{minutes}, 3, 'delta_minutes is 3' ); 224 is( $deltas{seconds}, 0, 'delta_seconds is 0' ); 225 is( $deltas{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 226 227 is( DateTime->compare( $dt1->clone->add_duration($dur), $dt2 ), 0, 228 'dt1 + dur = dt2' ); 229 # this are two examples from the docs 230 is( DateTime->compare( $dt2->clone->subtract_duration($dur), 231 $dt1->clone->add( hours => 1 ) ), 232 0, 233 'dt2 - dur != dt1 (not reversible)' ); 234 is( DateTime->compare( $dt2->clone->subtract_duration( $dur->clock_duration ) 235 ->subtract_duration( $dur->calendar_duration ), 236 $dt1 ), 237 0, 238 'dt2 - dur->clock - dur->cal = dt1 (reversible when componentized)' ); 239 240 my $dur2 = $dt1->subtract_datetime($dt2); 241 my %deltas2 = $dur2->deltas; 242 is( $deltas2{months}, 0, 'delta_months is 0' ); 243 is( $deltas2{days}, -1, 'delta_days is 1' ); 244 is( $deltas2{minutes}, -3, 'delta_minutes is 3' ); 245 is( $deltas2{seconds}, 0, 'delta_seconds is 0' ); 246 is( $deltas2{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 247 is( $dt2->clone->add_duration($dur2)->datetime, '2003-04-05T02:58:00', 'dt2 + dur2 != dt1' ); 248 is( DateTime->compare( $dt2->clone->add_duration( $dur2->clock_duration ) 249 ->add_duration( $dur2->calendar_duration ), 250 $dt1 ), 251 0, 252 'dt2 + dur2->clock + dur2->cal = dt1' ); 253 is( DateTime->compare( $dt1->clone->subtract_duration($dur2), $dt2 ), 0, 254 'dt1 - dur2 = dt2' ); 255 256} 257 258# These tests makes sure that days with DST changes are "normal" when 259# they're the smaller operand 260{ 261 my $dt1 = DateTime->new( year => 2003, month => 4, day => 6, 262 hour => 3, minute => 1, 263 time_zone => 'America/Chicago', 264 ); 265 266 my $dt2 = DateTime->new( year => 2003, month => 4, day => 7, 267 hour => 3, minute => 2, 268 time_zone => 'America/Chicago', 269 ); 270 271 my $dur = $dt2->subtract_datetime($dt1); 272 273 my %deltas = $dur->deltas; 274 is( $deltas{months}, 0, 'delta_months is 0' ); 275 is( $deltas{days}, 1, 'delta_days is 1' ); 276 is( $deltas{minutes}, 1, 'delta_minutes is 1' ); 277 is( $deltas{seconds}, 0, 'delta_seconds is 0' ); 278 is( $deltas{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 279 280 my $dur2 = $dt1->subtract_datetime($dt2); 281 282 my %deltas2 = $dur2->deltas; 283 is( $deltas2{months}, 0, 'delta_months is 0' ); 284 is( $deltas2{days}, -1, 'delta_days is -1' ); 285 is( $deltas2{minutes}, -1, 'delta_minutes is -1' ); 286 is( $deltas2{seconds}, 0, 'delta_seconds is 0' ); 287 is( $deltas2{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 288 289} 290 291{ 292 my $dt1 = DateTime->new( year => 2003, month => 4, day => 5, 293 hour => 1, minute => 58, 294 time_zone => 'America/Chicago', 295 ); 296 297 my $dt2 = DateTime->new( year => 2003, month => 4, day => 7, 298 hour => 2, minute => 1, 299 time_zone => 'America/Chicago', 300 ); 301 302 my $dur = $dt2->subtract_datetime($dt1); 303 304 my %deltas = $dur->deltas; 305 is( $deltas{months}, 0, 'delta_months is 0' ); 306 is( $deltas{days}, 2, 'delta_days is 2' ); 307 is( $deltas{minutes}, 3, 'delta_minutes is 3' ); 308 is( $deltas{seconds}, 0, 'delta_seconds is 0' ); 309 is( $deltas{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 310 311 is( DateTime->compare( $dt1->clone->add_duration($dur), $dt2 ), 0, 312 'subtraction is reversible' ); 313 is( DateTime->compare( $dt2->clone->subtract_duration($dur), $dt1 ), 0, 314 'subtraction is doubly reversible' ); 315} 316 317# from example in docs 318{ 319 my $dt1 = DateTime->new( year => 2003, month => 5, day => 6, 320 time_zone => 'America/Chicago', 321 ); 322 323 my $dt2 = DateTime->new( year => 2003, month => 11, day => 6, 324 time_zone => 'America/Chicago', 325 ); 326 327 $dt1->set_time_zone('floating'); 328 $dt2->set_time_zone('floating'); 329 330 my $dur = $dt2->subtract_datetime($dt1); 331 my %deltas = $dur->deltas; 332 is( $deltas{months}, 6, 'delta_months is 6' ); 333 is( $deltas{days}, 0, 'delta_days is 0' ); 334 is( $deltas{minutes}, 0, 'delta_minutes is 0' ); 335 is( $deltas{seconds}, 0, 'delta_seconds is 0' ); 336 is( $deltas{nanoseconds}, 0, 'delta_nanoseconds is 0' ); 337 338 is( DateTime->compare( $dt1->clone->add_duration($dur), $dt2 ), 0, 339 'subtraction is reversible from start point' ); 340 is( DateTime->compare( $dt2->clone->subtract_duration($dur), $dt1 ), 0, 341 'subtraction is reversible from end point' ); 342} 343 344{ 345 my $dt1 = DateTime->new( year => 2005, month => 8, 346 time_zone => 'Europe/London', 347 ); 348 349 my $dt2 = DateTime->new( year => 2005, month => 11, 350 time_zone => 'Europe/London', 351 ); 352 353 my $dur = $dt2->subtract_datetime($dt1); 354 my %deltas = $dur->deltas; 355 is( $deltas{months}, 3, '3 months between two local times over DST change' ); 356 is( $deltas{days}, 0, '0 days between two local times over DST change' ); 357 is( $deltas{minutes}, 0, '0 minutes between two local times over DST change' ); 358} 359 360# same as previous but without hours overflow 361{ 362 my $dt1 = DateTime->new( year => 2005, month => 8, hour => 12, 363 time_zone => 'Europe/London', 364 ); 365 366 my $dt2 = DateTime->new( year => 2005, month => 11, hour => 12, 367 time_zone => 'Europe/London', 368 ); 369 370 my $dur = $dt2->subtract_datetime($dt1); 371 my %deltas = $dur->deltas; 372 is( $deltas{months}, 3, '3 months between two local times over DST change' ); 373 is( $deltas{days}, 0, '0 days between two local times over DST change' ); 374 is( $deltas{minutes}, 0, '0 minutes between two local times over DST change' ); 375} 376 377# another docs example 378{ 379 my $dt2 = DateTime->new( year => 2003, month => 10, day => 26, 380 hour => 1, 381 time_zone => 'America/Chicago', 382 ); 383 384 my $dt1 = $dt2->clone->subtract( hours => 1 ); 385 386 my $dur = $dt2->subtract_datetime($dt1); 387 388 my %deltas = $dur->deltas; 389 is( $deltas{months}, 0, '0 months between two local times over DST change' ); 390 is( $deltas{days}, 0, '0 days between two local times over DST change' ); 391 is( $deltas{minutes}, 60, '60 minutes between two local times over DST change' ); 392 393 is( DateTime->compare( $dt1->clone->add_duration($dur), $dt2 ), 0, 394 'subtraction is reversible' ); 395 is( DateTime->compare( $dt2->clone->subtract_duration($dur), $dt1 ), 0, 396 'subtraction is doubly reversible' ); 397} 398 399{ 400 my $dt1 = DateTime->new( year => 2003, month => 5, day => 6, 401 time_zone => 'America/New_York', 402 ); 403 404 my $dt2 = DateTime->new( year => 2003, month => 5, day => 6, 405 time_zone => 'America/Chicago', 406 ); 407 408 my $dur = $dt2->subtract_datetime($dt1); 409 410 my %deltas = $dur->deltas; 411 is( $deltas{months}, 0, '0 months between two local times over DST change' ); 412 is( $deltas{days}, 0, '0 days between two local times over DST change' ); 413 is( $deltas{minutes}, 60, '60 minutes between two local times over DST change' ); 414 415 is( DateTime->compare( $dt1->clone->add_duration($dur), $dt2 ), 0, 416 'subtraction is reversible' ); 417 is( DateTime->compare( $dt2->clone->subtract_duration($dur), $dt1 ), 0, 418 'subtraction is doubly reversible' ); 419} 420 421# Fix a bug that occurred when the local time zone had DST and the two 422# datetime objects were on the same day 423{ 424 my $dt1 = DateTime->new( year => 2005, month => 4, day => 3, 425 hour => 7, minute => 0, 426 time_zone => 'America/New_York' ); 427 428 my $dt2 = DateTime->new( year => 2005, month => 4, day => 3, 429 hour => 8, minute => 0, 430 time_zone => 'America/New_York' ); 431 432 my $dur = $dt2->subtract_datetime($dt1); 433 my ( $minutes, $seconds ) = $dur->in_units( 'minutes','seconds' ); 434 435 is( $minutes, 60, 'subtraction of two dates on a DST change date, minutes == 60' ); 436 is( $seconds, 0, 'subtraction of two dates on a DST change date, seconds == 0' ); 437 438 $dur = $dt1->subtract_datetime($dt1); 439 ok( $dur->is_zero, 'dst change date (no dst) - itself, duration is zero' ); 440} 441 442{ 443 my $dt1 = DateTime->new( year => 2005, month => 4, day => 3, 444 hour => 1, minute => 0, 445 time_zone => 'America/New_York' ); 446 447 my $dur = $dt1->subtract_datetime($dt1); 448 ok( $dur->is_zero, 'dst change date (with dst) - itself, duration is zero' ); 449} 450 451# This tests a bug where one of the datetimes is changing DST, and the 452# other is not. In this case, no "adjustments" (aka hacks) are made in 453# subtract_datetime, and it just gives the "UTC difference". 454{ 455 # This is UTC-4 456 my $dt1 = DateTime->new( year => 2009, month => 3, day => 9, 457 time_zone => 'America/New_York' ); 458 # This is UTC+8 459 my $dt2 = DateTime->new( year => 2009, month => 3, day => 9, 460 time_zone => 'Asia/Hong_Kong' ); 461 462 my $dur = $dt1->subtract_datetime($dt2); 463 464 is( $dur->delta_minutes, 720, 465 'subtraction the day after a DST change in one zone, where the other datetime is in a different zone' ); 466} 467 468{ 469 # This is UTC-5 470 my $dt1 = DateTime->new( year => 2009, month => 3, day => 8, 471 hour => 1, 472 time_zone => 'America/New_York' ); 473 # This is UTC+8 474 my $dt2 = DateTime->new( year => 2009, month => 3, day => 8, 475 hour => 1, 476 time_zone => 'Asia/Hong_Kong' ); 477 478 my $dur = $dt1->subtract_datetime($dt2); 479 480 is( $dur->delta_minutes, 780, 481 'subtraction the day of a DST change in one zone (before the change),' 482 . ' where the other datetime is in a different zone' ); 483} 484 485 486{ 487 # This is UTC-4 488 my $dt1 = DateTime->new( year => 2009, month => 3, day => 8, 489 hour => 4, 490 time_zone => 'America/New_York' ); 491 # This is UTC+8 492 my $dt2 = DateTime->new( year => 2009, month => 3, day => 8, 493 hour => 4, 494 time_zone => 'Asia/Hong_Kong' ); 495 496 my $dur = $dt1->subtract_datetime($dt2); 497 498 is( $dur->delta_minutes, 720, 499 'subtraction the day of a DST change in one zone (after the change),' 500 . ' where the other datetime is in a different zone' ); 501} 502 503