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