In the current implementation DateTime is not a value object, but its
internal state can be modified at any given time. This can lead to very
obscure bugs when references to DateTime objects are shared or objects are
passed through a chain of methods/functions that modify it. Using DateTime
is not side-effect free.
I propose to allow to work with DateTime objects that are marked as
immutable optionally. This means that all methods add, sub, modify,
setDate, setTime, setISODate, setTimestamp and setTimezone will return a
NEW instance of Datetime instead of reusing the old one.
I also talked to Derick about this and he agrees that immutable DateTime
objects would be desirable. I have talked to many other people who agreed
that the current behavior is weird.
My proposed syntax would be:
$immutableDateTime = date_create("2010-12-05", null, true);
$immutableDateTime = new DateTime("2010-12-05", null, true);
$immutableDateTime = DateTime::createFromFormat("%Y-%m-%d", "2010-12-05",
null, true);
Where the third and fourth variable respectivly are boolean flags
$immutable yes or no. Also the DatePeriod iterator would be modified. If an
immutable start date is passed the date iterator would also create
immutable dates.
I have attached a patch that implements this functionality and a little
test-script that shows how it would work. This is the first complex bit of
C-code that I did so please bear with any mistakes I made ;-) Also i havent
followed the coding standards.
Any feedback is greatly appreciated. My C-Skills arent that good so i am
not finished with an additional solution allowing to call a method
"setImmutable()" on any datetime instance, marking it as immutable.
Obviously this would only mark the instance as immutable, allowing to
accept a flag to reset it to be mutable would be counter-productive.
The only drawback I see to this patch is the additional int variable on
the _php_date_obj and _php_date_period structs. I am not sure if they
affect memory in such a way that this solution isn't viable.
If this topic needs more discussion or pro/cons I am willing to open up an
RFC for a more detailed discussion.
<?php
ini_set("date.timezone", "Europe/Berlin");
$d = new DateTime("now", new DateTimeZone("Europe/Berlin"), true);
$d2 = $d->add(DateInterval::createFromDateString("1 month"));
$d3 = $d->sub(DateInterval::createFromDateString("1 month"));
echo $d2->format('d.m.Y') . " " . $d->format('d.m.Y') . " " . $d3->format('d.m.Y');
echo "\nd===d2: ";
echo $d === $d2 ? "equals" : "new";
echo "\nd===d3: ";
echo $d === $d3 ? "equals" : "new";
echo "\n";
$begin = new DateTime( '2007-12-31', new DateTimeZone("Europe/Berlin"), true);
$end = new DateTime( '2009-12-31 23:59:59', new DateTimeZone("Europe/Berlin"), true);
$interval = DateInterval::createFromDateString('last thursday of next month');
$period = new DatePeriod($begin, $interval, $end, DatePeriod::EXCLUDE_START_DATE);
$dates = array();
foreach ( $period as $dt ) {
$newdate = $dt->add(DateInterval::createFromDateString("1 month"));
echo "Iterator: ";
echo $newdate === $dt ? "equal" : "new";
echo "\n";
}
Index: ext/date/php_date.c
===================================================================
--- ext/date/php_date.c (revision 305965)
+++ ext/date/php_date.c (working copy)
@@ -29,6 +29,7 @@
#include "php_date.h"
#include "zend_interfaces.h"
#include "lib/timelib.h"
+#include "Zend/zend_operators.h"
#include <time.h>
#ifdef PHP_WIN32
@@ -140,12 +141,14 @@
ZEND_BEGIN_ARG_INFO_EX(arginfo_date_create, 0, 0, 0)
ZEND_ARG_INFO(0, time)
ZEND_ARG_INFO(0, object)
+ ZEND_ARG_INFO(0, immutable)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_date_create_from_format, 0, 0, 2)
ZEND_ARG_INFO(0, format)
ZEND_ARG_INFO(0, time)
ZEND_ARG_INFO(0, object)
+ ZEND_ARG_INFO(0, immutable)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_date_parse, 0, 0, 1)
@@ -1855,6 +1858,7 @@
newdateobj = (php_date_obj *) zend_object_store_get_object(iterator->current TSRMLS_CC);
newdateobj->time = timelib_time_ctor();
*newdateobj->time = *it_time;
+ newdateobj->immutable = object->immutable;
if (it_time->tz_abbr) {
newdateobj->time->tz_abbr = strdup(it_time->tz_abbr);
}
@@ -2022,6 +2026,7 @@
if (ptr) {
*ptr = intern;
}
+ intern->immutable = 0;
zend_object_std_init(&intern->std, class_type TSRMLS_CC);
zend_hash_copy(intern->std.properties, &class_type->default_properties, (copy_ctor_func_t) zval_add_ref, (void *) &tmp, sizeof(zval *));
@@ -2037,6 +2042,18 @@
return date_object_new_date_ex(class_type, NULL TSRMLS_CC);
}
+static zval* date_object_clone(zval *old TSRMLS_DC)
+{
+ zval *new;
+ zend_object_value oldvalue = date_object_clone_date(old);
+
+ MAKE_STD_ZVAL(new);
+ Z_TYPE_P(new) = IS_OBJECT;
+ Z_OBJVAL_P(new) = oldvalue;
+
+ return new;
+}
+
static zend_object_value date_object_clone_date(zval *this_ptr TSRMLS_DC)
{
php_date_obj *new_obj = NULL;
@@ -2372,7 +2389,7 @@
DATEG(last_errors) = last_errors;
}
-PHPAPI int php_date_initialize(php_date_obj *dateobj, /*const*/ char *time_str, int time_str_len, char *format, zval *timezone_object, int ctor TSRMLS_DC)
+PHPAPI int php_date_initialize(php_date_obj *dateobj, /*const*/ char *time_str, int time_str_len, char *format, zval *timezone_object, zend_bool immutable_flag, int ctor TSRMLS_DC)
{
timelib_time *now;
timelib_tzinfo *tzi;
@@ -2390,6 +2407,8 @@
dateobj->time = timelib_strtotime(time_str_len ? time_str : "now", time_str_len ? time_str_len : sizeof("now") -1, &err, DATE_TIMEZONEDB);
}
+ dateobj->immutable = immutable_flag;
+
/* update last errors and warnings */
update_errors_warnings(err TSRMLS_CC);
@@ -2459,16 +2478,17 @@
*/
PHP_FUNCTION(date_create)
{
+ zend_bool immutable_flag = 0;
zval *timezone_object = NULL;
char *time_str = NULL;
int time_str_len = 0;
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sO", &time_str, &time_str_len, &timezone_object, date_ce_timezone) == FAILURE) {
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sOb", &time_str, &time_str_len, &timezone_object, date_ce_timezone, &immutable_flag) == FAILURE) {
RETURN_FALSE;
}
php_date_instantiate(date_ce_date, return_value TSRMLS_CC);
- if (!php_date_initialize(zend_object_store_get_object(return_value TSRMLS_CC), time_str, time_str_len, NULL, timezone_object, 0 TSRMLS_CC)) {
+ if (!php_date_initialize(zend_object_store_get_object(return_value TSRMLS_CC), time_str, time_str_len, NULL, timezone_object, immutable_flag, 0 TSRMLS_CC)) {
RETURN_FALSE;
}
}
@@ -2479,16 +2499,17 @@
*/
PHP_FUNCTION(date_create_from_format)
{
+ zend_bool immutable_flag = 0;
zval *timezone_object = NULL;
char *time_str = NULL, *format_str = NULL;
int time_str_len = 0, format_str_len = 0;
- if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|O", &format_str, &format_str_len, &time_str, &time_str_len, &timezone_object, date_ce_timezone) == FAILURE) {
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|Ob", &format_str, &format_str_len, &time_str, &time_str_len, &timezone_object, date_ce_timezone, &immutable_flag) == FAILURE) {
RETURN_FALSE;
}
php_date_instantiate(date_ce_date, return_value TSRMLS_CC);
- if (!php_date_initialize(zend_object_store_get_object(return_value TSRMLS_CC), time_str, time_str_len, format_str, timezone_object, 0 TSRMLS_CC)) {
+ if (!php_date_initialize(zend_object_store_get_object(return_value TSRMLS_CC), time_str, time_str_len, format_str, timezone_object, immutable_flag, 0 TSRMLS_CC)) {
RETURN_FALSE;
}
}
@@ -2499,14 +2520,15 @@
*/
PHP_METHOD(DateTime, __construct)
{
+ zend_bool immutable_flag = 0;
zval *timezone_object = NULL;
char *time_str = NULL;
int time_str_len = 0;
zend_error_handling error_handling;
zend_replace_error_handling(EH_THROW, NULL, &error_handling TSRMLS_CC);
- if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sO", &time_str, &time_str_len, &timezone_object, date_ce_timezone)) {
- php_date_initialize(zend_object_store_get_object(getThis() TSRMLS_CC), time_str, time_str_len, NULL, timezone_object, 1 TSRMLS_CC);
+ if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sOb", &time_str, &time_str_len, &timezone_object, date_ce_timezone, &immutable_flag)) {
+ php_date_initialize(zend_object_store_get_object(getThis() TSRMLS_CC), time_str, time_str_len, NULL, timezone_object, immutable_flag, 1 TSRMLS_CC);
}
zend_restore_error_handling(&error_handling TSRMLS_CC);
}
@@ -2533,7 +2555,7 @@
case TIMELIB_ZONETYPE_ABBR: {
char *tmp = emalloc(Z_STRLEN_PP(z_date) + Z_STRLEN_PP(z_timezone) + 2);
snprintf(tmp, Z_STRLEN_PP(z_date) + Z_STRLEN_PP(z_timezone) + 2, "%s %s", Z_STRVAL_PP(z_date), Z_STRVAL_PP(z_timezone));
- php_date_initialize(*dateobj, tmp, Z_STRLEN_PP(z_date) + Z_STRLEN_PP(z_timezone) + 1, NULL, NULL, 0 TSRMLS_CC);
+ php_date_initialize(*dateobj, tmp, Z_STRLEN_PP(z_date) + Z_STRLEN_PP(z_timezone) + 1, NULL, NULL, 0, 0 TSRMLS_CC);
efree(tmp);
return 1;
}
@@ -2549,7 +2571,7 @@
tzobj->tzi.tz = tzi;
tzobj->initialized = 1;
- php_date_initialize(*dateobj, Z_STRVAL_PP(z_date), Z_STRLEN_PP(z_date), NULL, tmp_obj, 0 TSRMLS_CC);
+ php_date_initialize(*dateobj, Z_STRVAL_PP(z_date), Z_STRLEN_PP(z_date), NULL, tmp_obj, 0, 0 TSRMLS_CC);
zval_ptr_dtor(&tmp_obj);
return 1;
}
@@ -2780,6 +2802,11 @@
RETURN_FALSE;
}
dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ if (dateobj->immutable) {
+ object = date_object_clone(object);
+ dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ }
+
DATE_CHECK_INITIALIZED(dateobj->time, DateTime);
tmp_time = timelib_strtotime(modify, modify_len, &err, DATE_TIMEZONEDB);
@@ -2807,6 +2834,8 @@
}
/* }}} */
+
+
/* {{{ proto DateTime date_add(DateTime object, DateInterval interval)
Adds an interval to the current date in object.
*/
@@ -2821,11 +2850,15 @@
RETURN_FALSE;
}
dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ if (dateobj->immutable) {
+ object = date_object_clone(object);
+ dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ }
+
DATE_CHECK_INITIALIZED(dateobj->time, DateTime);
intobj = (php_interval_obj *) zend_object_store_get_object(interval TSRMLS_CC);
DATE_CHECK_INITIALIZED(intobj->initialized, DateInterval);
-
if (intobj->diff->have_weekday_relative || intobj->diff->have_special_relative) {
memcpy(&dateobj->time->relative, intobj->diff, sizeof(struct timelib_rel_time));
} else {
@@ -2866,6 +2899,11 @@
RETURN_FALSE;
}
dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ if (dateobj->immutable) {
+ object = date_object_clone(object);
+ dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ }
+
DATE_CHECK_INITIALIZED(dateobj->time, DateTime);
intobj = (php_interval_obj *) zend_object_store_get_object(interval TSRMLS_CC);
DATE_CHECK_INITIALIZED(intobj->initialized, DateInterval);
@@ -2951,6 +2989,10 @@
RETURN_FALSE;
}
dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ if (dateobj->immutable) {
+ object = date_object_clone(object);
+ dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ }
DATE_CHECK_INITIALIZED(dateobj->time, DateTime);
tzobj = (php_timezone_obj *) zend_object_store_get_object(timezone_object TSRMLS_CC);
if (tzobj->type != TIMELIB_ZONETYPE_ID) {
@@ -3012,6 +3054,11 @@
RETURN_FALSE;
}
dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ if (dateobj->immutable) {
+ object = date_object_clone(object);
+ dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ }
+
DATE_CHECK_INITIALIZED(dateobj->time, DateTime);
dateobj->time->h = h;
dateobj->time->i = i;
@@ -3035,6 +3082,10 @@
RETURN_FALSE;
}
dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ if (dateobj->immutable) {
+ object = date_object_clone(object);
+ dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ }
DATE_CHECK_INITIALIZED(dateobj->time, DateTime);
dateobj->time->y = y;
dateobj->time->m = m;
@@ -3058,6 +3109,11 @@
RETURN_FALSE;
}
dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ if (dateobj->immutable) {
+ object = date_object_clone(object);
+ dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ }
+
DATE_CHECK_INITIALIZED(dateobj->time, DateTime);
dateobj->time->y = y;
dateobj->time->m = 1;
@@ -3084,6 +3140,10 @@
RETURN_FALSE;
}
dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ if (dateobj->immutable) {
+ object = date_object_clone(object);
+ dateobj = (php_date_obj *) zend_object_store_get_object(object TSRMLS_CC);
+ }
DATE_CHECK_INITIALIZED(dateobj->time, DateTime);
timelib_unixtime2local(dateobj->time, (timelib_sll)timestamp);
timelib_update_ts(dateobj->time, NULL);
@@ -3754,6 +3814,9 @@
if (dateobj->time->tz_info) {
clone->tz_info = dateobj->time->tz_info;
}
+ if (dateobj->immutable) {
+ dpobj->immutable = 1;
+ }
dpobj->start = clone;
/* interval */
Index: ext/date/php_date.h
===================================================================
--- ext/date/php_date.h (revision 305965)
+++ ext/date/php_date.h (working copy)
@@ -111,6 +111,7 @@
zend_object std;
timelib_time *time;
HashTable *props;
+ int immutable;
};
struct _php_timezone_obj {
@@ -142,6 +143,7 @@
timelib_time *current;
timelib_time *end;
timelib_rel_time *interval;
+ int immutable;
int recurrences;
int initialized;
int include_start_date;
@@ -180,7 +182,7 @@
/* Functions for creating DateTime objects, and initializing them from a string */
PHPAPI zval *php_date_instantiate(zend_class_entry *pce, zval *object TSRMLS_DC);
-PHPAPI int php_date_initialize(php_date_obj *dateobj, /*const*/ char *time_str, int time_str_len, char *format, zval *timezone_object, int ctor TSRMLS_DC);
+PHPAPI int php_date_initialize(php_date_obj *dateobj, /*const*/ char *time_str, int time_str_len, char *format, zval *timezone_object, zend_bool immutableFlag, int ctor TSRMLS_DC);
#endif /* PHP_DATE_H */
--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php