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