xref: /PHP-7.1/ext/intl/common/common_date.cpp (revision 03f3b847)
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 
21 extern "C" {
22 #include "../php_intl.h"
23 #define USE_CALENDAR_POINTER 1
24 #include "../calendar/calendar_class.h"
25 #include <ext/date/php_date.h>
26 }
27 
28 #ifndef INFINITY
29 #define INFINITY (DBL_MAX+DBL_MAX)
30 #endif
31 
32 #ifndef NAN
33 #define NAN (INFINITY-INFINITY)
34 #endif
35 
36 /* {{{ timezone_convert_datetimezone
37  *      The timezone in DateTime and DateTimeZone is not unified. */
timezone_convert_datetimezone(int type,void * object,int is_datetime,intl_error * outside_error,const char * func)38 U_CFUNC TimeZone *timezone_convert_datetimezone(int type,
39 												void *object,
40 												int is_datetime,
41 												intl_error *outside_error,
42 												const char *func)
43 {
44 	char		*id = NULL,
45 				offset_id[] = "GMT+00:00";
46 	int32_t		id_len = 0;
47 	char		*message;
48 	TimeZone	*timeZone;
49 
50 	switch (type) {
51 		case TIMELIB_ZONETYPE_ID:
52 			id = is_datetime
53 				? ((php_date_obj*)object)->time->tz_info->name
54 				: ((php_timezone_obj*)object)->tzi.tz->name;
55 			id_len = strlen(id);
56 			break;
57 		case TIMELIB_ZONETYPE_OFFSET: {
58 			int offset_mins = is_datetime
59 				? -((php_date_obj*)object)->time->z
60 				: -(int)((php_timezone_obj*)object)->tzi.utc_offset,
61 				hours = offset_mins / 60,
62 				minutes = offset_mins - hours * 60;
63 			minutes *= minutes > 0 ? 1 : -1;
64 
65 			if (offset_mins <= -24 * 60 || offset_mins >= 24 * 60) {
66 				spprintf(&message, 0, "%s: object has an time zone offset "
67 					"that's too large", func);
68 				intl_errors_set(outside_error, U_ILLEGAL_ARGUMENT_ERROR,
69 					message, 1);
70 				efree(message);
71 				return NULL;
72 			}
73 
74 			id = offset_id;
75 			id_len = slprintf(id, sizeof(offset_id), "GMT%+03d:%02d",
76 				hours, minutes);
77 			break;
78 		}
79 		case TIMELIB_ZONETYPE_ABBR:
80 			id = is_datetime
81 				? ((php_date_obj*)object)->time->tz_abbr
82 				: ((php_timezone_obj*)object)->tzi.z.abbr;
83 			id_len = strlen(id);
84 			break;
85 	}
86 
87 	UnicodeString s = UnicodeString(id, id_len, US_INV);
88 	timeZone = TimeZone::createTimeZone(s);
89 #if U_ICU_VERSION_MAJOR_NUM >= 49
90 	if (*timeZone == TimeZone::getUnknown()) {
91 #else
92 	UnicodeString resultingId;
93 	timeZone->getID(resultingId);
94 	if (resultingId == UnicodeString("Etc/Unknown", -1, US_INV)
95 			|| resultingId == UnicodeString("GMT", -1, US_INV)) {
96 #endif
97 		spprintf(&message, 0, "%s: time zone id '%s' "
98 			"extracted from ext/date DateTimeZone not recognized", func, id);
99 		intl_errors_set(outside_error, U_ILLEGAL_ARGUMENT_ERROR,
100 			message, 1);
101 		efree(message);
102 		delete timeZone;
103 		return NULL;
104 	}
105 	return timeZone;
106 }
107 /* }}} */
108 
109 U_CFUNC int intl_datetime_decompose(zval *z, double *millis, TimeZone **tz,
110 		intl_error *err, const char *func)
111 {
112 	zval	retval;
113 	zval	zfuncname;
114 	char	*message;
115 
116 	if (err && U_FAILURE(err->code)) {
117 		return FAILURE;
118 	}
119 
120 	if (millis) {
121 		*millis = NAN;
122 	}
123 	if (tz) {
124 		*tz = NULL;
125 	}
126 
127 	if (millis) {
128 		php_date_obj *datetime;
129 
130 		ZVAL_STRING(&zfuncname, "getTimestamp");
131 		if (call_user_function(NULL, z, &zfuncname, &retval, 0, NULL)
132 				!= SUCCESS || Z_TYPE(retval) != IS_LONG) {
133 			spprintf(&message, 0, "%s: error calling ::getTimeStamp() on the "
134 					"object", func);
135 			intl_errors_set(err, U_INTERNAL_PROGRAM_ERROR,
136 				message, 1);
137 			efree(message);
138 			zval_ptr_dtor(&zfuncname);
139 			return FAILURE;
140 		}
141 
142 		datetime = Z_PHPDATE_P(z);
143 		*millis = U_MILLIS_PER_SECOND * ((double)Z_LVAL(retval) + datetime->time->f);
144 		zval_ptr_dtor(&zfuncname);
145 	}
146 
147 	if (tz) {
148 		php_date_obj *datetime;
149 		datetime = Z_PHPDATE_P(z);
150 		if (!datetime->time) {
151 			spprintf(&message, 0, "%s: the %s object is not properly "
152 					"initialized", func, ZSTR_VAL(Z_OBJCE_P(z)->name));
153 			intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR,
154 				message, 1);
155 			efree(message);
156 			return FAILURE;
157 		}
158 		if (!datetime->time->is_localtime) {
159 			*tz = TimeZone::getGMT()->clone();
160 		} else {
161 			*tz = timezone_convert_datetimezone(datetime->time->zone_type,
162 				datetime, 1, NULL, func);
163 			if (*tz == NULL) {
164 				spprintf(&message, 0, "%s: could not convert DateTime's "
165 						"time zone", func);
166 				intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR,
167 					message, 1);
168 				efree(message);
169 				return FAILURE;
170 			}
171 		}
172 	}
173 
174 	return SUCCESS;
175 }
176 
177 U_CFUNC double intl_zval_to_millis(zval *z, intl_error *err, const char *func)
178 {
179 	double	rv = NAN;
180 	zend_long	lv;
181 	int		type;
182 	char	*message;
183 
184 	if (err && U_FAILURE(err->code)) {
185 		return NAN;
186 	}
187 
188 	switch (Z_TYPE_P(z)) {
189 	case IS_STRING:
190 		type = is_numeric_string(Z_STRVAL_P(z), Z_STRLEN_P(z), &lv, &rv, 0);
191 		if (type == IS_DOUBLE) {
192 			rv *= U_MILLIS_PER_SECOND;
193 		} else if (type == IS_LONG) {
194 			rv = U_MILLIS_PER_SECOND * (double)lv;
195 		} else {
196 			spprintf(&message, 0, "%s: string '%s' is not numeric, "
197 					"which would be required for it to be a valid date", func,
198 					Z_STRVAL_P(z));
199 			intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR,
200 				message, 1);
201 			efree(message);
202 		}
203 		break;
204 	case IS_LONG:
205 		rv = U_MILLIS_PER_SECOND * (double)Z_LVAL_P(z);
206 		break;
207 	case IS_DOUBLE:
208 		rv = U_MILLIS_PER_SECOND * Z_DVAL_P(z);
209 		break;
210 	case IS_OBJECT:
211 		if (instanceof_function(Z_OBJCE_P(z), php_date_get_interface_ce())) {
212 			intl_datetime_decompose(z, &rv, NULL, err, func);
213 		} else if (instanceof_function(Z_OBJCE_P(z), Calendar_ce_ptr)) {
214 			Calendar_object *co = Z_INTL_CALENDAR_P(z);
215 			if (co->ucal == NULL) {
216 				spprintf(&message, 0, "%s: IntlCalendar object is not properly "
217 						"constructed", func);
218 				intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR,
219 					message, 1);
220 				efree(message);
221 			} else {
222 				UErrorCode status = UErrorCode();
223 				rv = (double)co->ucal->getTime(status);
224 				if (U_FAILURE(status)) {
225 					spprintf(&message, 0, "%s: call to internal "
226 							"Calendar::getTime() has failed", func);
227 					intl_errors_set(err, status, message, 1);
228 					efree(message);
229 				}
230 			}
231 		} else {
232 			/* TODO: try with cast(), get() to obtain a number */
233 			spprintf(&message, 0, "%s: invalid object type for date/time "
234 					"(only IntlCalendar and DateTimeInterface permitted)", func);
235 			intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR,
236 				message, 1);
237 			efree(message);
238 		}
239 		break;
240 	default:
241 		spprintf(&message, 0, "%s: invalid PHP type for date", func);
242 		intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR,
243 			message, 1);
244 		efree(message);
245 		break;
246 	}
247 
248 	return rv;
249 }
250