Skip to content

Commit 9c7c0a0

Browse files
committedJul 18, 2023
Implement DatePeriod::createFromISO8601String()
1 parent 1057cce commit 9c7c0a0

10 files changed

+211
-47
lines changed
 

‎ext/date/php_date.c

+84-43
Original file line numberDiff line numberDiff line change
@@ -4843,6 +4843,88 @@ static bool date_period_initialize(timelib_time **st, timelib_time **et, timelib
48434843
return retval;
48444844
} /* }}} */
48454845

4846+
static bool date_period_init_iso8601_string(php_period_obj *dpobj, char *isostr, size_t isostr_len, zend_long options, zend_long *recurrences) {
4847+
if (!date_period_initialize(&(dpobj->start), &(dpobj->end), &(dpobj->interval), recurrences, isostr, isostr_len)) {
4848+
return false;
4849+
}
4850+
4851+
if (dpobj->start == NULL) {
4852+
zend_string *func = get_active_function_or_method_name();
4853+
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);
4854+
zend_string_release(func);
4855+
return false;
4856+
}
4857+
if (dpobj->interval == NULL) {
4858+
zend_string *func = get_active_function_or_method_name();
4859+
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);
4860+
zend_string_release(func);
4861+
return false;
4862+
}
4863+
if (dpobj->end == NULL && recurrences == 0) {
4864+
zend_string *func = get_active_function_or_method_name();
4865+
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);
4866+
zend_string_release(func);
4867+
return false;
4868+
}
4869+
4870+
if (dpobj->start) {
4871+
timelib_update_ts(dpobj->start, NULL);
4872+
}
4873+
if (dpobj->end) {
4874+
timelib_update_ts(dpobj->end, NULL);
4875+
}
4876+
dpobj->start_ce = date_ce_date;
4877+
4878+
return true;
4879+
}
4880+
4881+
static bool date_period_init_finish(php_period_obj *dpobj, zend_long options, zend_long recurrences) {
4882+
if (dpobj->end == NULL && recurrences < 1) {
4883+
zend_string *func = get_active_function_or_method_name();
4884+
zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): Recurrence count must be greater than 0", ZSTR_VAL(func));
4885+
zend_string_release(func);
4886+
return false;
4887+
}
4888+
4889+
/* options */
4890+
dpobj->include_start_date = !(options & PHP_DATE_PERIOD_EXCLUDE_START_DATE);
4891+
dpobj->include_end_date = options & PHP_DATE_PERIOD_INCLUDE_END_DATE;
4892+
4893+
/* recurrrences */
4894+
dpobj->recurrences = recurrences + dpobj->include_start_date + dpobj->include_end_date;
4895+
4896+
dpobj->initialized = 1;
4897+
4898+
initialize_date_period_properties(dpobj);
4899+
4900+
return true;
4901+
}
4902+
4903+
PHP_METHOD(DatePeriod, createFromISO8601String)
4904+
{
4905+
php_period_obj *dpobj;
4906+
zend_long recurrences = 0, options = 0;
4907+
char *isostr = NULL;
4908+
size_t isostr_len = 0;
4909+
4910+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &isostr, &isostr_len, &options) == FAILURE) {
4911+
RETURN_THROWS();
4912+
}
4913+
4914+
object_init_ex(return_value, execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_period);
4915+
dpobj = Z_PHPPERIOD_P(return_value);
4916+
4917+
dpobj->current = NULL;
4918+
4919+
if (!date_period_init_iso8601_string(dpobj, isostr, isostr_len, options, &recurrences)) {
4920+
RETURN_THROWS();
4921+
}
4922+
4923+
if (!date_period_init_finish(dpobj, options, recurrences)) {
4924+
RETURN_THROWS();
4925+
}
4926+
}
4927+
48464928
/* {{{ Creates new DatePeriod object. */
48474929
PHP_METHOD(DatePeriod, __construct)
48484930
{
@@ -4867,36 +4949,9 @@ PHP_METHOD(DatePeriod, __construct)
48674949
dpobj->current = NULL;
48684950

48694951
if (isostr) {
4870-
if (!date_period_initialize(&(dpobj->start), &(dpobj->end), &(dpobj->interval), &recurrences, isostr, isostr_len)) {
4871-
RETURN_THROWS();
4872-
}
4873-
4874-
if (dpobj->start == NULL) {
4875-
zend_string *func = get_active_function_or_method_name();
4876-
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);
4877-
zend_string_release(func);
4878-
RETURN_THROWS();
4879-
}
4880-
if (dpobj->interval == NULL) {
4881-
zend_string *func = get_active_function_or_method_name();
4882-
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);
4883-
zend_string_release(func);
4952+
if (!date_period_init_iso8601_string(dpobj, isostr, isostr_len, options, &recurrences)) {
48844953
RETURN_THROWS();
48854954
}
4886-
if (dpobj->end == NULL && recurrences == 0) {
4887-
zend_string *func = get_active_function_or_method_name();
4888-
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);
4889-
zend_string_release(func);
4890-
RETURN_THROWS();
4891-
}
4892-
4893-
if (dpobj->start) {
4894-
timelib_update_ts(dpobj->start, NULL);
4895-
}
4896-
if (dpobj->end) {
4897-
timelib_update_ts(dpobj->end, NULL);
4898-
}
4899-
dpobj->start_ce = date_ce_date;
49004955
} else {
49014956
/* init */
49024957
php_interval_obj *intobj = Z_PHPINTERVAL_P(interval);
@@ -4925,23 +4980,9 @@ PHP_METHOD(DatePeriod, __construct)
49254980
}
49264981
}
49274982

