1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | http://www.php.net/license/3_01.txt                                  |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Gustavo Lopes <cataphract@php.net>                          |
14    +----------------------------------------------------------------------+
15  */
16 
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20 
21 #include "../intl_cppshims.h"
22 
23 #include <unicode/locid.h>
24 #include <unicode/calendar.h>
25 #include <unicode/gregocal.h>
26 #include <unicode/ustring.h>
27 
28 extern "C" {
29 #include "../php_intl.h"
30 #include "../intl_common.h"
31 #define USE_TIMEZONE_POINTER 1
32 #include "../timezone/timezone_class.h"
33 #define USE_CALENDAR_POINTER 1
34 #include "calendar_class.h"
35 #include <ext/date/php_date.h>
36 #include "zend_exceptions.h"
37 }
38 
39 using icu::GregorianCalendar;
40 using icu::Locale;
41 using icu::UnicodeString;
42 using icu::StringPiece;
43 
fetch_greg(Calendar_object * co)44 static inline GregorianCalendar *fetch_greg(Calendar_object *co) {
45 	return (GregorianCalendar*)co->ucal;
46 }
47 
_php_intlgregcal_constructor_body(INTERNAL_FUNCTION_PARAMETERS,zend_bool is_constructor)48 static void _php_intlgregcal_constructor_body(
49     INTERNAL_FUNCTION_PARAMETERS, zend_bool is_constructor)
50 {
51 	zval		*tz_object	= NULL;
52 	zval		args_a[6] = {0},
53 				*args		= &args_a[0];
54 	char		*locale		= NULL;
55 	size_t			locale_len;
56 	zend_long		largs[6];
57 	UErrorCode	status		= U_ZERO_ERROR;
58 	int			variant;
59   int zpp_flags = is_constructor ? ZEND_PARSE_PARAMS_THROW : 0;
60 	intl_error_reset(NULL);
61 
62 	// parameter number validation / variant determination
63 	if (ZEND_NUM_ARGS() > 6 ||
64 			zend_get_parameters_array_ex(ZEND_NUM_ARGS(), args) == FAILURE) {
65 		intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
66 			"intlgregcal_create_instance: too many arguments", 0);
67 		if (!is_constructor) {
68 			zval_ptr_dtor(return_value);
69 			RETVAL_NULL();
70 		}
71 		return;
72 	}
73 	for (variant = ZEND_NUM_ARGS();
74 		variant > 0 && Z_TYPE(args[variant - 1]) == IS_NULL;
75 		variant--) {}
76 	if (variant == 4) {
77 		intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
78 			"intlgregcal_create_instance: no variant with 4 arguments "
79 			"(excluding trailing NULLs)", 0);
80 		if (!is_constructor) {
81 			zval_ptr_dtor(return_value);
82 			RETVAL_NULL();
83 		}
84 		return;
85 	}
86 
87 	// argument parsing
88 	if (variant <= 2) {
89 		if (zend_parse_parameters_ex(zpp_flags, MIN(ZEND_NUM_ARGS(), 2),
90 				"|z!s!", &tz_object, &locale, &locale_len) == FAILURE) {
91 			intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
92 				"intlgregcal_create_instance: bad arguments", 0);
93 			if (!is_constructor) {
94 				zval_ptr_dtor(return_value);
95 				RETVAL_NULL();
96 			}
97 			return;
98 		}
99 	}
100 	if (variant > 2 && zend_parse_parameters_ex(zpp_flags, ZEND_NUM_ARGS(),
101 			"lll|lll", &largs[0], &largs[1], &largs[2], &largs[3], &largs[4],
102 			&largs[5]) == FAILURE) {
103 		intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
104 			"intlgregcal_create_instance: bad arguments", 0);
105 		if (!is_constructor) {
106 			zval_ptr_dtor(return_value);
107 			RETVAL_NULL();
108 		}
109 		return;
110 	}
111 
112 	// instantion of ICU object
113 	GregorianCalendar *gcal = NULL;
114 
115 	if (variant <= 2) {
116 		// From timezone and locale (0 to 2 arguments)
117 		TimeZone *tz = timezone_process_timezone_argument(tz_object, NULL,
118 			"intlgregcal_create_instance");
119 		if (tz == NULL) {
120 			if (!EG(exception)) {
121 				zend_throw_exception(IntlException_ce_ptr, "Constructor failed", 0);
122 			}
123 			if (!is_constructor) {
124 				zval_ptr_dtor(return_value);
125 				RETVAL_NULL();
126 			}
127 			return;
128 		}
129 		if (!locale) {
130 			locale = const_cast<char*>(intl_locale_get_default());
131 		}
132 
133 		gcal = new GregorianCalendar(tz, Locale::createFromName(locale),
134 			status);
135 		if (U_FAILURE(status)) {
136 			intl_error_set(NULL, status, "intlgregcal_create_instance: error "
137 				"creating ICU GregorianCalendar from time zone and locale", 0);
138 			if (gcal) {
139 				delete gcal;
140 			}
141 			delete tz;
142 			if (!is_constructor) {
143 				zval_ptr_dtor(return_value);
144 				RETVAL_NULL();
145 			}
146 			return;
147 		}
148 	} else {
149 		// From date/time (3, 5 or 6 arguments)
150 		for (int i = 0; i < variant; i++) {
151 			if (largs[i] < INT32_MIN || largs[i] > INT32_MAX) {
152 				intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
153 					"intlgregcal_create_instance: at least one of the arguments"
154 					" has an absolute value that is too large", 0);
155 				if (!is_constructor) {
156 					zval_ptr_dtor(return_value);
157 					RETVAL_NULL();
158 				}
159 				return;
160 			}
161 		}
162 
163 		if (variant == 3) {
164 			gcal = new GregorianCalendar((int32_t)largs[0], (int32_t)largs[1],
165 				(int32_t)largs[2], status);
166 		} else if (variant == 5) {
167 			gcal = new GregorianCalendar((int32_t)largs[0], (int32_t)largs[1],
168 				(int32_t)largs[2], (int32_t)largs[3], (int32_t)largs[4], status);
169 		} else if (variant == 6) {
170 			gcal = new GregorianCalendar((int32_t)largs[0], (int32_t)largs[1],
171 				(int32_t)largs[2], (int32_t)largs[3], (int32_t)largs[4], (int32_t)largs[5],
172 				status);
173 		}
174 		if (U_FAILURE(status)) {
175 			intl_error_set(NULL, status, "intlgregcal_create_instance: error "
176 				"creating ICU GregorianCalendar from date", 0);
177 			if (gcal) {
178 				delete gcal;
179 			}
180 			if (!is_constructor) {
181 				zval_ptr_dtor(return_value);
182 				RETVAL_NULL();
183 			}
184 			return;
185 		}
186 
187 		timelib_tzinfo *tzinfo = get_timezone_info();
188 		UnicodeString tzstr = UnicodeString::fromUTF8(StringPiece(tzinfo->name));
189 		if (tzstr.isBogus()) {
190 			intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
191 				"intlgregcal_create_instance: could not create UTF-8 string "
192 				"from PHP's default timezone name (see date_default_timezone_get())",
193 				0);
194 			delete gcal;
195 			if (!is_constructor) {
196 				zval_ptr_dtor(return_value);
197 				RETVAL_NULL();
198 			}
199 			return;
200 		}
201 
202 		TimeZone *tz = TimeZone::createTimeZone(tzstr);
203 		gcal->adoptTimeZone(tz);
204 	}
205 
206     Calendar_object *co = Z_INTL_CALENDAR_P(return_value);
207     co->ucal = gcal;
208 }
209 
PHP_FUNCTION(intlgregcal_create_instance)210 U_CFUNC PHP_FUNCTION(intlgregcal_create_instance)
211 {
212 	intl_error_reset(NULL);
213 
214 	object_init_ex(return_value, GregorianCalendar_ce_ptr);
215 	_php_intlgregcal_constructor_body(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
216 }
217 
PHP_METHOD(IntlGregorianCalendar,__construct)218 U_CFUNC PHP_METHOD(IntlGregorianCalendar, __construct)
219 {
220 	zend_error_handling error_handling;
221 
222 	zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, &error_handling);
223 	return_value = ZEND_THIS;
224 	_php_intlgregcal_constructor_body(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
225 	zend_restore_error_handling(&error_handling);
226 }
227 
PHP_FUNCTION(intlgregcal_set_gregorian_change)228 U_CFUNC PHP_FUNCTION(intlgregcal_set_gregorian_change)
229 {
230 	double date;
231 	CALENDAR_METHOD_INIT_VARS;
232 
233 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
234 			"Od", &object, GregorianCalendar_ce_ptr, &date) == FAILURE) {
235 		intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
236 			"intlgregcal_set_gregorian_change: bad arguments", 0);
237 		RETURN_FALSE;
238 	}
239 
240 	CALENDAR_METHOD_FETCH_OBJECT;
241 
242 	fetch_greg(co)->setGregorianChange(date, CALENDAR_ERROR_CODE(co));
243 	INTL_METHOD_CHECK_STATUS(co, "intlgregcal_set_gregorian_change: error "
244 		"calling ICU method");
245 
246 	RETURN_TRUE;
247 }
248 
PHP_FUNCTION(intlgregcal_get_gregorian_change)249 U_CFUNC PHP_FUNCTION(intlgregcal_get_gregorian_change)
250 {
251 	CALENDAR_METHOD_INIT_VARS;
252 
253 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
254 			"O", &object, GregorianCalendar_ce_ptr) == FAILURE) {
255 		intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
256 			"intlgregcal_get_gregorian_change: bad arguments", 0);
257 		RETURN_FALSE;
258 	}
259 
260 	CALENDAR_METHOD_FETCH_OBJECT;
261 
262 	RETURN_DOUBLE((double)fetch_greg(co)->getGregorianChange());
263 }
264 
PHP_FUNCTION(intlgregcal_is_leap_year)265 U_CFUNC PHP_FUNCTION(intlgregcal_is_leap_year)
266 {
267 	zend_long year;
268 	CALENDAR_METHOD_INIT_VARS;
269 
270 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
271 			"Ol", &object, GregorianCalendar_ce_ptr, &year) == FAILURE) {
272 		intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
273 			"intlgregcal_is_leap_year: bad arguments", 0);
274 		RETURN_FALSE;
275 	}
276 
277 	if (year < INT32_MIN || year > INT32_MAX) {
278 		intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
279 			"intlgregcal_is_leap_year: year out of bounds", 0);
280 		RETURN_FALSE;
281 	}
282 
283 	CALENDAR_METHOD_FETCH_OBJECT;
284 
285 	RETURN_BOOL((int)fetch_greg(co)->isLeapYear((int32_t)year));
286 }
287