#49081 [Asn]: [PATCH] DateTime::diff() mistake if start in January and interval > 28 days

2010-01-22 Thread danielc
 ID:   49081
 Updated by:   dani...@php.net
 Reported By:  nate at frickenate dot com
 Status:   Assigned
 Bug Type: Date/time related
 Operating System: *
 PHP Version:  5.3.0
 Assigned To:  derick
 New Comment:

Here is an updated patch against PHP_5_3.  It removes all of the
"next_month" code in 

do_range_limit_days_relative().
http://www.analysisandsolutions.com/php/bug49081v2.53.diff

As with the earlier patch, PHP built fine with it and all of the
ext/date tests that passed under 

the unpached PHP_5_3 build continued to pass under the patched
version.

Grepping the entire PHP_5_3 code base indicates the only place
do_range_limit_days_relative() gets 

called is in timelib_do_rel_normalize(), and the only place that gets
called is timelib_diff().

It is unclear what purpose the "next_month" functionality served. 
Perhaps it was there in case the 

"base_y" and "base_m" parameters were later than the "y"/"m"/"d"
parameters?  But that doesn't apply 

becausetimelib_diff() always passes the earlier date to
timelib_do_rel_normalize() as "one" and on to 

do_range_limit_days_relative() as "base_*".


Previous Comments:


[2010-01-06 16:19:14] dani...@php.net

Hmm... Isn't the whole days_next_month functionality in
do_range_limit_days_relative() unnecessary?



[2010-01-06 01:04:18] dani...@php.net

A better test file:
http://www.analysisandsolutions.com/php/bug49081v2.phpt

The scenarios it covers are more relevant and thorough, plus it
improves the context, making it easier see what the test is doing.



[2010-01-05 22:11:57] dani...@php.net

This bug continues to exist in 5.3.2RC2.

DateTime::diff() / date_diff() chokes on dates starting in January if
the interval is greater than 28 days (during non-leap years).

It is happening because of a bug in do_range_limit_days_relative() in
ext/date/lib/tm2unixtime.c.  At the end of the function if *d >
days_next_month, the code subtracts the days and adds a month.  So when
the starting month is January, the days_next_month is 28 or 29,
mistakenly triggering the month/day swapping behavior.

Patch:
http://www.analysisandsolutions.com/php/bug49081.diff

Test:
http://www.analysisandsolutions.com/php/bug49081.phpt



[2009-12-16 05:44:52] peter dot schleif at gmx dot de

More simple code to reproduce error:

diff($d2);
   print_r($d);
?>

Expected:
-
   [m] => 0
   [d] => 30

Actual:
---
   [m] => 1
   [d] => 2



[2009-07-27 22:55:24] nate at frickenate dot com

Description:

DateTime::diff calculates diffs incorrectly.

As an example, the diff of 2009-01-01 and 2009-03-31 *should be* "2
months, 30 days". 2009-01-01 + 2 months = 2009-03-01, + 30 days =
2009-03-31. Taking 2009-01-01 and using DateTime::add() to add 'P2M30D'
does indeed result in 2009-03-31. This is correct.

However, running the diff() of 2009-01-01 and 2009-03-31 returns "3
months, 2 days". add()ing 2009-01-01 + 'P3M2D' returns 2009-04-03
instead of 2009-03-31 (as it should, since the diff that told us to add
3M2D was incorrect).

Reproduce code:
---
add(new DateInterval('P2M30D'))); // correct period
var_dump($jan_1->add(new DateInterval('P3M2D')));  // incorrect period

// END EXAMPLE CODE - following is just extra fluff


// here's the replacement function I am currently
// using to calculate the correct diff until the
// built-in method is patched and functional
function date_diff2 ($t1, $t2) {
if (! preg_match('/^\d+\z/', $t1) && ($t1 = strtotime($t1)) ===
false)
return false;

if (! preg_match('/^\d+\z/', $t2) && ($t2 = strtotime($t2)) ===
false)
return false;

if ($t1 > $t2)
list($t1, $t2) = array($t2, $t1);

$diffs = array(
'years' => 0, 'months' => 0, 'days' => 0,
'hours' => 0, 'minutes' => 0, 'seconds' => 0,
);

foreach (array_keys($diffs) as $interval) {
while ($t2 >= ($t3 = strtotime("+1 ${interval}", $t1))) {
$t1 = $t3;
++$diffs[$interval];
}
}

return $diffs;
}


?>

Expected result:

