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 ZEND_PARSE_PARAMETERS_START(3, 3)
242 Z_PARAM_LONG(year)
243 Z_PARAM_LONG(month)
244 Z_PARAM_LONG(day)
245 ZEND_PARSE_PARAMETERS_END();
246
247 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(year, 1);
248 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(month, 2);
249 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(day, 3);
250
251 zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, &error_handling);
252
253 gcal = new GregorianCalendar((int32_t) year, (int32_t) month, (int32_t) day, status);
254 if (!set_gregorian_calendar_time_zone(gcal, status)) {
255 delete gcal;
256 goto cleanup;
257 }
258
259 object_init_ex(return_value, GregorianCalendar_ce_ptr);
260 co = Z_INTL_CALENDAR_P(return_value);
261 co->ucal = gcal;
262
263 cleanup:
264 zend_restore_error_handling(&error_handling);
265 }
266
PHP_METHOD(IntlGregorianCalendar,createFromDateTime)267 U_CFUNC PHP_METHOD(IntlGregorianCalendar, createFromDateTime)
268 {
269 zend_long year, month, day, hour, minute, second;
270 bool second_is_null = 1;
271 UErrorCode status = U_ZERO_ERROR;
272 zend_error_handling error_handling;
273 Calendar_object *co;
274 GregorianCalendar *gcal;
275
276 intl_error_reset(NULL);
277
278 ZEND_PARSE_PARAMETERS_START(5, 6)
279 Z_PARAM_LONG(year)
280 Z_PARAM_LONG(month)
281 Z_PARAM_LONG(day)
282 Z_PARAM_LONG(hour)
283 Z_PARAM_LONG(minute)
284 Z_PARAM_OPTIONAL
285 Z_PARAM_LONG_OR_NULL(second, second_is_null)
286 ZEND_PARSE_PARAMETERS_END();
287
288 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(year, 1);
289 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(month, 2);
290 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(day, 3);
291 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(hour, 4);
292 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(minute, 5);
293
294 zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, &error_handling);
295
296 if (second_is_null) {
297 gcal = new GregorianCalendar((int32_t) year, (int32_t) month, (int32_t) day, (int32_t) hour, (int32_t) minute, status);
298 } else {
299 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(second, 6);
300 gcal = new GregorianCalendar((int32_t) year, (int32_t) month, (int32_t) day, (int32_t) hour, (int32_t) minute, (int32_t) second, status);
301 }
302 if (!set_gregorian_calendar_time_zone(gcal, status)) {
303 delete gcal;
304 goto cleanup;
305 }
306
307 object_init_ex(return_value, GregorianCalendar_ce_ptr);
308 co = Z_INTL_CALENDAR_P(return_value);
309 co->ucal = gcal;
310
311 cleanup:
312 zend_restore_error_handling(&error_handling);
313 }
314
PHP_FUNCTION(intlgregcal_set_gregorian_change)315 U_CFUNC PHP_FUNCTION(intlgregcal_set_gregorian_change)
316 {
317 double date;
318 CALENDAR_METHOD_INIT_VARS;
319
320 if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
321 "Od", &object, GregorianCalendar_ce_ptr, &date) == FAILURE) {
322 RETURN_THROWS();
323 }
324
325
326 CALENDAR_METHOD_FETCH_OBJECT;
327
328 fetch_greg(co)->setGregorianChange(date, CALENDAR_ERROR_CODE(co));
329 INTL_METHOD_CHECK_STATUS(co, "intlgregcal_set_gregorian_change: error "
330 "calling ICU method");
331
332 RETURN_TRUE;
333 }
334
PHP_FUNCTION(intlgregcal_get_gregorian_change)335 U_CFUNC PHP_FUNCTION(intlgregcal_get_gregorian_change)
336 {
337 CALENDAR_METHOD_INIT_VARS;
338
339 if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
340 "O", &object, GregorianCalendar_ce_ptr) == FAILURE) {
341 RETURN_THROWS();
342 }
343
344
345 CALENDAR_METHOD_FETCH_OBJECT;
346
347 RETURN_DOUBLE((double)fetch_greg(co)->getGregorianChange());
348 }
349
PHP_FUNCTION(intlgregcal_is_leap_year)350 U_CFUNC PHP_FUNCTION(intlgregcal_is_leap_year)
351 {
352 zend_long year;
353 CALENDAR_METHOD_INIT_VARS;
354
355 if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
356 "Ol", &object, GregorianCalendar_ce_ptr, &year) == FAILURE) {
357 RETURN_THROWS();
358 }
359
360
361 if (UNEXPECTED(year < INT32_MIN || year > INT32_MAX)) {
362 zend_argument_value_error(hasThis() ? 1 : 2, "must be between %d and %d", INT32_MIN, INT32_MAX);
363 RETURN_THROWS();
364 }
365
366 CALENDAR_METHOD_FETCH_OBJECT;
367
368 RETURN_BOOL((int)fetch_greg(co)->isLeapYear((int32_t)year));
369 }
370