Skip to content

Commit 1486f52

Browse files
committedJul 18, 2023
Implement IntlGregorianCalendar::createFromDate() and IntlGregorianCalendar::createFromDateTime()
1 parent f236eb8 commit 1486f52

10 files changed

+250
-34
lines changed
 

‎ext/intl/calendar/calendar.stub.php

+4
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,10 @@ public function toDateTime(): DateTime|false {}
488488
/** @not-serializable */
489489
class IntlGregorianCalendar extends IntlCalendar
490490
{
491+
public static function createFromDate(int $year, int $month, int $dayOfMonth): static {}
492+
493+
public static function createFromDateTime(int $year, int $month, int $dayOfMonth, int $hour, int $minute, ?int $second = null): static {}
494+
491495
/**
492496
* @param DateTimeZone|IntlTimeZone|string|int|null $timezoneOrYear
493497
* @param string|int|null $localeOrMonth

‎ext/intl/calendar/calendar_arginfo.h

+39-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎ext/intl/calendar/gregoriancalendar_methods.cpp

+115-27
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,45 @@ using icu::Locale;
3939
using icu::UnicodeString;
4040
using icu::StringPiece;
4141

42+
#define ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(argument, zpp_arg_position) \
43+
if (UNEXPECTED(argument < INT32_MIN || argument > INT32_MAX)) { \
44+
zend_argument_value_error(zpp_arg_position, "must be between %d and %d", INT32_MIN, INT32_MAX); \
45+
RETURN_THROWS(); \
46+
}
47+
4248
static inline GregorianCalendar *fetch_greg(Calendar_object *co) {
4349
return (GregorianCalendar*)co->ucal;
4450
}
4551

52+
static bool set_gregorian_calendar_time_zone(GregorianCalendar *gcal, UErrorCode status)
53+
{
54+
if (U_FAILURE(status)) {
55+
intl_error_set(NULL, status,
56+
"IntlGregorianCalendar: Error creating ICU GregorianCalendar from date",
57+
0
58+
);
59+
60+
return false;
61+
}
62+
63+
timelib_tzinfo *tzinfo = get_timezone_info();
64+
UnicodeString tzstr = UnicodeString::fromUTF8(StringPiece(tzinfo->name));
65+
if (tzstr.isBogus()) {
66+
intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
67+
"IntlGregorianCalendar: Could not create UTF-8 string "
68+
"from PHP's default timezone name (see date_default_timezone_get())",
69+
0
70+
);
71+
72+
return false;
73+
}
74+
75+
TimeZone *tz = TimeZone::createTimeZone(tzstr);
76+
gcal->adoptTimeZone(tz);
77+
78+
return true;
79+
}
80+
4681
static void _php_intlgregcal_constructor_body(
4782
INTERNAL_FUNCTION_PARAMETERS, bool is_constructor, zend_error_handling *error_handling, bool *error_handling_replaced)
4883
{
@@ -135,11 +170,7 @@ static void _php_intlgregcal_constructor_body(
135170
} else {
136171
// From date/time (3, 5 or 6 arguments)
137172
for (int i = 0; i < variant; i++) {
138-
if (UNEXPECTED(largs[i] < INT32_MIN || largs[i] > INT32_MAX)) {
139-
zend_argument_value_error(getThis() ? (i-1) : i,
140-
"must be between %d and %d", INT32_MIN, INT32_MAX);
141-
RETURN_THROWS();
142-
}
173+
ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(largs[i], getThis() ? (i-1) : i);
143174
}
144175

145176
if (variant == 3) {
@@ -152,37 +183,18 @@ static void _php_intlgregcal_constructor_body(
152183
gcal = new GregorianCalendar((int32_t)largs[0], (int32_t)largs[1],
153184
(int32_t)largs[2], (int32_t)largs[3], (int32_t)largs[4], (int32_t)largs[5],
154185
status);
155-
}
156-
if (U_FAILURE(status)) {
157-
intl_error_set(NULL, status, "intlgregcal_create_instance: error "
158-
"creating ICU GregorianCalendar from date", 0);
159-
if (gcal) {
160-
delete gcal;
161-
}
162-
if (!is_constructor) {
163-
zval_ptr_dtor(return_value);
164-
RETVAL_NULL();
165-
}
166-
return;
186+
} else {
187+
ZEND_UNREACHABLE();
167188
}
168189

169-
timelib_tzinfo *tzinfo = get_timezone_info();
170-
UnicodeString tzstr = UnicodeString::fromUTF8(StringPiece(tzinfo->name));
171-
if (tzstr.isBogus()) {
172-
intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
173-
"intlgregcal_create_instance: could not create UTF-8 string "
174-
"from PHP's default timezone name (see date_default_timezone_get())",
175-
0);
190+
if (!set_gregorian_calendar_time_zone(gcal, status)) {
176191
delete gcal;
177192
if (!is_constructor) {
178193
zval_ptr_dtor(return_value);
179194
RETVAL_NULL();
180195
}
181196
return;
182197
}
183-
184-
TimeZone *tz = TimeZone::createTimeZone(tzstr);
185-
gcal->adoptTimeZone(tz);
186198
}
187199

188200
co->ucal = gcal;
@@ -208,6 +220,82 @@ U_CFUNC PHP_METHOD(IntlGregorianCalendar, __construct)
208220
}
209221
}
210222

223+
U_CFUNC PHP_METHOD(IntlGregorianCalendar, createFromDate)
224+
{
225+
zend_long year, month, day;
226+
UErrorCode status = U_ZERO_ERROR;
227+
zend_error_handling error_handling;
228+
Calendar_object *co;
229+
GregorianCalendar *gcal;
230+
231+
intl_error_reset(NULL);
232+
233+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "lll", &year, &month, &day) == FAILURE) {
234+
RETURN_THROWS();
235+
}
236+
237+
ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(year, 1);
238+
ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(month, 2);
239+
ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(day, 3);
240+
241+
zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, &error_handling);
242+
243+
gcal = new GregorianCalendar((int32_t) year, (int32_t) month, (int32_t) day, status);
244+
if (!set_gregorian_calendar_time_zone(gcal, status)) {
245+
delete gcal;
246+
goto cleanup;
247+
}
248+
249+
object_init_ex(return_value, GregorianCalendar_ce_ptr);
250+
co = Z_INTL_CALENDAR_P(return_value);
251+
co->ucal = gcal;
252+
253+
cleanup:
254+
zend_restore_error_handling(&error_handling);
255+
}
256+
257+
U_CFUNC PHP_METHOD(IntlGregorianCalendar, createFromDateTime)
258+
{
259+
zend_long year, month, day, hour, minute, second;
260+
bool second_is_null = 1;
261+
UErrorCode status = U_ZERO_ERROR;
262+
zend_error_handling error_handling;
263+
Calendar_object *co;
264+
GregorianCalendar *gcal;
265+
266+
intl_error_reset(NULL);
267+
268+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "lllll|l!", &year, &month, &day, &hour, &minute, &second, &second_is_null) == FAILURE) {
269+
RETURN_THROWS();
270+
}
271+
272+
ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(year, 1);
273+
ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(month, 2);
274+
ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(day, 3);
275+
ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(hour, 4);
276+
ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(minute, 5);
277+
278+
zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, &error_handling);
279+
280+
if (second_is_null) {
281+
gcal = new GregorianCalendar((int32_t) year, (int32_t) month, (int32_t) day, (int32_t) hour, (int32_t) minute, status);
282+
} else {
283+
ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(second, 6);
284+
gcal = new GregorianCalendar((int32_t) year, (int32_t) month, (int32_t) day, (int32_t) hour, (int32_t) minute, (int32_t) second, status);
285+
}
286+
if (!set_gregorian_calendar_time_zone(gcal, status)) {
287+
delete gcal;
288+
goto cleanup;
289+
}
290+
291+
object_init_ex(return_value, GregorianCalendar_ce_ptr);
292+
co = Z_INTL_CALENDAR_P(return_value);
293+
co->ucal = gcal;
294+
295+
cleanup:
296+
zend_restore_error_handling(&error_handling);
297+
}
298+
211299
U_CFUNC PHP_FUNCTION(intlgregcal_set_gregorian_change)
212300
{
213301
double date;

‎ext/intl/tests/calendar_equals_before_after_basic.phpt

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ intl
99
ini_set("intl.error_level", E_WARNING);
1010
ini_set("intl.default_locale", "nl");
1111

12-
$intlcal1 = new IntlGregorianCalendar(2012, 1, 29, 16, 59, 59);
12+
$intlcal1 = IntlGregorianCalendar::createFromDateTime(2012, 1, 29, 16, 59, 59);
1313
$intlcal2 = IntlCalendar::createInstance(null, '@calendar=japanese');
14-
$intlcal3 = new IntlGregorianCalendar(2012, 1, 29, 17, 00, 00);
14+
$intlcal3 = IntlGregorianCalendar::createFromDateTime(2012, 1, 29, 17, 00, 00);
1515
$intlcal2->setTime($intlcal1->getTime());
1616

1717
var_dump($intlcal2->getType());
@@ -52,4 +52,4 @@ bool(false)
5252
string(10) "3 before 2"
5353
bool(false)
5454
string(9) "3 after 2"
55-
bool(true)
55+
bool(true)

‎ext/intl/tests/calendar_getErrorCode_getErrorMessage_basic.phpt

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ intl
99
ini_set("intl.error_level", E_WARNING);
1010
ini_set("intl.default_locale", "nl");
1111

12-
$intlcal = new IntlGregorianCalendar(2012, 1, 29);
12+
$intlcal = IntlGregorianCalendar::createFromDate(2012, 1, 29);
1313
var_dump(
1414
$intlcal->getErrorCode(),
1515
intlcal_get_error_code($intlcal),

‎ext/intl/tests/calendar_get_setRepeatedWallTimeOption_basic.phpt

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ ini_set("intl.default_locale", "nl");
1010
date_default_timezone_set('Europe/Amsterdam');
1111

1212
//28 October 2012, transition from DST
13-
$intlcal = new IntlGregorianCalendar(2012, 9, 28, 0, 0, 0);
13+
$intlcal = IntlGregorianCalendar::createFromDateTime(2012, 9, 28, 0, 0, 0);
1414
var_dump($intlcal->setRepeatedWallTimeOption(IntlCalendar::WALLTIME_LAST));
1515
var_dump($intlcal->getRepeatedWallTimeOption());
1616
$intlcal->set(IntlCalendar::FIELD_HOUR_OF_DAY, 2);

‎ext/intl/tests/calendar_set_date_time.phpt

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ intl
55
--FILE--
66
<?php
77

8+
$intlcal = IntlCalendar::createInstance('UTC');
9+
$intlcal->clear();
10+
811
//two minutes to midnight!
912
$intlcal->setDateTime(2012, 1, 29, 23, 58);
1013
var_dump($intlcal->getTime(), strtotime('2012-02-29 23:58:00 +0000') * 1000.);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
IntlGregorianCalendar::setDate(): error cases
3+
--EXTENSIONS--
4+
intl
5+
--SKIPIF--
6+
<?php if (PHP_INT_SIZE != 8) die("skip: 64-bit only"); ?>
7+
--FILE--
8+
<?php
9+
try {
10+
var_dump(IntlGregorianCalendar::createFromDate(99999999999, 1, 1));
11+
} catch (ValueError $e) {
12+
echo $e->getMessage(), "\n";
13+
}
14+
15+
try {
16+
var_dump(IntlGregorianCalendar::createFromDate(1, 99999999999, 1));
17+
} catch (ValueError $e) {
18+
echo $e->getMessage(), "\n";
19+
}
20+
21+
try {
22+
var_dump(IntlGregorianCalendar::createFromDate(1, 1, 99999999999));
23+
} catch (ValueError $e) {
24+
echo $e->getMessage(), "\n";
25+
}
26+
27+
?>
28+
--EXPECT--
29+
IntlGregorianCalendar::createFromDate(): Argument #1 ($year) must be between -2147483648 and 2147483647
30+
IntlGregorianCalendar::createFromDate(): Argument #2 ($month) must be between -2147483648 and 2147483647
31+
IntlGregorianCalendar::createFromDate(): Argument #3 ($dayOfMonth) must be between -2147483648 and 2147483647

0 commit comments

Comments
 (0)
Please sign in to comment.