object(DateInterval)#3 (8) {
  ["y"]=>
  int(0)
  ["m"]=>
  int(2)
  ["d"]=>
  int(30)
  ["h"]=>
  int(0)
  ["i"]=>
  int(0)
  ["s"]=>
  int(0)
  ["invert"]=>
  int(0)
  ["days"]=>
  int(89)
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2009-03-31 00:00:00"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(16) "America/Montreal"
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2009-07-03 00:00:00"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(16) "America/Montreal"
}


Actual result:
--
object(DateInter

#49081 [Asn]: [PATCH] DateTime::diff() mistake if start in January and interval > 28 days

2010-01-06 Thread danielc
 ID:   49081
 Updated by:   dani...@php.net
 Reported By:  nate at frickenate dot com
 Status:   Assigned
 Bug Type: Date/time related
 Operating System: *
 PHP Version:  5.3.0
 Assigned To:  derick
 New Comment:

Hmm... Isn't the whole days_next_month functionality in
do_range_limit_days_relative() unnecessary?


Previous Comments:


[2010-01-06 01:04:18] dani...@php.net

A better test file:
http://www.analysisandsolutions.com/php/bug49081v2.phpt

The scenarios it covers are more relevant and thorough, plus it
improves the context, making it easier see what the test is doing.



[2010-01-05 22:11:57] dani...@php.net

This bug continues to exist in 5.3.2RC2.

DateTime::diff() / date_diff() chokes on dates starting in January if
the interval is greater than 28 days (during non-leap years).

It is happening because of a bug in do_range_limit_days_relative() in
ext/date/lib/tm2unixtime.c.  At the end of the function if *d >
days_next_month, the code subtracts the days and adds a month.  So when
the starting month is January, the days_next_month is 28 or 29,
mistakenly triggering the month/day swapping behavior.

Patch:
http://www.analysisandsolutions.com/php/bug49081.diff

Test:
http://www.analysisandsolutions.com/php/bug49081.phpt



[2009-12-16 05:44:52] peter dot schleif at gmx dot de

More simple code to reproduce error:

diff($d2);
   print_r($d);
?>

Expected:
-
   [m] => 0
   [d] => 30

Actual:
---
   [m] => 1
   [d] => 2



[2009-07-27 22:55:24] nate at frickenate dot com

Description:

DateTime::diff calculates diffs incorrectly.

As an example, the diff of 2009-01-01 and 2009-03-31 *should be* "2
months, 30 days". 2009-01-01 + 2 months = 2009-03-01, + 30 days =
2009-03-31. Taking 2009-01-01 and using DateTime::add() to add 'P2M30D'
does indeed result in 2009-03-31. This is correct.

However, running the diff() of 2009-01-01 and 2009-03-31 returns "3
months, 2 days". add()ing 2009-01-01 + 'P3M2D' returns 2009-04-03
instead of 2009-03-31 (as it should, since the diff that told us to add
3M2D was incorrect).

Reproduce code:
---
add(new DateInterval('P2M30D'))); // correct period
var_dump($jan_1->add(new DateInterval('P3M2D')));  // incorrect period

// END EXAMPLE CODE - following is just extra fluff


// here's the replacement function I am currently
// using to calculate the correct diff until the
// built-in method is patched and functional
function date_diff2 ($t1, $t2) {
if (! preg_match('/^\d+\z/', $t1) && ($t1 = strtotime($t1)) ===
false)
return false;

if (! preg_match('/^\d+\z/', $t2) && ($t2 = strtotime($t2)) ===
false)
return false;

if ($t1 > $t2)
list($t1, $t2) = array($t2, $t1);

$diffs = array(
'years' => 0, 'months' => 0, 'days' => 0,
'hours' => 0, 'minutes' => 0, 'seconds' => 0,
);

foreach (array_keys($diffs) as $interval) {
while ($t2 >= ($t3 = strtotime("+1 ${interval}", $t1))) {
$t1 = $t3;
++$diffs[$interval];
}
}

return $diffs;
}


?>

Expected result:

object(DateInterval)#3 (8) {
  ["y"]=>
  int(0)
  ["m"]=>
  int(2)
  ["d"]=>
  int(30)
  ["h"]=>
  int(0)
  ["i"]=>
  int(0)
  ["s"]=>
  int(0)
  ["invert"]=>
  int(0)
  ["days"]=>
  int(89)
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2009-03-31 00:00:00"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(16) "America/Montreal"
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2009-07-03 00:00:00"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(16) "America/Montreal"
}


