Skip to content

Commit

Permalink
Implement DatePeriod::createFromISO8601String()
Browse files Browse the repository at this point in the history
  • Loading branch information
kocsismate committed Jul 18, 2023
1 parent 1057cce commit 9c7c0a0
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 47 deletions.
127 changes: 84 additions & 43 deletions ext/date/php_date.c
Expand Up @@ -4843,6 +4843,88 @@ static bool date_period_initialize(timelib_time **st, timelib_time **et, timelib
return retval;
} /* }}} */

static bool date_period_init_iso8601_string(php_period_obj *dpobj, char *isostr, size_t isostr_len, zend_long options, zend_long *recurrences) {
if (!date_period_initialize(&(dpobj->start), &(dpobj->end), &(dpobj->interval), recurrences, isostr, isostr_len)) {
return false;
}

if (dpobj->start == NULL) {
zend_string *func = get_active_function_or_method_name();
zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): ISO interval must contain a start date, \"%s\" given", ZSTR_VAL(func), isostr);
zend_string_release(func);
return false;
}
if (dpobj->interval == NULL) {
zend_string *func = get_active_function_or_method_name();
zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): ISO interval must contain an interval, \"%s\" given", ZSTR_VAL(func), isostr);
zend_string_release(func);
return false;
}
if (dpobj->end == NULL && recurrences == 0) {
zend_string *func = get_active_function_or_method_name();
zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): ISO interval must contain an end date or a recurrence count, \"%s\" given", ZSTR_VAL(func), isostr);
zend_string_release(func);
return false;
}

if (dpobj->start) {
timelib_update_ts(dpobj->start, NULL);
}
if (dpobj->end) {
timelib_update_ts(dpobj->end, NULL);
}
dpobj->start_ce = date_ce_date;

return true;
}

static bool date_period_init_finish(php_period_obj *dpobj, zend_long options, zend_long recurrences) {
if (dpobj->end == NULL && recurrences < 1) {
zend_string *func = get_active_function_or_method_name();
zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): Recurrence count must be greater than 0", ZSTR_VAL(func));
zend_string_release(func);
return false;
}

/* options */
dpobj->include_start_date = !(options & PHP_DATE_PERIOD_EXCLUDE_START_DATE);
dpobj->include_end_date = options & PHP_DATE_PERIOD_INCLUDE_END_DATE;

/* recurrrences */
dpobj->recurrences = recurrences + dpobj->include_start_date + dpobj->include_end_date;

dpobj->initialized = 1;

initialize_date_period_properties(dpobj);

return true;
}

PHP_METHOD(DatePeriod, createFromISO8601String)
{
php_period_obj *dpobj;
zend_long recurrences = 0, options = 0;
char *isostr = NULL;
size_t isostr_len = 0;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &isostr, &isostr_len, &options) == FAILURE) {
RETURN_THROWS();
}

object_init_ex(return_value, execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_period);
dpobj = Z_PHPPERIOD_P(return_value);

dpobj->current = NULL;

if (!date_period_init_iso8601_string(dpobj, isostr, isostr_len, options, &recurrences)) {
RETURN_THROWS();
}

if (!date_period_init_finish(dpobj, options, recurrences)) {
RETURN_THROWS();
}
}

/* {{{ Creates new DatePeriod object. */
PHP_METHOD(DatePeriod, __construct)
{
Expand All @@ -4867,36 +4949,9 @@ PHP_METHOD(DatePeriod, __construct)
dpobj->current = NULL;

if (isostr) {
if (!date_period_initialize(&(dpobj->start), &(dpobj->end), &(dpobj->interval), &recurrences, isostr, isostr_len)) {
RETURN_THROWS();
}

if (dpobj->start == NULL) {
zend_string *func = get_active_function_or_method_name();
zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): ISO interval must contain a start date, \"%s\" given", ZSTR_VAL(func), isostr);
zend_string_release(func);
RETURN_THROWS();
}
if (dpobj->interval == NULL) {
zend_string *func = get_active_function_or_method_name();
zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): ISO interval must contain an interval, \"%s\" given", ZSTR_VAL(func), isostr);
zend_string_release(func);
if (!date_period_init_iso8601_string(dpobj, isostr, isostr_len, options, &recurrences)) {
RETURN_THROWS();
}
if (dpobj->end == NULL && recurrences == 0) {
zend_string *func = get_active_function_or_method_name();
zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): ISO interval must contain an end date or a recurrence count, \"%s\" given", ZSTR_VAL(func), isostr);
zend_string_release(func);
RETURN_THROWS();
}

