1 /*
2 +----------------------------------------------------------------------+
3 | This source file is subject to version 3.01 of the PHP license, |
4 | that is bundled with this package in the file LICENSE, and is |
5 | available through the world-wide-web at the following url: |
6 | https://www.php.net/license/3_01.txt |
7 | If you did not receive a copy of the PHP license and are unable to |
8 | obtain it through the world-wide-web, please send a note to |
9 | license@php.net so we can mail you a copy immediately. |
10 +----------------------------------------------------------------------+
11 | Authors: Gustavo Lopes <cataphract@php.net> |
12 +----------------------------------------------------------------------+
13 */
14
15 #ifdef HAVE_CONFIG_H
16 #include "config.h"
17 #endif
18
19 #include "../intl_cppshims.h"
20
21 #include <unicode/locid.h>
22 #include <unicode/calendar.h>
23 #include <unicode/gregocal.h>
24 #include <unicode/ustring.h>
25
26 extern "C" {
27 #include "../php_intl.h"
28 #include "../intl_common.h"
29 #define USE_TIMEZONE_POINTER 1
30 #include "../timezone/timezone_class.h"
31 #define USE_CALENDAR_POINTER 1
32 #include "calendar_class.h"
33 #include <ext/date/php_date.h>
34 #include "zend_exceptions.h"
35 }
36
37 using icu::GregorianCalendar;
38 using icu::Locale;
39 using icu::UnicodeString;
40 using icu::StringPiece;
41
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
fetch_greg(Calendar_object * co)48 static inline GregorianCalendar *fetch_greg(Calendar_object *co) {
49 return (GregorianCalendar*)co->ucal;
50 }
51
set_gregorian_calendar_time_zone(GregorianCalendar * gcal,UErrorCode status)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
_php_intlgregcal_constructor_body(INTERNAL_FUNCTION_PARAMETERS,bool is_constructor,zend_error_handling * error_handling,bool * error_handling_replaced)81 static void _php_intlgregcal_constructor_body(
82 INTERNAL_FUNCTION_PARAMETERS, bool is_constructor, zend_error_handling *error_handling, bool *error_handling_replaced)
83 {
84 zval *tz_object = NULL;
85 zval args_a[6],
86 *args = &args_a[0];
87 char *locale = NULL;
88 size_t locale_len;
89 zend_long largs[6];
90 UErrorCode status = U_ZERO_ERROR;
91 int variant;
92 intl_error_reset(NULL);
93
94 if (is_constructor && ZEND_NUM_ARGS() > 2) {
95 zend_error(E_DEPRECATED, "Calling IntlGregorianCalendar::__construct() with more than 2 arguments is deprecated, "
96 "use either IntlGregorianCalendar::createFromDate() or IntlGregorianCalendar::createFromDateTime() instead");
97 if (UNEXPECTED(EG(exception))) {
98 RETURN_THROWS();
99 }
100 }
101
102 // parameter number validation / variant determination
103 if (ZEND_NUM_ARGS() > 6 ||
104 zend_get_parameters_array_ex(ZEND_NUM_ARGS(), args) == FAILURE) {
105 zend_argument_count_error("Too many arguments");
106 RETURN_THROWS();
107 }
108
109 for (variant = ZEND_NUM_ARGS();
110 variant > 0 && Z_TYPE(args[variant - 1]) == IS_NULL;
111 variant--) {}
112 if (variant == 4) {
113 zend_argument_count_error("No variant with 4 arguments (excluding trailing NULLs)");
114 RETURN_THROWS();
115 }
116
117 // argument parsing
118 if (variant <= 2) {
119 if (zend_parse_parameters(MIN(ZEND_NUM_ARGS(), 2),
120 "|z!s!", &tz_object, &locale, &locale_len) == FAILURE) {
121 RETURN_THROWS();
122 }
123 }
124 if (variant > 2 && zend_parse_parameters(ZEND_NUM_ARGS(),
125 "lll|lll", &largs[0], &largs[1], &largs[2], &largs[3], &largs[4],
126 &largs[5]) == FAILURE) {
127 RETURN_THROWS();
128 }
129
130 if (error_handling != NULL) {
131 zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, error_handling);
132 *error_handling_replaced = 1;
133 }
134
135 // instantion of ICU object
136 Calendar_object *co = Z_INTL_CALENDAR_P(return_value);
137 GregorianCalendar *gcal = NULL;
138
139 if (co->ucal) {
140 zend_throw_error(NULL, "IntlGregorianCalendar object is already constructed");
141 RETURN_THROWS();
142 }
143
144 if (variant <= 2) {
145 // From timezone and locale (0 to 2 arguments)
146 TimeZone *tz = timezone_process_timezone_argument(tz_object, NULL,
147 "intlgregcal_create_instance");
148 if (tz == NULL) {
149 if (!EG(exception)) {
150 zend_throw_exception(IntlException_ce_ptr, "Constructor failed", 0);
151 }
152 if (!is_constructor) {
153 zval_ptr_dtor(return_value);
154 RETVAL_NULL();
155 }
156 return;
157 }
158 if (!locale) {
159 locale = const_cast<char*>(intl_locale_get_default());
160 }
161
162 gcal = new GregorianCalendar(tz, Locale::createFromName(locale),
163 status);
164 // Should this throw?
165 if (U_FAILURE(status)) {
166 intl_error_set(NULL, status, "intlgregcal_create_instance: error "
167 "creating ICU GregorianCalendar from time zone and locale", 0);
168 if (gcal) {
169 delete gcal;
170 }
171 delete tz;
172 if (!is_constructor) {
173 zval_ptr_dtor(return_value);
174 RETVAL_NULL();
175 }
176 return;
177 }
178 } else {
179 // From date/time (3, 5 or 6 arguments)
180 for (int i = 0; i < variant; i++) {
181 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(largs[i], hasThis() ? (i-1) : i);
182 }
183
184 if (variant == 3) {
185 gcal = new GregorianCalendar((int32_t)largs[0], (int32_t)largs[1],
186 (int32_t)largs[2], status);
187 } else if (variant == 5) {
188 gcal = new GregorianCalendar((int32_t)largs[0], (int32_t)largs[1],
189 (int32_t)largs[2], (int32_t)largs[3], (int32_t)largs[4], status);
190 } else if (variant == 6) {
191 gcal = new GregorianCalendar((int32_t)largs[0], (int32_t)largs[1],
192 (int32_t)largs[2], (int32_t)largs[3], (int32_t)largs[4], (int32_t)largs[5],
193 status);
194 } else {
195 ZEND_UNREACHABLE();
196 }
197
198 if (!set_gregorian_calendar_time_zone(gcal, status)) {
199 delete gcal;
200 if (!is_constructor) {
201 zval_ptr_dtor(return_value);
202 RETVAL_NULL();
203 }
204 return;
205 }
206 }
207
208 co->ucal = gcal;
209 }
210
PHP_FUNCTION(intlgregcal_create_instance)211 U_CFUNC PHP_FUNCTION(intlgregcal_create_instance)
212 {
213 intl_error_reset(NULL);
214
215 object_init_ex(return_value, GregorianCalendar_ce_ptr);
216 _php_intlgregcal_constructor_body(INTERNAL_FUNCTION_PARAM_PASSTHRU, /* is_constructor */ 0, NULL, NULL);
217 }
218
PHP_METHOD(IntlGregorianCalendar,__construct)219 U_CFUNC PHP_METHOD(IntlGregorianCalendar, __construct)
220 {
221 zend_error_handling error_handling;
222 bool error_handling_replaced = 0;
223
224 return_value = ZEND_THIS;
225 _php_intlgregcal_constructor_body(INTERNAL_FUNCTION_PARAM_PASSTHRU, /* is_constructor */ 1, &error_handling, &error_handling_replaced);
226 if (error_handling_replaced) {
227 zend_restore_error_handling(&error_handling);
228 }
229 }
230
PHP_METHOD(IntlGregorianCalendar,createFromDate)231 U_CFUNC PHP_METHOD(IntlGregorianCalendar, createFromDate)
232 {
233 zend_long year, month, day;
234 UErrorCode status = U_ZERO_ERROR;
235 zend_error_handling error_handling;
236 Calendar_object *co;
237 GregorianCalendar *gcal;
238
239 intl_error_reset(NULL);
240
241 if (zend_parse_parameters(ZEND_NUM_ARGS(), "lll", &year, &month, &day) == FAILURE) {
242 RETURN_THROWS();
243 }
244
245 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(year, 1);
246 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(month, 2);
247 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(day, 3);
248
249 zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, &error_handling);
250
251 gcal = new GregorianCalendar((int32_t) year, (int32_t) month, (int32_t) day, status);
252 if (!set_gregorian_calendar_time_zone(gcal, status)) {
253 delete gcal;
254 goto cleanup;
255 }
256
257 object_init_ex(return_value, GregorianCalendar_ce_ptr);
258 co = Z_INTL_CALENDAR_P(return_value);
259 co->ucal = gcal;
260
261 cleanup:
262 zend_restore_error_handling(&error_handling);
263 }
264
PHP_METHOD(IntlGregorianCalendar,createFromDateTime)265 U_CFUNC PHP_METHOD(IntlGregorianCalendar, createFromDateTime)
266 {
267 zend_long year, month, day, hour, minute, second;
268 bool second_is_null = 1;
269 UErrorCode status = U_ZERO_ERROR;
270 zend_error_handling error_handling;
271 Calendar_object *co;
272 GregorianCalendar *gcal;
273
274 intl_error_reset(NULL);
275
276 if (zend_parse_parameters(ZEND_NUM_ARGS(), "lllll|l!", &year, &month, &day, &hour, &minute, &second, &second_is_null) == FAILURE) {
277 RETURN_THROWS();
278 }
279
280 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(year, 1);
281 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(month, 2);
282 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(day, 3);
283 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(hour, 4);
284 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(minute, 5);
285
286 zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, &error_handling);
287
288 if (second_is_null) {
289 gcal = new GregorianCalendar((int32_t) year, (int32_t) month, (int32_t) day, (int32_t) hour, (int32_t) minute, status);
290 } else {
291 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(second, 6);
292 gcal = new GregorianCalendar((int32_t) year, (int32_t) month, (int32_t) day, (int32_t) hour, (int32_t) minute, (int32_t) second, status);
293 }
294 if (!set_gregorian_calendar_time_zone(gcal, status)) {
295 delete gcal;
296 goto cleanup;
297 }
298
299 object_init_ex(return_value, GregorianCalendar_ce_ptr);
300 co = Z_INTL_CALENDAR_P(return_value);
301 co->ucal = gcal;
302
303 cleanup:
304 zend_restore_error_handling(&error_handling);
305 }
306
PHP_FUNCTION(intlgregcal_set_gregorian_change)307 U_CFUNC PHP_FUNCTION(intlgregcal_set_gregorian_change)
308 {
309 double date;
310 CALENDAR_METHOD_INIT_VARS;
311
312 if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
313 "Od", &object, GregorianCalendar_ce_ptr, &date) == FAILURE) {
314 RETURN_THROWS();
315 }
316
317 CALENDAR_METHOD_FETCH_OBJECT;
318
319 fetch_greg(co)->setGregorianChange(date, CALENDAR_ERROR_CODE(co));
320 INTL_METHOD_CHECK_STATUS(co, "intlgregcal_set_gregorian_change: error "
321 "calling ICU method");
322
323 RETURN_TRUE;
324 }
325
PHP_FUNCTION(intlgregcal_get_gregorian_change)326 U_CFUNC PHP_FUNCTION(intlgregcal_get_gregorian_change)
327 {
328 CALENDAR_METHOD_INIT_VARS;
329
330 if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
331 "O", &object, GregorianCalendar_ce_ptr) == FAILURE) {
332 RETURN_THROWS();
333 }
334
335 CALENDAR_METHOD_FETCH_OBJECT;
336
337 RETURN_DOUBLE((double)fetch_greg(co)->getGregorianChange());
338 }
339
PHP_FUNCTION(intlgregcal_is_leap_year)340 U_CFUNC PHP_FUNCTION(intlgregcal_is_leap_year)
341 {
342 zend_long year;
343 CALENDAR_METHOD_INIT_VARS;
344
345 if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
346 "Ol", &object, GregorianCalendar_ce_ptr, &year) == FAILURE) {
347 RETURN_THROWS();
348 }
349
350 if (UNEXPECTED(year < INT32_MIN || year > INT32_MAX)) {
351 zend_argument_value_error(hasThis() ? 1 : 2, "must be between %d and %d", INT32_MIN, INT32_MAX);
352 RETURN_THROWS();
353 }
354
355 CALENDAR_METHOD_FETCH_OBJECT;
356
357 RETURN_BOOL((int)fetch_greg(co)->isLeapYear((int32_t)year));
358 }
359