php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #50559 Clone is not implemented for DateInterval and DatePeriod
Submitted: 2009-12-23 12:04 UTC Modified: 2017-10-24 07:52 UTC
Votes:28
Avg. Score:4.1 ± 0.9
Reproduced:27 of 27 (100.0%)
Same Version:11 (40.7%)
Same OS:3 (11.1%)
From: sr at emini dot dk Assigned:
Status: Open Package: Date/time related
PHP Version: 5.3.1 OS: Fedora 10
Private report: No CVE-ID: None
Have you experienced this issue?
Rate the importance of this bug to you:

 [2009-12-23 12:04 UTC] sr at emini dot dk
Description:
------------
I am unable to clone an object of type DateInterval or DatePeriod. The clone appears to be an empty object.

I've looked in the source and the code to clone the objects seem to be missing from both date_object_clone_interval() and date_object_clone_period().

Reproduce code:
---------------
$dateInterval1 = new \DateInterval('P1D');
$dateInterval2 = clone $dateInterval;
var_dump($dateInterval1);
var_dump($dateInterval2);


Expected result:
----------------
$dateInterval2 should be a clone of $dateInterval1.

Actual result:
--------------
$dateInterval1 works as expected, but $dateInterval2 appears to be an empty object.

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2010-01-27 13:47 UTC] yoarvi at gmail dot com
The following patch implements the logic to clone DatePeriod and DateInterval objects and also includes a test case:

Index: ext/date/php_date.c
===================================================================
--- ext/date/php_date.c	(revision 293574)
+++ ext/date/php_date.c	(working copy)
@@ -2213,7 +2213,9 @@
 	
 	zend_objects_clone_members(&new_obj->std, new_ov, &old_obj->std, Z_OBJ_HANDLE_P(this_ptr) TSRMLS_CC);
 
-	/** FIX ME ADD CLONE STUFF **/
+	new_obj->diff = timelib_rel_time_clone(old_obj->diff);
+	new_obj->initialized = 1;
+
 	return new_ov;
 }
 
@@ -2283,7 +2285,27 @@
 	
 	zend_objects_clone_members(&new_obj->std, new_ov, &old_obj->std, Z_OBJ_HANDLE_P(this_ptr) TSRMLS_CC);
 
-	/** FIX ME ADD CLONE STUFF **/
+	new_obj->start = timelib_time_ctor();
+	*new_obj->start = *old_obj->start;
+	if (old_obj->start->tz_abbr) {
+		new_obj->start->tz_abbr = strdup(old_obj->start->tz_abbr);
+	}
+	if (old_obj->start->tz_info) {
+		new_obj->start->tz_info = old_obj->start->tz_info;
+	}
+	new_obj->end = timelib_time_ctor();
+	*new_obj->end = *old_obj->end;
+	if (old_obj->end->tz_abbr) {
+		new_obj->end->tz_abbr = strdup(old_obj->end->tz_abbr);
+	}
+	if (old_obj->end->tz_info) {
+		new_obj->end->tz_info = old_obj->end->tz_info;
+	}
+	new_obj->interval = timelib_rel_time_clone(old_obj->interval);
+	new_obj->recurrences = old_obj->recurrences;
+	new_obj->include_start_date = old_obj->include_start_date;
+	new_obj->initialized = 1;
+
 	return new_ov;
 }

