Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

date_diff() bug #65768 #512

Closed
wants to merge 4 commits into from

4 participants

@nikita2206

Fixed the bug when DateTimeInterface::diff() (and date_diff()) was declared as diff(DateTimeInterface, DateTimeInterface), but the implementations were always diff(DateTime, DateTime), even when working with DateTimeImmutable (which gave you a fatal in any case)

Here's a link to bugtracker https://bugs.php.net/bug.php?id=65768

@nikita2206 nikita2206 Fixed the bug when DateTimeInterface::diff() was declared as diff(Dat…
…eTimeInterface, DateTimeInterface), but the implementations were always diff(DateTime, DateTime), even when working with DateTimeImmutable (which gave you a fatal in any case)
a76fa13
@nikic
Owner

Would you mind adding a test for this change?

@glenjamin

Does this fix calling on both DateTime and DateTimeImmutable instances?

@nikita2206

@nikic can't do it now (from home), will do tomorrow
@glenjamin Yes, as docs say, you can pass any DateTimeInterface instance

@nikita2206

@nikic Hmm, while writing tests I just came to a conclusion that we can't use date_ce_interface to make sure the object is suitable for us here, because then you can do something like

$d = new DateTimeImmutable("now");

class dt implements DateTimeInterface {
  function diff($d, $a = false){}
  function format($f){}
  function getOffset(){}
  function getTimestamp(){}
  function getTimezone(){}
  function __wakeup(){}
}
$d2 = new dt();

$d->diff($d2);

and it will break things badly...
So, I assume we should check if this object is a child of DateTime or DateTimeImmutable. But it can become a mess...
Maybe we can go for AbstractDateTime? Or should I just create a macros, say DATE_CHECK_INTERNAL?

@nikita2206

@nikic But on the other side it would be reasonable to support diffing even fully user-defined dates... Just, if passed object is not an internal date, then we call its diff method and pass it the other object (which can be internal or not, we shouldn't care about that from that point). Yeah, I will try to implement it.

@glenjamin

The problem arises because while diff checks for DateTimeInterface, it is implemented using something that is not specified in the interface.

There'd be a performance penalty to creating a timelib struct using only the public interface, but perhaps you could have DateTime and DateTimeImmutable special-cased, otherwise use getTimestamp to create a timelib of the argument object, then proceeed as normal?

@nikita2206

@glenjamin
There's always some trade-off, as I see it:
First, we could call user-defined diff, as I said before. But it is bad because: we are calling user-space function from engine - that's not good I suppose, also we must take care of the returned value (maybe typecheck), and we will grow call-stack...
And second, we could convert non-internal dates to timelib_time, as you suggested, but there's one thing I think will mess us up - getTimestamp returns long (php's long), which can be of size of 32-bit on 32-bit systems, while timelib_time's sse member is of type signed long long. So I suppose we can't use timestamps here. Then we could use format method here - but the DateTimeInterface doesn't explicitly declare how it should work. I mean - we use i for minutes and m for months, but what if user, who implemented DateTimeInterface, wants to use m for minutes and i for months?

@nikic
Owner

@nikita2206 That issues doesn't seem specific to date_diff, but also applies to all the other methods accepting date_ce_interface. To avoid special handling all over the place I'd suggest to disallow implementation of DateTimeInterface by userland classes. So add a handler along the lines of:

static int implement_date_interface(zend_class_entry *interface, zend_class_entry *ce TSRMLS_DC)
{
    if (ce->type == ZEND_USER_CLASS) {
        zend_error(E_ERROR, "DateTimeImmutable can't be implemented by user classes");
    }

    return SUCCESS;
}

Might need a bit of tweaking (maybe it needs to check whether a parent if DateTime/DateTimeImmutable instead, don't remember the invokation point just now).

@nikita2206

@nikic Hi, I commited a test-case and made DateTimeInterface not implementable, as you said. I also have an implementation of what I was talking about a few posts above, actually there are pretty few of functions that take DateTimeInterface as an argument

@nikita2206

Btw, travis reported failure due to date timezone not being set. How we usually deal with this kind of fails?

@nikita2206

I fixed a test, now it should pass on travis.

@TazeTSchnitzel

DateTimeInterface not being implementable, shouldn't that go in UPGRADING or something like that? (Sorry if I'm confused here and wrong)

@nikita2206

@tazetschnitzel yeah, I think it should. Didn't think about it at the time when I committed it, I will add this note in UPGRADING file tomorrow

@nikic
Owner

Merged via 5f09944 for 5.5.8, thanks for the patch!

Could you please close this PR yourself? Our tool for that seems to be currently broken...

@nikita2206

Ok, thanks!

@nikita2206 nikita2206 closed this
@nikic
Owner

One more note: Usually it's best to do code changes with --enable-maintainer-zts, otherwise you'll likely forget a TSRMLS_CC somewhere. E.g. in this case instanceof_function needed one.

Maybe we should make travis builds in zts to catch this.

@nikita2206

@nikic oops, ok I will use it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 29, 2013
  1. @nikita2206

    Fixed the bug when DateTimeInterface::diff() was declared as diff(Dat…

    nikita2206 authored
    …eTimeInterface, DateTimeInterface), but the implementations were always diff(DateTime, DateTime), even when working with DateTimeImmutable (which gave you a fatal in any case)
