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 // parameter number validation / variant determination
95 if (ZEND_NUM_ARGS() > 6 ||
96 zend_get_parameters_array_ex(ZEND_NUM_ARGS(), args) == FAILURE) {
97 zend_argument_count_error("Too many arguments");
98 RETURN_THROWS();
99 }
100
101 for (variant = ZEND_NUM_ARGS();
102 variant > 0 && Z_TYPE(args[variant - 1]) == IS_NULL;
103 variant--) {}
104 if (variant == 4) {
105 zend_argument_count_error("No variant with 4 arguments (excluding trailing NULLs)");
106 RETURN_THROWS();
107 }
108
109 // argument parsing
110 if (variant <= 2) {
111 if (zend_parse_parameters(MIN(ZEND_NUM_ARGS(), 2),
112 "|z!s!", &tz_object, &locale, &locale_len) == FAILURE) {
113 RETURN_THROWS();
114 }
115 }
116 if (variant > 2 && zend_parse_parameters(ZEND_NUM_ARGS(),
117 "lll|lll", &largs[0], &largs[1], &largs[2], &largs[3], &largs[4],
118 &largs[5]) == FAILURE) {
119 RETURN_THROWS();
120 }
121
122 if (error_handling != NULL) {
123 zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, error_handling);
124 *error_handling_replaced = 1;
125 }
126
127 // instantion of ICU object
128 Calendar_object *co = Z_INTL_CALENDAR_P(return_value);
129 GregorianCalendar *gcal = NULL;
130
131 if (co->ucal) {
132 zend_throw_error(NULL, "IntlGregorianCalendar object is already constructed");
133 RETURN_THROWS();
134 }
135
136 if (variant <= 2) {
137 // From timezone and locale (0 to 2 arguments)
138 TimeZone *tz = timezone_process_timezone_argument(tz_object, NULL,
139 "intlgregcal_create_instance");
140 if (tz == NULL) {
141 if (!EG(exception)) {
142 zend_throw_exception(IntlException_ce_ptr, "Constructor failed", 0);
143 }
144 if (!is_constructor) {
145 zval_ptr_dtor(return_value);
146 RETVAL_NULL();
147 }
148 return;
149 }
150 if (!locale) {
151 locale = const_cast<char*>(intl_locale_get_default());
152 }
153
154 gcal = new GregorianCalendar(tz, Locale::createFromName(locale),
155 status);
156 // Should this throw?
157 if (U_FAILURE(status)) {
158 intl_error_set(NULL, status, "intlgregcal_create_instance: error "
159 "creating ICU GregorianCalendar from time zone and locale", 0);
160 if (gcal) {
161 delete gcal;
162 }
163 delete tz;
164 if (!is_constructor) {
165 zval_ptr_dtor(return_value);
166 RETVAL_NULL();
167 }
168 return;
169 }
170 } else {
171 // From date/time (3, 5 or 6 arguments)
172 for (int i = 0; i < variant; i++) {
173 ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(largs[i], getThis() ? (i-1) : i);
174 }
175
176 if (variant == 3) {
177 gcal = new GregorianCalendar((int32_t)largs[0], (int32_t)largs[1],
178 (int32_t)largs[2], status);
179 } else if (variant == 5) {
180 gcal = new GregorianCalendar((int32_t)largs[0], (int32_t)largs[1],
181 (int32_t)largs[2], (int32_t)largs[3], (int32_t)largs[4], status);
182 } else if (variant == 6) {
183 gcal = new GregorianCalendar((int32_t)largs[0], (int32_t)largs[1],
184 (int32_t)largs[2], (int32_t)largs[3], (int32_t)largs[4], (int32_t)largs[5],
185 status);
186 } else {
187 ZEND_UNREACHABLE();
188 }
189
190 if (!set_gregorian_calendar_time_zone(gcal, status)) {
191 delete gcal;
192 if (!is_constructor) {
193 zval_ptr_dtor(return_value);
194 RETVAL_NULL();
195 }
196 return;
197 }
198 }
199
200 co->ucal = gcal;
201 }
202
PHP_FUNCTION(intlgregcal_create_instance)203 U_CFUNC PHP_FUNCTION(intlgregcal_create_instance)
204 {
205 intl_error_reset(NULL);
206
207 object_init_ex(return_value, GregorianCalendar_ce_ptr);
208 _php_intlgregcal_constructor_body(INTERNAL_FUNCTION_PARAM_PASSTHRU, /* is_constructor */ 0, NULL, NULL);
209 }
210
PHP_METHOD(IntlGregorianCalendar,__construct)211 U_CFUNC PHP_METHOD(IntlGregorianCalendar, __construct)
212 {
213 zend_error_handling error_handling;
214 bool error_handling_replaced = 0;
215
216 return_value = ZEND_THIS;
217 _php_intlgregcal_constructor_body(INTERNAL_FUNCTION_PARAM_PASSTHRU, /* is_constructor */ 1, &error_handling, &error_handling_replaced);
218 if (error_handling_replaced) {
219 zend_restore_error_handling(&error_handling);
220 }
221 }
222
PHP_METHOD(IntlGregorianCalendar,createFromDate)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
PHP_METHOD(IntlGregorianCalendar,createFromDateTime)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
PHP_FUNCTION(intlgregcal_set_gregorian_change)299 U_CFUNC PHP_FUNCTION(intlgregcal_set_gregorian_change)
300 {
301 double date;
302 CALENDAR_METHOD_INIT_VARS;
303
304 if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
305 "Od", &object, GregorianCalendar_ce_ptr, &date) == FAILURE) {
306 RETURN_THROWS();
307 }
308
309 CALENDAR_METHOD_FETCH_OBJECT;
310
311 fetch_greg(co)->setGregorianChange(date, CALENDAR_ERROR_CODE(co));
312 INTL_METHOD_CHECK_STATUS(co, "intlgregcal_set_gregorian_change: error "
313 "calling ICU method");
314
315 RETURN_TRUE;
316 }
317
PHP_FUNCTION(intlgregcal_get_gregorian_change)318 U_CFUNC PHP_FUNCTION(intlgregcal_get_gregorian_change)
319 {
320 CALENDAR_METHOD_INIT_VARS;
321
322 if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
323 "O", &object, GregorianCalendar_ce_ptr) == FAILURE) {
324 RETURN_THROWS();
325 }
326
327 CALENDAR_METHOD_FETCH_OBJECT;
328
329 RETURN_DOUBLE((double)fetch_greg(co)->getGregorianChange());
330 }
331
PHP_FUNCTION(intlgregcal_is_leap_year)332 U_CFUNC PHP_FUNCTION(intlgregcal_is_leap_year)
333 {
334 zend_long year;
335 CALENDAR_METHOD_INIT_VARS;
336
337 if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
338 "Ol", &object, GregorianCalendar_ce_ptr, &year) == FAILURE) {
339 RETURN_THROWS();
340 }
341
342 if (UNEXPECTED(year < INT32_MIN || year > INT32_MAX)) {
343 zend_argument_value_error(getThis() ? 1 : 2, "must be between %d and %d", INT32_MIN, INT32_MAX);
344 RETURN_THROWS();
345 }
346
347 CALENDAR_METHOD_FETCH_OBJECT;
348
349 RETURN_BOOL((int)fetch_greg(co)->isLeapYear((int32_t)year));
350 }
351