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