4928-
if (dpobj->end == NULL && recurrences < 1) {
4929-
zend_string *func = get_active_function_or_method_name();
4930-
zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): Recurrence count must be greater than 0", ZSTR_VAL(func));
4931-
zend_string_release(func);
4983+
if (!date_period_init_finish(dpobj, options, recurrences)) {
49324984
RETURN_THROWS();
49334985
}
4934-
4935-
/* options */
4936-
dpobj->include_start_date = !(options & PHP_DATE_PERIOD_EXCLUDE_START_DATE);
4937-
dpobj->include_end_date = options & PHP_DATE_PERIOD_INCLUDE_END_DATE;
4938-
4939-
/* recurrrences */
4940-
dpobj->recurrences = recurrences + dpobj->include_start_date + dpobj->include_end_date;
4941-
4942-
dpobj->initialized = 1;
4943-
4944-
initialize_date_period_properties(dpobj);
49454986
}
49464987
/* }}} */
49474988

‎ext/date/php_date.stub.php

+2
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,8 @@ class DatePeriod implements IteratorAggregate
714714
/** @readonly */
715715
public bool $include_end_date;
716716

717+
public static function createFromISO8601String(string $specification, int $options = 0): static {}
718+
717719
/**
718720
* @param DateTimeInterface|string $start
719721
* @param DateInterval|int $interval

‎ext/date/php_date_arginfo.h

+8-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
--TEST--
2+
Test the static return type of DatePeriod::createFromISO8601String()
3+
--FILE--
4+
<?php
5+
6+
class MyDatePeriod extends DatePeriod {}
7+
8+
var_dump(MyDatePeriod::createFromISO8601String("R4/2012-07-01T00:00:00Z/P7D"));
9+
10+
try {
11+
MyDatePeriod::createFromISO8601String("R4/2012-07-01T00:/P7D");
12+
} catch (DateMalformedPeriodStringException $e) {
13+
echo $e->getMessage() . "\n";
14+
}
15+
16+
try {
17+
MyDatePeriod::createFromISO8601String("R4/2012-07-01T00:00:00Z");
18+
} catch (DateMalformedPeriodStringException $e) {
19+
echo $e->getMessage() . "\n";
20+
}
21+
22+
?>
23+
--EXPECT--
24+
object(MyDatePeriod)#1 (7) {
25+
["start"]=>
26+
object(DateTime)#2 (3) {
27+
["date"]=>
28+
string(26) "2012-07-01 00:00:00.000000"
29+
["timezone_type"]=>
30+
int(1)
31+
["timezone"]=>
32+
string(6) "+00:00"
33+
}
34+
["current"]=>
35+
NULL
36+
["end"]=>
37+
NULL
38+
["interval"]=>
39+
object(DateInterval)#3 (10) {
40+
["y"]=>
41+
int(0)
42+
["m"]=>
43+
int(0)
44+
["d"]=>
45+
int(7)
46+
["h"]=>
47+
int(0)
48+
["i"]=>
49+
int(0)
50+
["s"]=>
51+
int(0)
52+
["f"]=>
53+
float(0)
54+
["invert"]=>
55+
int(0)
56+
["days"]=>
57+
bool(false)
58+
["from_string"]=>
59+
bool(false)
60+
}
61+
["recurrences"]=>
62+
int(5)
63+
["include_start_date"]=>
64+
bool(true)
65+
["include_end_date"]=>
66+
bool(false)
67+
}
68+
Unknown or bad format (R4/2012-07-01T00:/P7D)
69+
DatePeriod::createFromISO8601String(): ISO interval must contain an interval, "R4/2012-07-01T00:00:00Z" given

‎ext/date/tests/DatePeriod_serialize-001.phpt

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Test DatePeriod::__serialize and DatePeriod::__unserialize (ISO String)
44
<?php
55
date_default_timezone_set("Europe/London");
66

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

‎ext/date/tests/DatePeriod_wrong_arguments.phpt

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ echo get_class($dp) == 'DatePeriod' ? "OK\n" : "FAIL\n";
1111
$dp = new DatePeriod("R4/2012-07-01T00:00:00Z/P7D");
1212
echo get_class($dp) == 'DatePeriod' ? "OK\n" : "FAIL\n";
1313

14+
$dp = DatePeriod::createFromISO8601String("R4/2012-07-01T00:00:00Z/P7D");
15+
echo get_class($dp) == 'DatePeriod' ? "OK\n" : "FAIL\n";
16+
1417
try {
1518
$dp = new DatePeriod("2023-01-13 17:24:58", DateInterval::createFromDateString("tomorrow"), 4);
1619
echo "OK\n";
@@ -22,4 +25,5 @@ try {
2225
OK
2326
OK
2427
OK
28+
OK
2529
TypeError: DatePeriod::__construct() accepts (DateTimeInterface, DateInterval, int [, int]), or (DateTimeInterface, DateInterval, DateTime [, int]), or (string [, int]) as arguments

‎ext/date/tests/bug44562.phpt

+7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ try {
1010
echo $e::class, ': ', $e->getMessage(), "\n";
1111
}
1212

13+
try {
14+
DatePeriod::createFromISO8601String('2D');
15+
} catch (Exception $e) {
16+
echo $e::class, ': ', $e->getMessage(), "\n";
17+
}
18+
1319
$begin = new DateTime( "2008-07-20T22:44:53+0200" );
1420
$interval = DateInterval::createFromDateString( "1 day" );
1521

@@ -22,6 +28,7 @@ foreach ( $dp as $d )
2228
?>
2329
--EXPECT--
2430
DateMalformedPeriodStringException: Unknown or bad format (2D)
31+
DateMalformedPeriodStringException: Unknown or bad format (2D)
2532
string(24) "2008-07-20T22:44:53+0200"
2633
string(24) "2008-07-21T22:44:53+0200"
2734
string(24) "2008-07-22T22:44:53+0200"

‎ext/date/tests/bug46874.phpt

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Bug #46874 (DatePeriod not resetting after foreach loop)
33
--FILE--
44
<?php
5-
$dp = new DatePeriod('R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M');
5+
$dp = DatePeriod::createFromISO8601String('R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M');
66

77
foreach ($dp as $date) {
88
echo $date->format("Y-m-d H:i:s\n");

‎ext/date/tests/date_interval_bad_format_leak.phpt

+14
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,28 @@ try {
1515
echo $e::class, ': ', $e->getMessage(), "\n";
1616
}
1717

18+
try {
19+
DatePeriod::createFromISO8601String('P3"D');
20+
} catch (Exception $e) {
21+
echo $e::class, ': ', $e->getMessage(), "\n";
22+
}
23+
1824
try {
1925
new DatePeriod('2008-03-01T12:00:00Z1');
2026
} catch (Exception $e) {
2127
echo $e::class, ': ', $e->getMessage(), "\n";
2228
}
2329

30+
try {
31+
DatePeriod::createFromISO8601String('2008-03-01T12:00:00Z1');
32+
} catch (Exception $e) {
33+
echo $e::class, ': ', $e->getMessage(), "\n";
34+
}
35+
2436
?>
2537
--EXPECT--
2638
DateMalformedIntervalStringException: Unknown or bad format (P3"D)
2739
DateMalformedPeriodStringException: Unknown or bad format (P3"D)
40+
DateMalformedPeriodStringException: Unknown or bad format (P3"D)
41+
DateMalformedPeriodStringException: Unknown or bad format (2008-03-01T12:00:00Z1)
2842
DateMalformedPeriodStringException: Unknown or bad format (2008-03-01T12:00:00Z1)

‎ext/date/tests/date_period_bad_iso_format.phpt

+21-1
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,41 @@ try {
99
echo $e::class, ': ', $e->getMessage(), "\n";
1010
}
1111

12+
try {
13+
DatePeriod::createFromISO8601String("R4");
14+
} catch (Exception $e) {
15+
echo $e::class, ': ', $e->getMessage(), "\n";
16+
}
17+
1218
try {
1319
new DatePeriod("R4/2012-07-01T00:00:00Z");
1420
} catch (Exception $e) {
1521
echo $e::class, ': ', $e->getMessage(), "\n";
1622
}
1723

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

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

36+
try {
37+
DatePeriod::createFromISO8601String("2012-07-01T00:00:00Z/P7D");
38+
} catch (Exception $e) {
39+
echo $e::class, ': ', $e->getMessage(), "\n";
40+
}
41+
2542
?>
2643
--EXPECT--
2744
DateMalformedPeriodStringException: DatePeriod::__construct(): ISO interval must contain a start date, "R4" given
45+
DateMalformedPeriodStringException: DatePeriod::createFromISO8601String(): ISO interval must contain a start date, "R4" given
2846
DateMalformedPeriodStringException: DatePeriod::__construct(): ISO interval must contain an interval, "R4/2012-07-01T00:00:00Z" given
29-
DateMalformedPeriodStringException: DatePeriod::__construct(): ISO interval must contain an end date or a recurrence count, "2012-07-01T00:00:00Z/P7D" given
47+
DateMalformedPeriodStringException: DatePeriod::createFromISO8601String(): ISO interval must contain an interval, "R4/2012-07-01T00:00:00Z" given
48+
DateMalformedPeriodStringException: DatePeriod::__construct(): Recurrence count must be greater than 0
49+
DateMalformedPeriodStringException: DatePeriod::createFromISO8601String(): Recurrence count must be greater than 0

0 commit comments

Comments
 (0)
Please sign in to comment.