Skip to content

PHPC-536: UTCDateTime construction from DateTime or current time #336

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 29, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions phongo_compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
# error Unsupported architecture (integers are neither 32-bit nor 64-bit)
#endif
# define SIZEOF_PHONGO_LONG SIZEOF_ZEND_LONG
# define phongo_str(str) str->val
# define phongo_str(str) (str)->val
# define phongo_create_object_retval zend_object*
# define PHONGO_ALLOC_OBJECT_T(_obj_t, _class_type) (_obj_t *)ecalloc(1, sizeof(_obj_t)+zend_object_properties_size(_class_type))
# define PHONGO_TSRMLS_FETCH_FROM_CTX(user_data)
Expand Down Expand Up @@ -164,7 +164,7 @@
# define ADD_ASSOC_NULL_EX(_zv, _key) add_assoc_null_ex(_zv, ZEND_STRS(_key));
# define ADD_ASSOC_BOOL_EX(_zv, _key, _value) add_assoc_bool_ex(_zv, ZEND_STRS(_key), _value);
# define ADD_INDEX_STRINGL(_zv, _ind, _value, _len) add_index_stringl(_zv, _ind, _value, _len, 0);
# define Z_PHPDATE_P(object) zend_object_store_get_object(object TSRMLS_CC)
# define Z_PHPDATE_P(object) ((php_date_obj*)zend_object_store_get_object(object TSRMLS_CC))
# define Z_ISUNDEF(x) !x
# define phongo_free_object_arg void
# define phongo_zpp_char_len int
Expand Down
54 changes: 51 additions & 3 deletions src/BSON/UTCDateTime.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,16 @@
#include <php_ini.h>
#include <ext/standard/info.h>
#include <Zend/zend_interfaces.h>
#include <ext/date/php_date.h>
#include <ext/spl/spl_iterators.h>
#include <ext/date/php_date.h>
/* Our Compatability header */
#include "phongo_compat.h"

#ifdef PHP_WIN32
#include "win32/time.h"
#endif

/* Our stuffz */
#include "php_phongo.h"
#include "php_bson.h"
Expand All @@ -47,17 +52,60 @@ PHONGO_API zend_class_entry *php_phongo_utcdatetime_ce;

zend_object_handlers php_phongo_handler_utcdatetime;

/* {{{ proto BSON\UTCDateTime UTCDateTime::__construct(integer $milliseconds)
static void php_phongo_utcdatetime_init_from_current_time(php_phongo_utcdatetime_t *intern)
{
int64_t sec, usec;
struct timeval cur_time;

gettimeofday(&cur_time, NULL);
sec = cur_time.tv_sec;
usec = cur_time.tv_usec;

intern->milliseconds = (sec * 1000) + (usec / 1000);
}

static void php_phongo_utcdatetime_init_from_date(php_phongo_utcdatetime_t *intern, php_date_obj *datetime_obj)
{
int64_t sec, usec;

/* The following assignments use the same logic as date_format() in php_date.c */
sec = datetime_obj->time->sse;
usec = (int64_t) floor(datetime_obj->time->f * 1000000 + 0.5);

intern->milliseconds = (sec * 1000) + (usec / 1000);
}

/* {{{ proto BSON\UTCDateTime UTCDateTime::__construct([integer|DateTimeInterface $milliseconds = null])
Construct a new UTCDateTime */
PHP_METHOD(UTCDateTime, __construct)
{
php_phongo_utcdatetime_t *intern;
zend_error_handling error_handling;

zend_error_handling error_handling;
zval *datetime = NULL;

zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling TSRMLS_CC);
intern = Z_UTCDATETIME_OBJ_P(getThis());

if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "|o!", &datetime) == SUCCESS) {
if (datetime == NULL) {
php_phongo_utcdatetime_init_from_current_time(intern);
} else if (instanceof_function(Z_OBJCE_P(datetime), php_date_get_date_ce() TSRMLS_CC)) {
php_phongo_utcdatetime_init_from_date(intern, Z_PHPDATE_P(datetime));
#if PHP_VERSION_ID >= 50500
} else if (instanceof_function(Z_OBJCE_P(datetime), php_date_get_immutable_ce() TSRMLS_CC)) {
php_phongo_utcdatetime_init_from_date(intern, Z_PHPDATE_P(datetime));
#endif
} else {
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC,
"Expected instance of DateTimeInterface, %s given",
phongo_str(Z_OBJCE_P(datetime)->name)
);
}

zend_restore_error_handling(&error_handling TSRMLS_CC);
return;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if you now pass it in as a non datetime class, it will not throw an error/exception.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still end up with an InvalidArgumentException:

MongoDB\BSON\UTCDateTime::__construct() expects parameter 1 to be long, object given

In the HHVM driver, you end up casting the argument to an integer if it's neither null nor a DateTime instance. Would you like to intercept other objects and throw a special message?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with "Expected instance of DateTimeInterface, $classname given". We technically check for an instance of DateTime and DateTimeImmutable (on 5.5+); however, DateTimeInterface cannot be implemented by userland classes so the message is still accurate.