if (dpobj->start) {
timelib_update_ts(dpobj->start, NULL);
}
if (dpobj->end) {
timelib_update_ts(dpobj->end, NULL);
}
dpobj->start_ce = date_ce_date;
} else {
/* init */
php_interval_obj *intobj = Z_PHPINTERVAL_P(interval);
Expand Down Expand Up @@ -4925,23 +4980,9 @@ PHP_METHOD(DatePeriod, __construct)
}
}

if (dpobj->end == NULL && recurrences < 1) {
zend_string *func = get_active_function_or_method_name();
zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): Recurrence count must be greater than 0", ZSTR_VAL(func));
zend_string_release(func);
if (!date_period_init_finish(dpobj, options, recurrences)) {
RETURN_THROWS();
}

/* options */
dpobj->include_start_date = !(options & PHP_DATE_PERIOD_EXCLUDE_START_DATE);
dpobj->include_end_date = options & PHP_DATE_PERIOD_INCLUDE_END_DATE;

/* recurrrences */
dpobj->recurrences = recurrences + dpobj->include_start_date + dpobj->include_end_date;

dpobj->initialized = 1;

initialize_date_period_properties(dpobj);
}
/* }}} */

Expand Down
2 changes: 2 additions & 0 deletions ext/date/php_date.stub.php
Expand Up @@ -714,6 +714,8 @@ class DatePeriod implements IteratorAggregate
/** @readonly */
public bool $include_end_date;

public static function createFromISO8601String(string $specification, int $options = 0): static {}