Actual result:
--
object(DateInterval)#3 (8) {
  ["y"]=>
  int(0)
  ["m"]=>
  int(3)
  ["d"]=>
  int(2)
  ["h"]=>
  int(0)
  ["i"]=>
  int(0)
  ["s"]=>
  int(0)
  ["invert"]=>
  int(0)
  ["days"]=>
  int(89)
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2009-03-31 00:00:00"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(16) "America/Montreal"
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2009-07-03 00:00:00"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(16) "America/Montreal"
}






-- 
Edit this bug report at http://bugs.php.net/?id=49081&edit=1



#49081 [Asn]: [PATCH] DateTime::diff() mistake if start in January and interval > 28 days

2010-01-05 Thread danielc
 ID:   49081
 Updated by:   dani...@php.net
 Reported By:  nate at frickenate dot com
 Status:   Assigned
 Bug Type: Date/time related
 Operating System: *
 PHP Version:  5.3.0
 Assigned To:  derick
 New Comment:

A better test file:
http://www.analysisandsolutions.com/php/bug49081v2.phpt

The scenarios it covers are more relevant and thorough, plus it
improves the context, making it easier see what the test is doing.


Previous Comments:


[2010-01-05 22:11:57] dani...@php.net

This bug continues to exist in 5.3.2RC2.

DateTime::diff() / date_diff() chokes on dates starting in January if
the interval is greater than 28 days (during non-leap years).

It is happening because of a bug in do_range_limit_days_relative() in
ext/date/lib/tm2unixtime.c.  At the end of the function if *d >
days_next_month, the code subtracts the days and adds a month.  So when
the starting month is January, the days_next_month is 28 or 29,
mistakenly triggering the month/day swapping behavior.

Patch:
http://www.analysisandsolutions.com/php/bug49081.diff

Test:
http://www.analysisandsolutions.com/php/bug49081.phpt



[2009-12-16 05:44:52] peter dot schleif at gmx dot de

More simple code to reproduce error:

diff($d2);
   print_r($d);
?>

Expected:
-
   [m] => 0
   [d] => 30

Actual:
---
   [m] => 1
   [d] => 2



[2009-07-27 22:55:24] nate at frickenate dot com

Description:

DateTime::diff calculates diffs incorrectly.

As an example, the diff of 2009-01-01 and 2009-03-31 *should be* "2
months, 30 days". 2009-01-01 + 2 months = 2009-03-01, + 30 days =
2009-03-31. Taking 2009-01-01 and using DateTime::add() to add 'P2M30D'
does indeed result in 2009-03-31. This is correct.

However, running the diff() of 2009-01-01 and 2009-03-31 returns "3
months, 2 days". add()ing 2009-01-01 + 'P3M2D' returns 2009-04-03
instead of 2009-03-31 (as it should, since the diff that told us to add
3M2D was incorrect).

Reproduce code:
---
add(new DateInterval('P2M30D'))); // correct period
var_dump($jan_1->add(new DateInterval('P3M2D')));  // incorrect period

// END EXAMPLE CODE - following is just extra fluff


// here's the replacement function I am currently
// using to calculate the correct diff until the
// built-in method is patched and functional
function date_diff2 ($t1, $t2) {
if (! preg_match('/^\d+\z/', $t1) && ($t1 = strtotime($t1)) ===
false)
return false;

if (! preg_match('/^\d+\z/', $t2) && ($t2 = strtotime($t2)) ===
false)
return false;

if ($t1 > $t2)
list($t1, $t2) = array($t2, $t1);

$diffs = array(
'years' => 0, 'months' => 0, 'days' => 0,
'hours' => 0, 'minutes' => 0, 'seconds' => 0,
);

foreach (array_keys($diffs) as $interval) {
while ($t2 >= ($t3 = strtotime("+1 ${interval}", $t1))) {
$t1 = $t3;
++$diffs[$interval];
}
}

return $diffs;
}


?>

Expected result:

object(DateInterval)#3 (8) {
  ["y"]=>
  int(0)
  ["m"]=>
  int(2)
  ["d"]=>
  int(30)
  ["h"]=>
  int(0)
  ["i"]=>
  int(0)
  ["s"]=>
  int(0)
  ["invert"]=>
  int(0)
  ["days"]=>
  int(89)
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2009-03-31 00:00:00"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(16) "America/Montreal"
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2009-07-03 00:00:00"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(16) "America/Montreal"
}


Actual result:
--
object(DateInterval)#3 (8) {
  ["y"]=>
  int(0)
  ["m"]=>
  int(3)
  ["d"]=>
  int(2)
  ["h"]=>
  int(0)
  ["i"]=>
  int(0)
  ["s"]=>
  int(0)
  ["invert"]=>
  int(0)
  ["days"]=>
  int(89)
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2009-03-31 00:00:00"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(16) "America/Montreal"
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2009-07-03 00:00:00"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(16) "America/Montreal"
}






