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 #if U_ICU_VERSION_MAJOR_NUM * 10 + U_ICU_VERSION_MINOR_NUM >= 42
189 		UnicodeString tzstr = UnicodeString::fromUTF8(StringPiece(tzinfo->name));
190 #else
191 		UnicodeString tzstr = UnicodeString(tzinfo->name,
192 			strlen(tzinfo->name), US_INV);
193 #endif
194 		if (tzstr.isBogus()) {
195 			intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
196 				"intlgregcal_create_instance: could not create UTF-8 string "
197 				"from PHP's default timezone name (see date_default_timezone_get())",
198 				0);
199 			delete gcal;
200 			if (!is_constructor) {
201 				zval_ptr_dtor(return_value);
202 				RETVAL_NULL();
203 			}
204 			return;
205 		}
206 
207 		TimeZone *tz = TimeZone::createTimeZone(tzstr);
208 		gcal->adoptTimeZone(tz);
209 	}
210 
211     Calendar_object *co = Z_INTL_CALENDAR_P(return_value);
212     co->ucal = gcal;
213 }
214 
PHP_FUNCTION(intlgregcal_create_instance)215 U_CFUNC PHP_FUNCTION(intlgregcal_create_instance)
216 {
217 	intl_error_reset(NULL);
218 
219 	object_init_ex(return_value, GregorianCalendar_ce_ptr);
220 	_php_intlgregcal_constructor_body(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
221 }
222 
PHP_METHOD(IntlGregorianCalendar,__construct)223 U_CFUNC PHP_METHOD(IntlGregorianCalendar, __construct)
224 {
225 	zend_error_handling error_handling;
226 
227 	zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, &error_handling);
228 	return_value = getThis();
229 	_php_intlgregcal_constructor_body(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
230 	zend_restore_error_handling(&error_handling);
231 }
232 
PHP_FUNCTION(intlgregcal_set_gregorian_change)233 U_CFUNC PHP_FUNCTION(intlgregcal_set_gregorian_change)
234 {
235 	double date;
236 	CALENDAR_METHOD_INIT_VARS;
237 
238 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
239 			"Od", &object, GregorianCalendar_ce_ptr, &date) == FAILURE) {
240 		intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
241 			"intlgregcal_set_gregorian_change: bad arguments", 0);
242 		RETURN_FALSE;
243 	}
244 
245 	CALENDAR_METHOD_FETCH_OBJECT;
246 
247 	fetch_greg(co)->setGregorianChange(date, CALENDAR_ERROR_CODE(co));
248 	INTL_METHOD_CHECK_STATUS(co, "intlgregcal_set_gregorian_change: error "
249 		"calling ICU method");
250 
251 	RETURN_TRUE;
252 }
253 
PHP_FUNCTION(intlgregcal_get_gregorian_change)254 U_CFUNC PHP_FUNCTION(intlgregcal_get_gregorian_change)
255 {
256 	CALENDAR_METHOD_INIT_VARS;
257 
258 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
259 			"O", &object, GregorianCalendar_ce_ptr) == FAILURE) {
260 		intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
261 			"intlgregcal_get_gregorian_change: bad arguments", 0);
262 		RETURN_FALSE;
263 	}
264 
265 	CALENDAR_METHOD_FETCH_OBJECT;
266 
267 	RETURN_DOUBLE((double)fetch_greg(co)->getGregorianChange());
268 }
269 
PHP_FUNCTION(intlgregcal_is_leap_year)270 U_CFUNC PHP_FUNCTION(intlgregcal_is_leap_year)
271 {
272 	zend_long year;
273 	CALENDAR_METHOD_INIT_VARS;
274 
275 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
276 			"Ol", &object, GregorianCalendar_ce_ptr, &year) == FAILURE) {
277 		intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
278 			"intlgregcal_is_leap_year: bad arguments", 0);
279 		RETURN_FALSE;
280 	}
281 
282 	if (year < INT32_MIN || year > INT32_MAX) {
283 		intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
284 			"intlgregcal_is_leap_year: year out of bounds", 0);
285 		RETURN_FALSE;
286 	}
287 
288 	CALENDAR_METHOD_FETCH_OBJECT;
289 
290 	RETURN_BOOL((int)fetch_greg(co)->isLeapYear((int32_t)year));
291 }
292