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 	if (timeStyle != DateFormat::NONE) {
150 		timeStyle = (DateFormat::EStyle)(timeStyle & ~DateFormat::kRelative);
151 	}
152 
153 	zend_class_entry *instance_ce = Z_OBJCE_P(object);
154 	if (instanceof_function(instance_ce, Calendar_ce_ptr)) {
155 		Calendar *obj_cal = calendar_fetch_native_calendar(object);
156 		if (obj_cal == NULL) {
157 			intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR,
158 					"datefmt_format_object: bad IntlCalendar instance: "
159 					"not initialized properly", 0);
160 			RETURN_FALSE;
161 		}
162 		timeZone = obj_cal->getTimeZone().clone();
163 		date = obj_cal->getTime(status);
164 		if (U_FAILURE(status)) {
165 			intl_error_set(NULL, status,
166 					"datefmt_format_object: error obtaining instant from "
167 					"IntlCalendar", 0);
168 			RETVAL_FALSE;
169 			goto cleanup;
170 		}
171 		cal = obj_cal->clone();
172 	} else if (instanceof_function(instance_ce, php_date_get_interface_ce())) {
173 		if (intl_datetime_decompose(object, &date, &timeZone, NULL,
174 				"datefmt_format_object") == FAILURE) {
175 			RETURN_FALSE;
176 		}
177 		cal = new GregorianCalendar(Locale::createFromName(locale_str), status);
178 		if (U_FAILURE(status)) {
179 			intl_error_set(NULL, status,
180 					"datefmt_format_object: could not create GregorianCalendar",
181 					0);
182 			RETVAL_FALSE;
183 			goto cleanup;
184 		}
185 	} else {
186 		intl_error_set(NULL, status, "datefmt_format_object: the passed object "
187 				"must be an instance of either IntlCalendar or DateTime",
188 				0);
189 		RETURN_FALSE;
190 	}
191 
192 	if (pattern) {
193 		StringPiece sp(Z_STRVAL_P(format));
194 		df = new SimpleDateFormat(
195 			UnicodeString::fromUTF8(sp),
196 			Locale::createFromName(locale_str),
197 			status);
198 
199 		if (U_FAILURE(status)) {
200 			intl_error_set(NULL, status,
201 					"datefmt_format_object: could not create SimpleDateFormat",
202 					0);
203 			RETVAL_FALSE;
204 			goto cleanup;
205 		}
206 	} else {
207 		df = DateFormat::createDateTimeInstance(dateStyle, timeStyle,
208 				Locale::createFromName(locale_str));
209 
210 		if (df == NULL) { /* according to ICU sources, this should never happen */
211 			intl_error_set(NULL, status,
212 					"datefmt_format_object: could not create DateFormat",
213 					0);
214 			RETVAL_FALSE;
215 			goto cleanup;
216 		}
217 	}
218 
219 	//must be in this order (or have the cal adopt the tz)
220 	df->adoptCalendar(cal);
221 	cal = NULL;
222 	df->adoptTimeZone(timeZone);
223 	timeZone = NULL;
224 
225 	{
226 		zend_string *u8str;
227 		UnicodeString result = UnicodeString();
228 		df->format(date, result);
229 
230 		u8str = intl_charFromString(result, &status);
231 		if (!u8str) {
232 			intl_error_set(NULL, status,
233 					"datefmt_format_object: error converting result to UTF-8",
234 					0);
235 			RETVAL_FALSE;
236 			goto cleanup;
237 		}
238 		RETVAL_STR(u8str);
239 	}
240 
241 
242 cleanup:
243 	delete df;
244 	delete timeZone;
245 	delete cal;
246 }
247