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 
fetch_greg(Calendar_object * co)42 static inline GregorianCalendar *fetch_greg(Calendar_object *co) {
43 	return (GregorianCalendar*)co->ucal;
44 }
45 
_php_intlgregcal_constructor_body(INTERNAL_FUNCTION_PARAMETERS,bool is_constructor,zend_error_handling * error_handling,bool * error_handling_replaced)46 static void _php_intlgregcal_constructor_body(
47     INTERNAL_FUNCTION_PARAMETERS, bool is_constructor, zend_error_handling *error_handling, bool *error_handling_replaced)
48 {
49 	zval		*tz_object	= NULL;
50 	zval		args_a[6],
51 				*args		= &args_a[0];
52 	char		*locale		= NULL;
53 	size_t			locale_len;
54 	zend_long		largs[6];
55 	UErrorCode	status		= U_ZERO_ERROR;
56 	int			variant;
57 	intl_error_reset(NULL);
58 
59 	// parameter number validation / variant determination
60 	if (ZEND_NUM_ARGS() > 6 ||
61 			zend_get_parameters_array_ex(ZEND_NUM_ARGS(), args) == FAILURE) {
62 		zend_argument_count_error("Too many arguments");
63 		RETURN_THROWS();
64 	}
65 
66 	for (variant = ZEND_NUM_ARGS();
67 		variant > 0 && Z_TYPE(args[variant - 1]) == IS_NULL;
68 		variant--) {}
69 	if (variant == 4) {
70 		zend_argument_count_error("No variant with 4 arguments (excluding trailing NULLs)");
71 		RETURN_THROWS();
72 	}
73 
74 	// argument parsing
75 	if (variant <= 2) {
76 		if (zend_parse_parameters(MIN(ZEND_NUM_ARGS(), 2),
77 				"|z!s!", &tz_object, &locale, &locale_len) == FAILURE) {
78 			RETURN_THROWS();
79 		}
80 	}
81 	if (variant > 2 && zend_parse_parameters(ZEND_NUM_ARGS(),
82 			"lll|lll", &largs[0], &largs[1], &largs[2], &largs[3], &largs[4],
83 			&largs[5]) == FAILURE) {
84 		RETURN_THROWS();
85 	}
86 
87 	if (error_handling != NULL) {
88 		zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, error_handling);
89 		*error_handling_replaced = 1;
90 	}
91 
92 	// instantion of ICU object
93 	Calendar_object *co = Z_INTL_CALENDAR_P(return_value);
94 	GregorianCalendar *gcal = NULL;
95 
96 	if (co->ucal) {
97 		zend_throw_error(NULL, "IntlGregorianCalendar object is already constructed");
98 		RETURN_THROWS();
99 	}
100 
101 	if (variant <= 2) {
102 		// From timezone and locale (0 to 2 arguments)
103 		TimeZone *tz = timezone_process_timezone_argument(tz_object, NULL,
104 			"intlgregcal_create_instance");
105 		if (tz == NULL) {
106 			if (!EG(exception)) {
107 				zend_throw_exception(IntlException_ce_ptr, "Constructor failed", 0);
108 			}
109 			if (!is_constructor) {
110 				zval_ptr_dtor(return_value);
111 				RETVAL_NULL();
112 			}
113 			return;
114 		}
115 		if (!locale) {
116 			locale = const_cast<char*>(intl_locale_get_default());
117 		}
118 
119 		gcal = new GregorianCalendar(tz, Locale::createFromName(locale),
120 			status);
121 			// Should this throw?
122 		if (U_FAILURE(status)) {
123 			intl_error_set(NULL, status, "intlgregcal_create_instance: error "
124 				"creating ICU GregorianCalendar from time zone and locale", 0);
125 			if (gcal) {
126 				delete gcal;
127 			}
128 			delete tz;
129 			if (!is_constructor) {
130 				zval_ptr_dtor(return_value);
131 				RETVAL_NULL();
132 			}
133 			return;
134 		}
135 	} else {
136 		// From date/time (3, 5 or 6 arguments)
137 		for (int i = 0; i < variant; i++) {
138 			if (largs[i] < INT32_MIN || largs[i] > INT32_MAX) {
139 				zend_argument_value_error(getThis() ? (i-1) : i,
140 					"must be between %d and %d", INT32_MIN, INT32_MAX);
141 				RETURN_THROWS();
142 			}
143 		}
144 
145 		if (variant == 3) {
146 			gcal = new GregorianCalendar((int32_t)largs[0], (int32_t)largs[1],
147 				(int32_t)largs[2], status);
148 		} else if (variant == 5) {
149 			gcal = new GregorianCalendar((int32_t)largs[0], (int32_t)largs[1],
150 				(int32_t)largs[2], (int32_t)largs[3], (int32_t)largs[4], status);
151 		} else if (variant == 6) {
152 			gcal = new GregorianCalendar((int32_t)largs[0], (int32_t)largs[1],
153 				(int32_t)largs[2], (int32_t)largs[3], (int32_t)largs[4], (int32_t)largs[5],
154 				status);
155 		}
156 		if (U_FAILURE(status)) {
157 			intl_error_set(NULL, status, "intlgregcal_create_instance: error "
158 				"creating ICU GregorianCalendar from date", 0);
159 			if (gcal) {
160 				delete gcal;
161 			}
162 			if (!is_constructor) {
163 				zval_ptr_dtor(return_value);
164 				RETVAL_NULL();
165 			}
166 			return;
167 		}
168 
169 		timelib_tzinfo *tzinfo = get_timezone_info();
170 		UnicodeString tzstr = UnicodeString::fromUTF8(StringPiece(tzinfo->name));
171 		if (tzstr.isBogus()) {
172 			intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
173 				"intlgregcal_create_instance: could not create UTF-8 string "
174 				"from PHP's default timezone name (see date_default_timezone_get())",
175 				0);
176 			delete gcal;
177 			if (!is_constructor) {
178 				zval_ptr_dtor(return_value);
179 				RETVAL_NULL();
180 			}
181 			return;
182 		}
183 
184 		TimeZone *tz = TimeZone::createTimeZone(tzstr);
185 		gcal->adoptTimeZone(tz);
186 	}
187 
188 	co->ucal = gcal;
189 }
190 
PHP_FUNCTION(intlgregcal_create_instance)191 U_CFUNC PHP_FUNCTION(intlgregcal_create_instance)
192 {
193 	intl_error_reset(NULL);
194 
195 	object_init_ex(return_value, GregorianCalendar_ce_ptr);
196 	_php_intlgregcal_constructor_body(INTERNAL_FUNCTION_PARAM_PASSTHRU, /* is_constructor */ 0, NULL, NULL);
197 }
198 
PHP_METHOD(IntlGregorianCalendar,__construct)199 U_CFUNC PHP_METHOD(IntlGregorianCalendar, __construct)
200 {
201 	zend_error_handling error_handling;
202 	bool error_handling_replaced = 0;
203 
204 	return_value = ZEND_THIS;
205 	_php_intlgregcal_constructor_body(INTERNAL_FUNCTION_PARAM_PASSTHRU, /* is_constructor */ 1, &error_handling, &error_handling_replaced);
206 	if (error_handling_replaced) {
207 		zend_restore_error_handling(&error_handling);
208 	}
209 }
210 
PHP_FUNCTION(intlgregcal_set_gregorian_change)211 U_CFUNC PHP_FUNCTION(intlgregcal_set_gregorian_change)
212 {
213 	double date;
214 	CALENDAR_METHOD_INIT_VARS;
215 
216 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
217 			"Od", &object, GregorianCalendar_ce_ptr, &date) == FAILURE) {
218 		RETURN_THROWS();
219 	}
220 
221 	CALENDAR_METHOD_FETCH_OBJECT;
222 
223 	fetch_greg(co)->setGregorianChange(date, CALENDAR_ERROR_CODE(co));
224 	INTL_METHOD_CHECK_STATUS(co, "intlgregcal_set_gregorian_change: error "
225 		"calling ICU method");
226 
227 	RETURN_TRUE;
228 }
229 
PHP_FUNCTION(intlgregcal_get_gregorian_change)230 U_CFUNC PHP_FUNCTION(intlgregcal_get_gregorian_change)
231 {
232 	CALENDAR_METHOD_INIT_VARS;
233 
234 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
235 			"O", &object, GregorianCalendar_ce_ptr) == FAILURE) {
236 		RETURN_THROWS();
237 	}
238 
239 	CALENDAR_METHOD_FETCH_OBJECT;
240 
241 	RETURN_DOUBLE((double)fetch_greg(co)->getGregorianChange());
242 }
243 
PHP_FUNCTION(intlgregcal_is_leap_year)244 U_CFUNC PHP_FUNCTION(intlgregcal_is_leap_year)
245 {
246 	zend_long year;
247 	CALENDAR_METHOD_INIT_VARS;
248 
249 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
250 			"Ol", &object, GregorianCalendar_ce_ptr, &year) == FAILURE) {
251 		RETURN_THROWS();
252 	}
253 
254 	if (year < INT32_MIN || year > INT32_MAX) {
255 		zend_argument_value_error(getThis() ? 1 : 2, "must be between %d and %d", INT32_MIN, INT32_MAX);
256 		RETURN_THROWS();
257 	}
258 
259 	CALENDAR_METHOD_FETCH_OBJECT;
260 
261 	RETURN_BOOL((int)fetch_greg(co)->isLeapYear((int32_t)year));
262 }
263