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 #include "../intl_cppshims.h"
18 
19 #include <unicode/calendar.h>
20 #include <unicode/gregocal.h>
21 #include <unicode/datefmt.h>
22 #include <unicode/smpdtfmt.h>
23 #include <unicode/locid.h>
24 
25 #include "../intl_convertcpp.h"
26 
27 extern "C" {
28 #include "../php_intl.h"
29 #include "../locale/locale.h"
30 #define USE_CALENDAR_POINTER 1
31 #include "../calendar/calendar_class.h"
32 #include <ext/date/php_date.h>
33 #include "../common/common_date.h"
34 }
35 
36 static const DateFormat::EStyle valid_styles[] = {
37 		DateFormat::kNone,
38 		DateFormat::kFull,
39 		DateFormat::kLong,
40 		DateFormat::kMedium,
41 		DateFormat::kShort,
42 		DateFormat::kFullRelative,
43 		DateFormat::kLongRelative,
44 		DateFormat::kMediumRelative,
45 		DateFormat::kShortRelative,
46 };
47 
valid_format(zval * z)48 static bool valid_format(zval *z) {
49 	if (Z_TYPE_P(z) == IS_LONG) {
50 		zend_long lval = Z_LVAL_P(z);
51 		for (int i = 0; i < sizeof(valid_styles) / sizeof(*valid_styles); i++) {
52 			if ((zend_long)valid_styles[i] == lval) {
53 				return true;
54 			}
55 		}
56 	}
57 
58 	return false;
59 }
60 
PHP_FUNCTION(datefmt_format_object)61 U_CFUNC PHP_FUNCTION(datefmt_format_object)
62 {
63 	zval				*object,
64 						*format = NULL;
65 	const char			*locale_str	= NULL;
66 	size_t				locale_len;
67 	bool				pattern		= false;
68 	UDate				date;
69 	TimeZone			*timeZone	= NULL;
70 	UErrorCode			status		= U_ZERO_ERROR;
71 	DateFormat			*df			= NULL;
72 	Calendar			*cal		= NULL;
73 	DateFormat::EStyle	dateStyle = DateFormat::kDefault,
74 						timeStyle = DateFormat::kDefault;
75 
76 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "o|zs!",
77 			&object, &format, &locale_str, &locale_len) == FAILURE) {
78 		RETURN_FALSE;
79 	}
80 
81 	if (!locale_str) {
82 		locale_str = intl_locale_get_default();
83 	}
84 
85 	if (format == NULL || Z_TYPE_P(format) == IS_NULL) {
86 		//nothing
87 	} else if (Z_TYPE_P(format) == IS_ARRAY) {
88 		HashTable		*ht	= Z_ARRVAL_P(format);
89 		uint32_t         idx;
90 		zval			*z;
91 
92 		if (zend_hash_num_elements(ht) != 2) {
93 			intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
94 					"datefmt_format_object: bad format; if array, it must have "
95 					"two elements", 0);
96 			RETURN_FALSE;
97 		}
98 
99 		idx = 0;
100 		while (idx < ht->nNumUsed) {
101 			z = &ht->arData[idx].val;
102 			if (Z_TYPE_P(z) != IS_UNDEF) {
103 				break;
104 			}
105 			idx++;
106 		}
107 		if (idx >= ht->nNumUsed || !valid_format(z)) {
108 			intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
109 					"datefmt_format_object: bad format; the date format (first "
110 					"element of the array) is not valid", 0);
111 			RETURN_FALSE;
112 		}
113 		dateStyle = (DateFormat::EStyle)Z_LVAL_P(z);
114 
115 		idx++;
116 		while (idx < ht->nNumUsed) {
117 			z = &ht->arData[idx].val;
118 			if (Z_TYPE_P(z) != IS_UNDEF) {
119 				break;
120 			}
121 			idx++;
122 		}
123 		if (idx >= ht->nNumUsed || !valid_format(z)) {
124 			intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
125 					"datefmt_format_object: bad format; the time format ("
126 					"second element of the array) is not valid", 0);
127 			RETURN_FALSE;
128 		}
129 		timeStyle = (DateFormat::EStyle)Z_LVAL_P(z);
130 	} else if (Z_TYPE_P(format) == IS_LONG) {
131 		if (!valid_format(format)) {
132 			intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
133 					"datefmt_format_object: the date/time format type is invalid",
134 					0);
135 			RETURN_FALSE;
136 		}
137 		dateStyle = timeStyle = (DateFormat::EStyle)Z_LVAL_P(format);
138 	} else {
139 		convert_to_string_ex(format);
140 		if (Z_STRLEN_P(format) == 0) {
141 			intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
142 					"datefmt_format_object: the format is empty", 0);
143 			RETURN_FALSE;
144 		}
145 		pattern = true;
146 	}
147 
148 	//there's no support for relative time in ICU yet
149 	timeStyle = (DateFormat::EStyle)(timeStyle & ~DateFormat::kRelative);
150 
151 	zend_class_entry *instance_ce = Z_OBJCE_P(object);
152 	if (instanceof_function(instance_ce, Calendar_ce_ptr)) {
153 		Calendar *obj_cal = calendar_fetch_native_calendar(object);
154 		if (obj_cal == NULL) {
155 			intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
156 					"datefmt_format_object: bad IntlCalendar instance: "
157 					"not initialized properly", 0);
158 			RETURN_FALSE;
159 		}
160 		timeZone = obj_cal->getTimeZone().clone();
161 		date = obj_cal->getTime(status);
162 		if (U_FAILURE(status)) {
163 			intl_error_set(NULL, status,
164 					"datefmt_format_object: error obtaining instant from "
165 					"IntlCalendar", 0);
166 			RETVAL_FALSE;
167 			goto cleanup;
168 		}
169 		cal = obj_cal->clone();
170 	} else if (instanceof_function(instance_ce, php_date_get_date_ce())) {
171 		if (intl_datetime_decompose(object, &date, &timeZone, NULL,
172 				"datefmt_format_object") == FAILURE) {
173 			RETURN_FALSE;
174 		}
175 		cal = new GregorianCalendar(Locale::createFromName(locale_str), status);
176 		if (U_FAILURE(status)) {
177 			intl_error_set(NULL, status,
178 					"datefmt_format_object: could not create GregorianCalendar",
179 					0);
180 			RETVAL_FALSE;
181 			goto cleanup;
182 		}
183 	} else {
184 		intl_error_set(NULL, status, "datefmt_format_object: the passed object "
185 				"must be an instance of either IntlCalendar or DateTime",
186 				0);
187 		RETURN_FALSE;
188 	}
189 
190 	if (pattern) {
191 		 df = new SimpleDateFormat(
192 				UnicodeString(Z_STRVAL_P(format), Z_STRLEN_P(format),
193 						UnicodeString::kInvariant),
194 				Locale::createFromName(locale_str),
195 				status);
196 
197 		if (U_FAILURE(status)) {
198 			intl_error_set(NULL, status,
199 					"datefmt_format_object: could not create SimpleDateFormat",
200 					0);
201 			RETVAL_FALSE;
202 			goto cleanup;
203 		}
204 	} else {
205 		df = DateFormat::createDateTimeInstance(dateStyle, timeStyle,
206 				Locale::createFromName(locale_str));
207 
208 		if (df == NULL) { /* according to ICU sources, this should never happen */
209 			intl_error_set(NULL, status,
210 					"datefmt_format_object: could not create DateFormat",
211 					0);
212 			RETVAL_FALSE;
213 			goto cleanup;
214 		}
215 	}
216 
217 	//must be in this order (or have the cal adopt the tz)
218 	df->adoptCalendar(cal);
219 	cal = NULL;
220 	df->adoptTimeZone(timeZone);
221 	timeZone = NULL;
222 
223 	{
224 		zend_string *u8str;
225 		UnicodeString result = UnicodeString();
226 		df->format(date, result);
227 
228 		u8str = intl_charFromString(result, &status);
229 		if (!u8str) {
230 			intl_error_set(NULL, status,
231 					"datefmt_format_object: error converting result to UTF-8",
232 					0);
233 			RETVAL_FALSE;
234 			goto cleanup;
235 		}
236 		RETVAL_STR(u8str);
237 	}
238 
239 
240 cleanup:
241 	delete df;
242 	delete timeZone;
243 	delete cal;
244 }
245