Commits on Oct 30, 2013
  1. @nikita2206
Commits on Nov 18, 2013
  1. @nikita2206
Commits on Nov 25, 2013
  1. @nikita2206
This page is out of date. Refresh to see the latest.
Showing with 52 additions and 4 deletions.
  1. +16 −4 ext/date/php_date.c
  2. +36 −0 tests/classes/bug65768.phpt
View
20 ext/date/php_date.c
@@ -1981,12 +1981,24 @@ zend_object_iterator *date_object_period_get_iterator(zend_class_entry *ce, zval
return (zend_object_iterator*)iterator;
}
+static int implement_date_interface_handler(zend_class_entry *interface, zend_class_entry *implementor TSRMLS_DC)
+{
+ if (implementor->type == ZEND_USER_CLASS && !instanceof_function(implementor, date_ce_date) &&
+ !instanceof_function(implementor, date_ce_immutable)) {
+
+ zend_error(E_ERROR, "DateTimeInterface can't be implemented by user classes");
+ }
+
+ return SUCCESS;
+}
+
static void date_register_classes(TSRMLS_D)
{
zend_class_entry ce_date, ce_immutable, ce_timezone, ce_interval, ce_period, ce_interface;
INIT_CLASS_ENTRY(ce_interface, "DateTimeInterface", date_funcs_interface);
date_ce_interface = zend_register_internal_interface(&ce_interface TSRMLS_CC);
+ date_ce_interface->interface_gets_implemented = implement_date_interface_handler;
INIT_CLASS_ENTRY(ce_date, "DateTime", date_funcs_date);
ce_date.create_object = date_object_new_date;
@@ -3603,13 +3615,13 @@ PHP_FUNCTION(date_diff)
php_interval_obj *interval;
long absolute = 0;
- if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "OO|l", &object1, date_ce_date, &object2, date_ce_date, &absolute) == FAILURE) {
+ if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "OO|l", &object1, date_ce_interface, &object2, date_ce_interface, &absolute) == FAILURE) {
RETURN_FALSE;
}
dateobj1 = (php_date_obj *) zend_object_store_get_object(object1 TSRMLS_CC);
dateobj2 = (php_date_obj *) zend_object_store_get_object(object2 TSRMLS_CC);
- DATE_CHECK_INITIALIZED(dateobj1->time, DateTime);
- DATE_CHECK_INITIALIZED(dateobj2->time, DateTime);
+ DATE_CHECK_INITIALIZED(dateobj1->time, DateTimeInterface);
+ DATE_CHECK_INITIALIZED(dateobj2->time, DateTimeInterface);
timelib_update_ts(dateobj1->time, NULL);
timelib_update_ts(dateobj2->time, NULL);
@@ -4387,7 +4399,7 @@ PHP_METHOD(DatePeriod, __construct)
zend_replace_error_handling(EH_THROW, NULL, &error_handling TSRMLS_CC);
if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "OOl|l", &start, date_ce_interface, &interval, date_ce_interval, &recurrences, &options) == FAILURE) {
- if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "OOO|l", &start, date_ce_interface, &interval, date_ce_interval, &end, date_ce_date, &options) == FAILURE) {
+ if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "OOO|l", &start, date_ce_interface, &interval, date_ce_interval, &end, date_ce_interface, &options) == FAILURE) {
if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &isostr, &isostr_len, &options) == FAILURE) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "This constructor accepts either (DateTimeInterface, DateInterval, int) OR (DateTimeInterface, DateInterval, DateTime) OR (string) as arguments.");
zend_restore_error_handling(&error_handling TSRMLS_CC);
View
36 tests/classes/bug65768.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Bug #65768: date_diff accepts only DateTime instance even though docs say about DateTimeInterface
+--INI--
+date.timezone=Europe/London
+--FILE--
+<?php
+
+$dt1 = new DateTime("2010-10-20");
+$dti1 = new DateTimeImmutable("2010-10-25");
+$dti2 = new DateTimeImmutable("2010-10-28");
+
+$diff1 = $dt1->diff($dti1);
+echo $diff1->y, " ", $diff1->m, " ", $diff1->d, " ",
+ $diff1->h, " ", $diff1->i, " ", $diff1->s, "\n";
+
+$diff2 = $dti1->diff($dti2);
+echo $diff2->y, " ", $diff2->m, " ", $diff2->d, " ",
+ $diff2->h, " ", $diff2->i, " ", $diff2->s, "\n";
+
+$diff3 = date_diff($dt1, $dti2);
+echo $diff3->y, " ", $diff3->m, " ", $diff3->d, " ",
+ $diff3->h, " ", $diff3->i, " ", $diff3->s, "\n";
+
+class cdt1 extends DateTime implements DateTimeInterface {}
+
+class cdt2 extends DateTimeImmutable implements DateTimeInterface {}
+
+class cdt3 implements DateTimeInterface {}
+
+?>
+--EXPECTF--
+0 0 5 0 0 0
+0 0 3 0 0 0
+0 0 8 0 0 0
+
+Fatal error: DateTimeInterface can't be implemented by user classes in %sbug65768.php on line %d
Something went wrong with that request. Please try again.