-- 
Edit this bug report at http://bugs.php.net/?id=49081&edit=1



#49081 [Asn]: [PATCH] DateTime::diff() mistake if start in January and interval > 28 days

2010-01-05 Thread danielc
 ID:   49081
 Updated by:   dani...@php.net
-Summary:  DateTime::diff calculates diffs incorrectly
 Reported By:  nate at frickenate dot com
 Status:   Assigned
 Bug Type: Date/time related
 Operating System: *
 PHP Version:  5.3.0
 Assigned To:  derick
 New Comment:

This bug continues to exist in 5.3.2RC2.

DateTime::diff() / date_diff() chokes on dates starting in January if
the interval is greater than 28 days (during non-leap years).

It is happening because of a bug in do_range_limit_days_relative() in
ext/date/lib/tm2unixtime.c.  At the end of the function if *d >
days_next_month, the code subtracts the days and adds a month.  So when
the starting month is January, the days_next_month is 28 or 29,
mistakenly triggering the month/day swapping behavior.

Patch:
http://www.analysisandsolutions.com/php/bug49081.diff

Test:
http://www.analysisandsolutions.com/php/bug49081.phpt


Previous Comments:


[2009-12-16 05:44:52] peter dot schleif at gmx dot de

More simple code to reproduce error:

diff($d2);
   print_r($d);
?>

Expected:
-
   [m] => 0
   [d] => 30

Actual:
---
   [m] => 1
   [d] => 2



[2009-07-27 22:55:24] nate at frickenate dot com

Description:

DateTime::diff calculates diffs incorrectly.

As an example, the diff of 2009-01-01 and 2009-03-31 *should be* "2
months, 30 days". 2009-01-01 + 2 months = 2009-03-01, + 30 days =
2009-03-31. Taking 2009-01-01 and using DateTime::add() to add 'P2M30D'
does indeed result in 2009-03-31. This is correct.

However, running the diff() of 2009-01-01 and 2009-03-31 returns "3
months, 2 days". add()ing 2009-01-01 + 'P3M2D' returns 2009-04-03
instead of 2009-03-31 (as it should, since the diff that told us to add
3M2D was incorrect).

Reproduce code:
---
add(new DateInterval('P2M30D'))); // correct period
var_dump($jan_1->add(new DateInterval('P3M2D')));  // incorrect period

// END EXAMPLE CODE - following is just extra fluff


// here's the replacement function I am currently
// using to calculate the correct diff until the
// built-in method is patched and functional
function date_diff2 ($t1, $t2) {
if (! preg_match('/^\d+\z/', $t1) && ($t1 = strtotime($t1)) ===
false)
return false;

if (! preg_match('/^\d+\z/', $t2) && ($t2 = strtotime($t2)) ===
false)
return false;

if ($t1 > $t2)
list($t1, $t2) = array($t2, $t1);

$diffs = array(
'years' => 0, 'months' => 0, 'days' => 0,
'hours' => 0, 'minutes' => 0, 'seconds' => 0,
);

foreach (array_keys($diffs) as $interval) {
while ($t2 >= ($t3 = strtotime("+1 ${interval}", $t1))) {
$t1 = $t3;
++$diffs[$interval];
}
}

return $diffs;
}


?>

Expected result:

object(DateInterval)#3 (8) {
  ["y"]=>
  int(0)
  ["m"]=>
  int(2)
  ["d"]=>
  int(30)
  ["h"]=>
  int(0)
  ["i"]=>
  int(0)
  ["s"]=>
  int(0)
  ["invert"]=>
  int(0)
  ["days"]=>
  int(89)
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2009-03-31 00:00:00"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(16) "America/Montreal"
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2009-07-03 00:00:00"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(16) "America/Montreal"
}


Actual result:
--
object(DateInterval)#3 (8) {
  ["y"]=>
  int(0)
  ["m"]=>
  int(3)
  ["d"]=>
  int(2)
  ["h"]=>
  int(0)
  ["i"]=>
  int(0)
  ["s"]=>
  int(0)
  ["invert"]=>
  int(0)
  ["days"]=>
  int(89)
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2009-03-31 00:00:00"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(16) "America/Montreal"
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(19) "2009-07-03 00:00:00"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(16) "America/Montreal"
}






-- 
Edit this bug report at http://bugs.php.net/?id=49081&edit=1