#if SIZEOF_PHONGO_LONG == 8
{
phongo_long milliseconds;
Expand Down
30 changes: 30 additions & 0 deletions tests/bson/bson-utcdatetime-004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
BSON BSON\UTCDateTime constructor defaults to current time
--SKIPIF--
<?php require __DIR__ . "/../utils/basic-skipif.inc"?>
--FILE--
<?php
require_once __DIR__ . "/../utils/basic.inc";

$tests = [
new MongoDB\BSON\UTCDateTime,
new MongoDB\BSON\UTCDateTime(null),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "null" should just be coerced to 0, and not it being empty. Hence, the milliseconds should end up being 0 (e.g., the Epoch)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current behavior is consistent with your HHVM implementation. Note that DateTime's constructor also defaults to the current time if null is provided. I say we keep this behavior and document it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

];

foreach ($tests as $test) {
var_dump($test);
}

?>
===DONE===
<?php exit(0); ?>
--EXPECTF--
object(%SBSON\UTCDateTime)#%d (%d) {
["milliseconds"]=>
int(%d)
}
object(%SBSON\UTCDateTime)#%d (%d) {
["milliseconds"]=>
int(%d)
}
===DONE===
40 changes: 40 additions & 0 deletions tests/bson/bson-utcdatetime-005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--TEST--
BSON BSON\UTCDateTime construction from DateTime
--SKIPIF--
<?php require __DIR__ . "/../utils/basic-skipif.inc"?>
--FILE--
<?php
require_once __DIR__ . "/../utils/basic.inc";

$tests = [
new DateTime(),
new DateTime('@1215282385'),
new DateTime('2011-01-01T15:03:01.012345Z'),
new DateTime('2050-11-12T13:14:15.999999Z'),
];

foreach ($tests as $test) {
var_dump(new MongoDB\BSON\UTCDateTime($test));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should try to round trip them back with toDateTime as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a good idea. Let's get #338 merged first in that case, since it fixes toDateTime() and the expected output.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

}

?>
===DONE===
<?php exit(0); ?>
--EXPECTF--
object(%SBSON\UTCDateTime)#%d (%d) {
["milliseconds"]=>
int(%d000)
}
object(%SBSON\UTCDateTime)#%d (%d) {
["milliseconds"]=>
int(1215282385000)
}
object(%SBSON\UTCDateTime)#%d (%d) {
["milliseconds"]=>
int(1293894181012)
}
object(%SBSON\UTCDateTime)#%d (%d) {
["milliseconds"]=>
int(2551871655999)
}
===DONE===
41 changes: 41 additions & 0 deletions tests/bson/bson-utcdatetime-006.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--TEST--
BSON BSON\UTCDateTime construction from DateTimeImmutable
--SKIPIF--
<?php if (!version_compare(phpversion(), "5.5", '>=')) echo "skip >= PHP 5.5 needed\n"; ?>
<?php require __DIR__ . "/../utils/basic-skipif.inc"?>
--FILE--
<?php
require_once __DIR__ . "/../utils/basic.inc";

$tests = [
new DateTimeImmutable(),
new DateTimeImmutable('@1215282385'),
new DateTimeImmutable('2011-01-01T15:03:01.012345Z'),
new DateTimeImmutable('2050-11-12T13:14:15.999999Z'),
];

foreach ($tests as $test) {
var_dump(new MongoDB\BSON\UTCDateTime($test));
}

?>
===DONE===
<?php exit(0); ?>
--EXPECTF--
object(%SBSON\UTCDateTime)#%d (%d) {
["milliseconds"]=>
int(%d000)
}
object(%SBSON\UTCDateTime)#%d (%d) {
["milliseconds"]=>
int(1215282385000)
}
object(%SBSON\UTCDateTime)#%d (%d) {
["milliseconds"]=>
int(1293894181012)
}
object(%SBSON\UTCDateTime)#%d (%d) {
["milliseconds"]=>
int(2551871655999)
}
===DONE===
15 changes: 6 additions & 9 deletions tests/bson/bson-utcdatetime_error-001.phpt
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
--TEST--
BSON BSON\UTCDateTime #001 error
--INI--
date.timezone=America/Los_Angeles
BSON BSON\UTCDateTime requires object argument to implement DateTimeInterface
--SKIPIF--
<?php if (defined("HHVM_VERSION_ID")) exit("skip HHVM handles parameter parsing differently"); ?>
<?php require __DIR__ . "/../utils/basic-skipif.inc"?>
--FILE--
<?php
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might have to put this one back in I suppose, if we find that it's still different with HHVM.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it will be a problem. Your constructor accepts a mixed type and all validation is left to you, so it should be trivial to customize the exception message here.

require_once __DIR__ . "/../utils/basic.inc";

throws(function() {
$classname = BSON_NAMESPACE . "\\UTCDateTime";
new $classname;
}, "MongoDB\\Driver\\Exception\\InvalidArgumentException");
echo throws(function() {
new MongoDB\BSON\UTCDateTime(new stdClass);
}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n";

?>
===DONE===
<?php exit(0); ?>
--EXPECTF--
--EXPECT--
OK: Got MongoDB\Driver\Exception\InvalidArgumentException
Expected instance of DateTimeInterface, stdClass given
===DONE===