Index: ext/date/tests/bug50559.phpt
===================================================================
--- ext/date/tests/bug50559.phpt	(revision 0)
+++ ext/date/tests/bug50559.phpt	(revision 0)
@@ -0,0 +1,131 @@
+--TEST--
+Bug #50559 (Clone is not implemented for DateInterval and DatePeriod)
+--FILE--
+<?php
+date_default_timezone_set('Asia/Calcutta');
+
+# Test DateInterval cloning
+$dateInterval1 = new \DateInterval('P1D');
+$dateInterval2 = clone $dateInterval1;
+echo "============================\n";
+echo "DateInterval (original)\n";
+var_dump($dateInterval1);
+echo "============================\n";
+echo "DateInterval (clone)\n";
+var_dump($dateInterval2);
+
+# Test DatePeriod cloning
+$begin = new DateTime('2007-12-31');
+$end = new DateTime('2009-12-31 23:59:59');
+$interval = DateInterval::createFromDateString('last thursday of next month');
+$datePeriod1 = new \DatePeriod($begin, $interval, $end,
+                               DatePeriod::EXCLUDE_START_DATE);
+$datePeriod2 = clone $datePeriod1;
+
+echo "============================\n";
+echo "DatePeriod (original)\n";
+foreach ($datePeriod1 as $p) {
+	echo $p->format("l Y-m-d H:i:s\n");
+}
+
+echo "============================\n";
+echo "DatePeriod (clone)\n";
+foreach ($datePeriod2 as $p) {
+	echo $p->format("l Y-m-d H:i:s\n");
+}
+echo "============================\n";
+?>
+--EXPECT--
+============================
+DateInterval (original)
+object(DateInterval)#1 (8) {
+  ["y"]=>
+  int(0)
+  ["m"]=>
+  int(0)
+  ["d"]=>
+  int(1)
+  ["h"]=>
+  int(0)
+  ["i"]=>
+  int(0)
+  ["s"]=>
+  int(0)
+  ["invert"]=>
+  int(0)
+  ["days"]=>
+  int(0)
+}
+============================
+DateInterval (clone)
+object(DateInterval)#2 (8) {
+  ["y"]=>
+  int(0)
+  ["m"]=>
+  int(0)
+  ["d"]=>
+  int(1)
+  ["h"]=>
+  int(0)
+  ["i"]=>
+  int(0)
+  ["s"]=>
+  int(0)
+  ["invert"]=>
+  int(0)
+  ["days"]=>
+  int(0)
+}
+============================
+DatePeriod (original)
+Thursday 2008-01-31 00:00:00
+Thursday 2008-02-28 00:00:00
+Thursday 2008-03-27 00:00:00
+Thursday 2008-04-24 00:00:00
+Thursday 2008-05-29 00:00:00
+Thursday 2008-06-26 00:00:00
+Thursday 2008-07-31 00:00:00
+Thursday 2008-08-28 00:00:00
+Thursday 2008-09-25 00:00:00
+Thursday 2008-10-30 00:00:00
+Thursday 2008-11-27 00:00:00
+Thursday 2008-12-25 00:00:00
+Thursday 2009-01-29 00:00:00
+Thursday 2009-02-26 00:00:00
+Thursday 2009-03-26 00:00:00
+Thursday 2009-04-30 00:00:00
+Thursday 2009-05-28 00:00:00
+Thursday 2009-06-25 00:00:00
+Thursday 2009-07-30 00:00:00
+Thursday 2009-08-27 00:00:00
+Thursday 2009-09-24 00:00:00
+Thursday 2009-10-29 00:00:00
+Thursday 2009-11-26 00:00:00
+Thursday 2009-12-31 00:00:00
+============================
+DatePeriod (clone)
+Thursday 2008-01-31 00:00:00
+Thursday 2008-02-28 00:00:00
+Thursday 2008-03-27 00:00:00
+Thursday 2008-04-24 00:00:00
+Thursday 2008-05-29 00:00:00
+Thursday 2008-06-26 00:00:00
+Thursday 2008-07-31 00:00:00
+Thursday 2008-08-28 00:00:00
+Thursday 2008-09-25 00:00:00
+Thursday 2008-10-30 00:00:00
+Thursday 2008-11-27 00:00:00
+Thursday 2008-12-25 00:00:00
+Thursday 2009-01-29 00:00:00
+Thursday 2009-02-26 00:00:00
+Thursday 2009-03-26 00:00:00
+Thursday 2009-04-30 00:00:00
+Thursday 2009-05-28 00:00:00
+Thursday 2009-06-25 00:00:00
+Thursday 2009-07-30 00:00:00
+Thursday 2009-08-27 00:00:00
+Thursday 2009-09-24 00:00:00
+Thursday 2009-10-29 00:00:00
+Thursday 2009-11-26 00:00:00
+Thursday 2009-12-31 00:00:00
+============================
 [2010-03-07 20:24 UTC] derick@php.net
This patch causes issues. If I try the attached script I end up in an infinite loop.
 [2010-03-08 10:59 UTC] yoarvi at gmail dot com
Did you forget to attach the script?
 [2011-05-06 11:14 UTC] giorgio dot liscio at email dot it
on windows 7 + apache2 + php 5.3.6

apache crashes when

$cl = clone $dateIntervalObject;
in any scope, in any function, on an empty page too.

i tried to provide a backtrace but something goes wrong

i can't convert the generated .dmp file to a readable dump to attach here
 [2013-08-07 20:09 UTC] kofkof at laposte dot net
If I swap lines 2 and 3 in the bug description code, this way:

$dateInterval1 = new DateInterval('P1D');
var_dump($dateInterval1);
$dateInterval2 = clone $dateInterval1;
var_dump($dateInterval2);

... then $dateInterval2 is not empty (as displayed by var_dump()). However, when 
I try to use it:

$d = new DateTime();
$d->add($dateInterval2);

... then I get "Warning: DateTime::add(): The DateInterval object has not been 
correctly initialized by its constructor in..."