/**
* @param DateTimeInterface|string $start
* @param DateInterval|int $interval
Expand Down
9 changes: 8 additions & 1 deletion ext/date/php_date_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

@@ -0,0 +1,69 @@
--TEST--
Test the static return type of DatePeriod::createFromISO8601String()
--FILE--
<?php

class MyDatePeriod extends DatePeriod {}

var_dump(MyDatePeriod::createFromISO8601String("R4/2012-07-01T00:00:00Z/P7D"));

try {
MyDatePeriod::createFromISO8601String("R4/2012-07-01T00:/P7D");
} catch (DateMalformedPeriodStringException $e) {
echo $e->getMessage() . "\n";
}

try {
MyDatePeriod::createFromISO8601String("R4/2012-07-01T00:00:00Z");
} catch (DateMalformedPeriodStringException $e) {
echo $e->getMessage() . "\n";
}

?>
--EXPECT--
object(MyDatePeriod)#1 (7) {
["start"]=>
object(DateTime)#2 (3) {
["date"]=>
string(26) "2012-07-01 00:00:00.000000"
["timezone_type"]=>
int(1)
["timezone"]=>
string(6) "+00:00"
}
["current"]=>
NULL
["end"]=>
NULL
["interval"]=>
object(DateInterval)#3 (10) {
["y"]=>
int(0)
["m"]=>
int(0)
["d"]=>
int(7)
["h"]=>
int(0)
["i"]=>
int(0)
["s"]=>
int(0)
["f"]=>
float(0)
["invert"]=>
int(0)
["days"]=>
bool(false)
["from_string"]=>
bool(false)
}
["recurrences"]=>
int(5)
["include_start_date"]=>
bool(true)
["include_end_date"]=>
bool(false)
}
Unknown or bad format (R4/2012-07-01T00:/P7D)
DatePeriod::createFromISO8601String(): ISO interval must contain an interval, "R4/2012-07-01T00:00:00Z" given
2 changes: 1 addition & 1 deletion ext/date/tests/DatePeriod_serialize-001.phpt
Expand Up @@ -4,7 +4,7 @@ Test DatePeriod::__serialize and DatePeriod::__unserialize (ISO String)
<?php
date_default_timezone_set("Europe/London");

$d = new DatePeriod('R4/2012-07-01T00:00:00Z/P7D');
$d = DatePeriod::createFromISO8601String('R4/2012-07-01T00:00:00Z/P7D');
echo "Original object:\n";
var_dump($d);

Expand Down
4 changes: 4 additions & 0 deletions ext/date/tests/DatePeriod_wrong_arguments.phpt
Expand Up @@ -11,6 +11,9 @@ echo get_class($dp) == 'DatePeriod' ? "OK\n" : "FAIL\n";
$dp = new DatePeriod("R4/2012-07-01T00:00:00Z/P7D");
echo get_class($dp) == 'DatePeriod' ? "OK\n" : "FAIL\n";

$dp = DatePeriod::createFromISO8601String("R4/2012-07-01T00:00:00Z/P7D");
echo get_class($dp) == 'DatePeriod' ? "OK\n" : "FAIL\n";

try {
$dp = new DatePeriod("2023-01-13 17:24:58", DateInterval::createFromDateString("tomorrow"), 4);
echo "OK\n";
Expand All @@ -22,4 +25,5 @@ try {
OK
OK
OK
OK
TypeError: DatePeriod::__construct() accepts (DateTimeInterface, DateInterval, int [, int]), or (DateTimeInterface, DateInterval, DateTime [, int]), or (string [, int]) as arguments
7 changes: 7 additions & 0 deletions ext/date/tests/bug44562.phpt
Expand Up @@ -10,6 +10,12 @@ try {
echo $e::class, ': ', $e->getMessage(), "\n";
}

try {
DatePeriod::createFromISO8601String('2D');
} catch (Exception $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}

$begin = new DateTime( "2008-07-20T22:44:53+0200" );
$interval = DateInterval::createFromDateString( "1 day" );

Expand All @@ -22,6 +28,7 @@ foreach ( $dp as $d )
?>
--EXPECT--
DateMalformedPeriodStringException: Unknown or bad format (2D)
DateMalformedPeriodStringException: Unknown or bad format (2D)
string(24) "2008-07-20T22:44:53+0200"
string(24) "2008-07-21T22:44:53+0200"
string(24) "2008-07-22T22:44:53+0200"
Expand Down
2 changes: 1 addition & 1 deletion ext/date/tests/bug46874.phpt
Expand Up @@ -2,7 +2,7 @@
Bug #46874 (DatePeriod not resetting after foreach loop)
--FILE--
<?php
$dp = new DatePeriod('R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M');
$dp = DatePeriod::createFromISO8601String('R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M');

foreach ($dp as $date) {
echo $date->format("Y-m-d H:i:s\n");
Expand Down
14 changes: 14 additions & 0 deletions ext/date/tests/date_interval_bad_format_leak.phpt
Expand Up @@ -15,14 +15,28 @@ try {
echo $e::class, ': ', $e->getMessage(), "\n";
}

try {
DatePeriod::createFromISO8601String('P3"D');
} catch (Exception $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}

try {
new DatePeriod('2008-03-01T12:00:00Z1');
} catch (Exception $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}

try {
DatePeriod::createFromISO8601String('2008-03-01T12:00:00Z1');
} catch (Exception $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}

?>
--EXPECT--
DateMalformedIntervalStringException: Unknown or bad format (P3"D)
DateMalformedPeriodStringException: Unknown or bad format (P3"D)
DateMalformedPeriodStringException: Unknown or bad format (P3"D)
DateMalformedPeriodStringException: Unknown or bad format (2008-03-01T12:00:00Z1)
DateMalformedPeriodStringException: Unknown or bad format (2008-03-01T12:00:00Z1)
22 changes: 21 additions & 1 deletion ext/date/tests/date_period_bad_iso_format.phpt
Expand Up @@ -9,21 +9,41 @@ try {
echo $e::class, ': ', $e->getMessage(), "\n";
}

try {
DatePeriod::createFromISO8601String("R4");
} catch (Exception $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}

try {
new DatePeriod("R4/2012-07-01T00:00:00Z");
} catch (Exception $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}

try {
DatePeriod::createFromISO8601String("R4/2012-07-01T00:00:00Z");
} catch (Exception $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}

try {
new DatePeriod("2012-07-01T00:00:00Z/P7D");
} catch (Exception $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}

try {
DatePeriod::createFromISO8601String("2012-07-01T00:00:00Z/P7D");
} catch (Exception $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}

?>
--EXPECT--
DateMalformedPeriodStringException: DatePeriod::__construct(): ISO interval must contain a start date, "R4" given
DateMalformedPeriodStringException: DatePeriod::createFromISO8601String(): ISO interval must contain a start date, "R4" given
DateMalformedPeriodStringException: DatePeriod::__construct(): ISO interval must contain an interval, "R4/2012-07-01T00:00:00Z" given
DateMalformedPeriodStringException: DatePeriod::__construct(): ISO interval must contain an end date or a recurrence count, "2012-07-01T00:00:00Z/P7D" given
DateMalformedPeriodStringException: DatePeriod::createFromISO8601String(): ISO interval must contain an interval, "R4/2012-07-01T00:00:00Z" given
DateMalformedPeriodStringException: DatePeriod::__construct(): Recurrence count must be greater than 0
DateMalformedPeriodStringException: DatePeriod::createFromISO8601String(): Recurrence count must be greater than 0

0 comments on commit 9c7c0a0

Please sign in to comment.