|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[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.
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Tue Dec 02 05:00:02 2025 UTC |
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 +============================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)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, ))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); //OKAlso, 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