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