(PHP 5.4.11 on Mac OS 10.7.5)
 [2016-10-30 15:08 UTC] anatoliy at ukhvanovy dot name
There's a WORKAROUND.
If you var_export the instance, then it won't be empty anymore, and you can clone it. Consider the following code:

<?php
header('Content-Type:text/plain');

$originalInstance = new DateInterval('P1D');
// Original instance is not initialized yet.
// You cannot clone it now, otherwise you'll get an empty copy.

$copyCreatedBeforeOriginalInstanceIsInitialized =
    clone $originalInstance;
echo PHP_EOL, 'This copy is empty: ';
var_export($copyCreatedBeforeOriginalInstanceIsInitialized);

// Now, let's initialize original instance by var_export'ing it.
// We don't want to output the var_export's result -
// it is used only to initialize our instance, not to output it.
$doNotEchoVarExport = true;
var_export($originalInstance, $doNotEchoVarExport);
// That's all! For now, original instance is already initialized,
// and we may clone it — we'll get correct copy.

$copyCreatedAfterOriginalInstanceIsInitialized =
    clone $originalInstance;

// Now, let's see what we've got:

echo PHP_EOL, 'Original instance is OK: ';
var_export($originalInstance);

echo PHP_EOL, 'Empty copy is still empty: ';
var_export($copyCreatedBeforeOriginalInstanceIsInitialized);

echo PHP_EOL, 'Correct copy is OK: ';
var_export($copyCreatedAfterOriginalInstanceIsInitialized);

// DateInterval behaves like Schrödinger's cat:
// the action of observation affects the phenomenon being observed.
// (var_export'ing the state of DateInterval affects its state.)
?>

The code above produces the following output:

This copy is empty: DateInterval::__set_state(array(
))
Original instance is OK: DateInterval::__set_state(array(
   'y' => 0,
   'm' => 0,
   'd' => 1,
   'h' => 0,
   'i' => 0,
   's' => 0,
   'weekday' => 0,
   'weekday_behavior' => 0,
   'first_last_day_of' => 0,
   'invert' => 0,
   'days' => false,
   'special_type' => 0,
   'special_amount' => 0,
   'have_weekday_relative' => 0,
   'have_special_relative' => 0,
))
Empty copy is still empty: DateInterval::__set_state(array(
))
Correct copy is OK: DateInterval::__set_state(array(
   'y' => 0,
   'm' => 0,
   'd' => 1,
   'h' => 0,
   'i' => 0,
   's' => 0,
   'weekday' => 0,
   'weekday_behavior' => 0,
   'first_last_day_of' => 0,
   'invert' => 0,
   'days' => false,
   'special_type' => 0,
   'special_amount' => 0,
   'have_weekday_relative' => 0,
   'have_special_relative' => 0,
))
 [2016-10-30 16:12 UTC] anatoliy at ukhvanovy dot name
The previous example doesn't work. The cloned copy can be var_export'ed correctly, that's true. But when you try to add it to a DateTime instance, the datetime won't change at all. Thus, cloned copies are broken. That's why I use another workaround:
<?php
class CloneDateInterval
{
    /**
     * @param \DateInterval $originalInstance
     * @return \DateInterval
     */
    public static function buildClone(\DateInterval $originalInstance) {
        $copy = new \DateInterval('P0D');
        foreach ($originalInstance as $propertyName => $propertyValue) {
            $copy->{$propertyName} = $propertyValue;
        }
        return $copy;
    }
}
?> 
This one works fine:
$copy = CloneDateTime::buildClone($originalDateInterval);
$datetime->add($copy); //OK
 [2017-06-30 21:49 UTC] anatoliy at ukhvanovy dot name
Also, if you don't wanna deal with manual coping all the fields like in example above, you may copy a \DateInterval object indirectly, using the inverse operations \DateTime::add() and \DateTime::diff() as shown below:

<?php
class CloneDateInterval
{
    /**
     * @var \DateTime
     */
    private static $midnight;

    private static function getMidnight(): \DateTime
    {
        if (self::$midnight === null) {
            self::$midnight = new \DateTime('midnight');
        }
        return clone self::$midnight;
    }

    public static function makeClone(\DateInterval $dateInterval): \DateInterval
    {
        return self::getMidnight()->diff(self::getMidnight()->add($dateInterval));
    }
}
?>

Although, maybe this method is not as computationally efficient as the one from above, but it definitely is an interesting alternative, at least from theoretical point of view. :D
 [2017-10-24 07:52 UTC] kalle@php.net
-Status: Assigned +Status: Open -Assigned To: derick +Assigned To:
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Sun Nov 19 01:31:42 2017 UTC