xref: /php-src/ext/date/php_date.c (revision bfc98855)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
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    | https://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: Derick Rethans <derick@derickrethans.nl>                    |
14    +----------------------------------------------------------------------+
15  */
16 
17 #include "php.h"
18 #include "php_main.h"
19 #include "php_ini.h"
20 #include "ext/standard/info.h"
21 #include "ext/standard/php_versioning.h"
22 #include "php_date.h"
23 #include "zend_interfaces.h"
24 #include "zend_exceptions.h"
25 #include "lib/timelib.h"
26 #include "lib/timelib_private.h"
27 #ifndef PHP_WIN32
28 #include <time.h>
29 #else
30 #include "win32/time.h"
31 #endif
32 
33 #ifdef PHP_WIN32
php_date_llabs(__int64 i)34 static __inline __int64 php_date_llabs( __int64 i ) { return i >= 0? i: -i; }
35 #elif defined(__GNUC__) && __GNUC__ < 3
php_date_llabs(__int64_t i)36 static __inline __int64_t php_date_llabs( __int64_t i ) { return i >= 0 ? i : -i; }
37 #else
php_date_llabs(long long i)38 static inline long long php_date_llabs( long long i ) { return i >= 0 ? i : -i; }
39 #endif
40 
41 #ifdef PHP_WIN32
42 #define DATE_I64_BUF_LEN 65
43 # define DATE_I64A(i, s, len) _i64toa_s(i, s, len, 10)
44 # define DATE_A64I(i, s) i = _atoi64(s)
45 #else
46 #define DATE_I64_BUF_LEN 65
47 # define DATE_I64A(i, s, len) \
48 	do { \
49 		int st = snprintf(s, len, "%lld", i); \
50 		s[st] = '\0'; \
51 	} while (0);
52 #define DATE_A64I(i, s) i = strtoll(s, NULL, 10)
53 #endif
54 
php_time(void)55 PHPAPI time_t php_time(void)
56 {
57 #ifdef HAVE_GETTIMEOFDAY
58 	struct timeval tm;
59 
60 	if (UNEXPECTED(gettimeofday(&tm, NULL) != SUCCESS)) {
61 		/* fallback, can't reasonably happen */
62 		return time(NULL);
63 	}
64 
65 	return tm.tv_sec;
66 #else
67 	return time(NULL);
68 #endif
69 }
70 
71 /*
72  * RFC822, Section 5.1: http://www.ietf.org/rfc/rfc822.txt
73  *  date-time   =  [ day "," ] date time        ; dd mm yy hh:mm:ss zzz
74  *  day         =  "Mon"  / "Tue" /  "Wed"  / "Thu"  /  "Fri"  / "Sat" /  "Sun"
75  *  date        =  1*2DIGIT month 2DIGIT        ; day month year e.g. 20 Jun 82
76  *  month       =  "Jan"  /  "Feb" /  "Mar"  /  "Apr"  /  "May"  /  "Jun" /  "Jul"  /  "Aug"  /  "Sep"  /  "Oct" /  "Nov"  /  "Dec"
77  *  time        =  hour zone                    ; ANSI and Military
78  *  hour        =  2DIGIT ":" 2DIGIT [":" 2DIGIT] ; 00:00:00 - 23:59:59
79  *  zone        =  "UT"  / "GMT"  /  "EST" / "EDT"  /  "CST" / "CDT"  /  "MST" / "MDT"  /  "PST" / "PDT"  /  1ALPHA  / ( ("+" / "-") 4DIGIT )
80  */
81 #define DATE_FORMAT_RFC822   "D, d M y H:i:s O"
82 
83 /*
84  * RFC850, Section 2.1.4: http://www.ietf.org/rfc/rfc850.txt
85  *  Format must be acceptable both to the ARPANET and to the getdate routine.
86  *  One format that is acceptable to both is Weekday, DD-Mon-YY HH:MM:SS TIMEZONE
87  *  TIMEZONE can be any timezone name (3 or more letters)
88  */
89 #define DATE_FORMAT_RFC850   "l, d-M-y H:i:s T"
90 
91 /*
92  * RFC1036, Section 2.1.2: http://www.ietf.org/rfc/rfc1036.txt
93  *  Its format must be acceptable both in RFC-822 and to the getdate(3)
94  *  Wdy, DD Mon YY HH:MM:SS TIMEZONE
95  *  There is no hope of having a complete list of timezones.  Universal
96  *  Time (GMT), the North American timezones (PST, PDT, MST, MDT, CST,
97  *  CDT, EST, EDT) and the +/-hhmm offset specified in RFC-822 should be supported.
98  */
99 #define DATE_FORMAT_RFC1036  "D, d M y H:i:s O"
100 
101 /*
102  * RFC1123, Section 5.2.14: http://www.ietf.org/rfc/rfc1123.txt
103  *  RFC-822 Date and Time Specification: RFC-822 Section 5
104  *  The syntax for the date is hereby changed to: date = 1*2DIGIT month 2*4DIGIT
105  */
106 #define DATE_FORMAT_RFC1123  "D, d M Y H:i:s O"
107 
108 /*
109  * RFC7231, Section 7.1.1: http://tools.ietf.org/html/rfc7231
110  */
111 #define DATE_FORMAT_RFC7231  "D, d M Y H:i:s \\G\\M\\T"
112 
113 /*
114  * RFC2822, Section 3.3: http://www.ietf.org/rfc/rfc2822.txt
115  *  FWS             =       ([*WSP CRLF] 1*WSP) /   ; Folding white space
116  *  CFWS            =       *([FWS] comment) (([FWS] comment) / FWS)
117  *
118  *  date-time       =       [ day-of-week "," ] date FWS time [CFWS]
119  *  day-of-week     =       ([FWS] day-name)
120  *  day-name        =       "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
121  *  date            =       day month year
122  *  year            =       4*DIGIT
123  *  month           =       (FWS month-name FWS)
124  *  month-name      =       "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
125  *  day             =       ([FWS] 1*2DIGIT)
126  *  time            =       time-of-day FWS zone
127  *  time-of-day     =       hour ":" minute [ ":" second ]
128  *  hour            =       2DIGIT
129  *  minute          =       2DIGIT
130  *  second          =       2DIGIT
131  *  zone            =       (( "+" / "-" ) 4DIGIT)
132  */
133 #define DATE_FORMAT_RFC2822  "D, d M Y H:i:s O"
134 
135 /*
136  * RFC3339, Section 5.6: http://www.ietf.org/rfc/rfc3339.txt
137  *  date-fullyear   = 4DIGIT
138  *  date-month      = 2DIGIT  ; 01-12
139  *  date-mday       = 2DIGIT  ; 01-28, 01-29, 01-30, 01-31 based on month/year
140  *
141  *  time-hour       = 2DIGIT  ; 00-23
142  *  time-minute     = 2DIGIT  ; 00-59
143  *  time-second     = 2DIGIT  ; 00-58, 00-59, 00-60 based on leap second rules
144  *
145  *  time-secfrac    = "." 1*DIGIT
146  *  time-numoffset  = ("+" / "-") time-hour ":" time-minute
147  *  time-offset     = "Z" / time-numoffset
148  *
149  *  partial-time    = time-hour ":" time-minute ":" time-second [time-secfrac]
150  *  full-date       = date-fullyear "-" date-month "-" date-mday
151  *  full-time       = partial-time time-offset
152  *
153  *  date-time       = full-date "T" full-time
154  */
155 #define DATE_FORMAT_RFC3339  "Y-m-d\\TH:i:sP"
156 
157 /*
158  * This format does not technically match the ISO 8601 standard, as it does not
159  * use : in the UTC offset format specifier. This is kept for BC reasons. The
160  * DATE_FORMAT_ISO8601_EXPANDED format does correct this, as well as adding
161  * support for years out side of the traditional 0000-9999 range.
162  */
163 #define DATE_FORMAT_ISO8601  "Y-m-d\\TH:i:sO"
164 
165 /* ISO 8601:2004(E)
166  *
167  * Section 3.5 Expansion:
168  * By mutual agreement of the partners in information interchange, it is
169  * permitted to expand the component identifying the calendar year, which is
170  * otherwise limited to four digits. This enables reference to dates and times
171  * in calendar years outside the range supported by complete representations,
172  * i.e. before the start of the year [0000] or after the end of the year
173  * [9999]."
174  *
175  * Section 4.1.2.4 Expanded representations:
176  * If, by agreement, expanded representations are used, the formats shall be as
177  * specified below. The interchange parties shall agree the additional number of
178  * digits in the time element year. In the examples below it has been agreed to
179  * expand the time element year with two digits.
180  * Extended format: ±YYYYY-MM-DD
181  * Example: +001985-04-12
182  *
183  * PHP's year expansion digits are variable.
184  */
185 #define DATE_FORMAT_ISO8601_EXPANDED    "X-m-d\\TH:i:sP"
186 
187 /* Internal Only
188  * This format only extends the year when needed, keeping the 'P' format with
189  * colon for UTC offsets
190  */
191 #define DATE_FORMAT_ISO8601_LARGE_YEAR  "x-m-d\\TH:i:sP"
192 
193 /*
194  * RFC3339, Appendix A: http://www.ietf.org/rfc/rfc3339.txt
195  *  ISO 8601 also requires (in section 5.3.1.3) that a decimal fraction
196  *  be proceeded by a "0" if less than unity.  Annex B.2 of ISO 8601
197  *  gives examples where the decimal fractions are not preceded by a "0".
198  *  This grammar assumes section 5.3.1.3 is correct and that Annex B.2 is
199  *  in error.
200  */
201 #define DATE_FORMAT_RFC3339_EXTENDED  "Y-m-d\\TH:i:s.vP"
202 
203 /*
204  * This comes from various sources that like to contradict. I'm going with the
205  * format here because of:
206  * http://msdn.microsoft.com/en-us/library/windows/desktop/aa384321%28v=vs.85%29.aspx
207  * and http://curl.haxx.se/rfc/cookie_spec.html
208  */
209 #define DATE_FORMAT_COOKIE   "l, d-M-Y H:i:s T"
210 
211 #define SUNFUNCS_RET_TIMESTAMP 0
212 #define SUNFUNCS_RET_STRING    1
213 #define SUNFUNCS_RET_DOUBLE    2
214 
215 #define PHP_DATE_TIMEZONE_GROUP_AFRICA     0x0001
216 #define PHP_DATE_TIMEZONE_GROUP_AMERICA    0x0002
217 #define PHP_DATE_TIMEZONE_GROUP_ANTARCTICA 0x0004
218 #define PHP_DATE_TIMEZONE_GROUP_ARCTIC     0x0008
219 #define PHP_DATE_TIMEZONE_GROUP_ASIA       0x0010
220 #define PHP_DATE_TIMEZONE_GROUP_ATLANTIC   0x0020
221 #define PHP_DATE_TIMEZONE_GROUP_AUSTRALIA  0x0040
222 #define PHP_DATE_TIMEZONE_GROUP_EUROPE     0x0080
223 #define PHP_DATE_TIMEZONE_GROUP_INDIAN     0x0100
224 #define PHP_DATE_TIMEZONE_GROUP_PACIFIC    0x0200
225 #define PHP_DATE_TIMEZONE_GROUP_UTC        0x0400
226 #define PHP_DATE_TIMEZONE_GROUP_ALL        0x07FF
227 #define PHP_DATE_TIMEZONE_GROUP_ALL_W_BC   0x0FFF
228 #define PHP_DATE_TIMEZONE_PER_COUNTRY      0x1000
229 
230 #define PHP_DATE_PERIOD_EXCLUDE_START_DATE 0x0001
231 #define PHP_DATE_PERIOD_INCLUDE_END_DATE   0x0002
232 
233 #include "php_date_arginfo.h"
234 
235 static const char* guess_timezone(const timelib_tzdb *tzdb);
236 static void date_register_classes(void);
237 /* }}} */
238 
239 ZEND_DECLARE_MODULE_GLOBALS(date)
240 static PHP_GINIT_FUNCTION(date);
241 
242 /* True global */
243 timelib_tzdb *php_date_global_timezone_db;
244 int php_date_global_timezone_db_enabled;
245 
246 #define DATE_DEFAULT_LATITUDE "31.7667"
247 #define DATE_DEFAULT_LONGITUDE "35.2333"
248 
249 /* on 90'50; common sunset declaration (start of sun body appear) */
250 #define DATE_SUNSET_ZENITH "90.833333"
251 
252 /* on 90'50; common sunrise declaration (sun body disappeared) */
253 #define DATE_SUNRISE_ZENITH "90.833333"
254 
255 static PHP_INI_MH(OnUpdate_date_timezone);
256 
257 /* {{{ INI Settings */
258 PHP_INI_BEGIN()
259 	STD_PHP_INI_ENTRY("date.timezone", "UTC", PHP_INI_ALL, OnUpdate_date_timezone, default_timezone, zend_date_globals, date_globals)
260 	PHP_INI_ENTRY("date.default_latitude",           DATE_DEFAULT_LATITUDE,        PHP_INI_ALL, NULL)
261 	PHP_INI_ENTRY("date.default_longitude",          DATE_DEFAULT_LONGITUDE,       PHP_INI_ALL, NULL)
262 	PHP_INI_ENTRY("date.sunset_zenith",              DATE_SUNSET_ZENITH,           PHP_INI_ALL, NULL)
263 	PHP_INI_ENTRY("date.sunrise_zenith",             DATE_SUNRISE_ZENITH,          PHP_INI_ALL, NULL)
264 PHP_INI_END()
265 /* }}} */
266 
267 static zend_class_entry *date_ce_date, *date_ce_timezone, *date_ce_interval, *date_ce_period;
268 static zend_class_entry *date_ce_immutable, *date_ce_interface;
269 static zend_class_entry *date_ce_date_error, *date_ce_date_object_error, *date_ce_date_range_error;
270 static zend_class_entry *date_ce_date_exception, *date_ce_date_invalid_timezone_exception, *date_ce_date_invalid_operation_exception, *date_ce_date_malformed_string_exception, *date_ce_date_malformed_interval_string_exception, *date_ce_date_malformed_period_string_exception;
271 
272 
php_date_get_date_ce(void)273 PHPAPI zend_class_entry *php_date_get_date_ce(void)
274 {
275 	return date_ce_date;
276 }
277 
php_date_get_immutable_ce(void)278 PHPAPI zend_class_entry *php_date_get_immutable_ce(void)
279 {
280 	return date_ce_immutable;
281 }
282 
php_date_get_interface_ce(void)283 PHPAPI zend_class_entry *php_date_get_interface_ce(void)
284 {
285 	return date_ce_interface;
286 }
287 
php_date_get_timezone_ce(void)288 PHPAPI zend_class_entry *php_date_get_timezone_ce(void)
289 {
290 	return date_ce_timezone;
291 }
292 
php_date_get_interval_ce(void)293 PHPAPI zend_class_entry *php_date_get_interval_ce(void)
294 {
295 	return date_ce_interval;
296 }
297 
php_date_get_period_ce(void)298 PHPAPI zend_class_entry *php_date_get_period_ce(void)
299 {
300 	return date_ce_period;
301 }
302 
303 static zend_object_handlers date_object_handlers_date;
304 static zend_object_handlers date_object_handlers_immutable;
305 static zend_object_handlers date_object_handlers_timezone;
306 static zend_object_handlers date_object_handlers_interval;
307 static zend_object_handlers date_object_handlers_period;
308 
date_throw_uninitialized_error(zend_class_entry * ce)309 static void date_throw_uninitialized_error(zend_class_entry *ce)
310 {
311 	if (ce->type == ZEND_INTERNAL_CLASS) {
312 		zend_throw_error(date_ce_date_object_error, "Object of type %s has not been correctly initialized by calling parent::__construct() in its constructor", ZSTR_VAL(ce->name));
313 	} else {
314 		zend_class_entry *ce_ptr = ce;
315 		while (ce_ptr && ce_ptr->parent && ce_ptr->type == ZEND_USER_CLASS) {
316 			ce_ptr = ce_ptr->parent;
317 		}
318 		if (ce_ptr->type != ZEND_INTERNAL_CLASS) {
319 			zend_throw_error(date_ce_date_object_error, "Object of type %s not been correctly initialized by calling parent::__construct() in its constructor", ZSTR_VAL(ce->name));
320 		}
321 		zend_throw_error(date_ce_date_object_error, "Object of type %s (inheriting %s) has not been correctly initialized by calling parent::__construct() in its constructor", ZSTR_VAL(ce->name), ZSTR_VAL(ce_ptr->name));
322 	}
323 }
324 
325 #define DATE_CHECK_INITIALIZED(member, ce) \
326 	if (UNEXPECTED(!member)) { \
327 		date_throw_uninitialized_error(ce); \
328 		RETURN_THROWS(); \
329 	}
330 
331 static void date_object_free_storage_date(zend_object *object);
332 static void date_object_free_storage_timezone(zend_object *object);
333 static void date_object_free_storage_interval(zend_object *object);
334 static void date_object_free_storage_period(zend_object *object);
335 
336 static zend_object *date_object_new_date(zend_class_entry *class_type);
337 static zend_object *date_object_new_timezone(zend_class_entry *class_type);
338 static zend_object *date_object_new_interval(zend_class_entry *class_type);
339 static zend_object *date_object_new_period(zend_class_entry *class_type);
340 
341 static zend_object *date_object_clone_date(zend_object *this_ptr);
342 static zend_object *date_object_clone_timezone(zend_object *this_ptr);
343 static zend_object *date_object_clone_interval(zend_object *this_ptr);
344 static zend_object *date_object_clone_period(zend_object *this_ptr);
345 
346 static int date_object_compare_date(zval *d1, zval *d2);
347 static HashTable *date_object_get_gc(zend_object *object, zval **table, int *n);
348 static HashTable *date_object_get_properties_for(zend_object *object, zend_prop_purpose purpose);
349 static HashTable *date_object_get_gc_interval(zend_object *object, zval **table, int *n);
350 static HashTable *date_object_get_properties_interval(zend_object *object);
351 static HashTable *date_object_get_gc_period(zend_object *object, zval **table, int *n);
352 static HashTable *date_object_get_properties_for_timezone(zend_object *object, zend_prop_purpose purpose);
353 static HashTable *date_object_get_gc_timezone(zend_object *object, zval **table, int *n);
354 static HashTable *date_object_get_debug_info_timezone(zend_object *object, int *is_temp);
355 static void php_timezone_to_string(php_timezone_obj *tzobj, zval *zv);
356 
357 static int date_interval_compare_objects(zval *o1, zval *o2);
358 static zval *date_interval_read_property(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv);
359 static zval *date_interval_write_property(zend_object *object, zend_string *member, zval *value, void **cache_slot);
360 static zval *date_interval_get_property_ptr_ptr(zend_object *object, zend_string *member, int type, void **cache_slot);
361 static zval *date_period_read_property(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv);
362 static zval *date_period_write_property(zend_object *object, zend_string *name, zval *value, void **cache_slot);
363 static zval *date_period_get_property_ptr_ptr(zend_object *object, zend_string *name, int type, void **cache_slot);
364 
365 static int date_object_compare_timezone(zval *tz1, zval *tz2);
366 
367 /* {{{ Module struct */
368 zend_module_entry date_module_entry = {
369 	STANDARD_MODULE_HEADER_EX,
370 	NULL,
371 	NULL,
372 	"date",                     /* extension name */
373 	ext_functions,              /* function list */
374 	PHP_MINIT(date),            /* process startup */
375 	PHP_MSHUTDOWN(date),        /* process shutdown */
376 	PHP_RINIT(date),            /* request startup */
377 	PHP_RSHUTDOWN(date),        /* request shutdown */
378 	PHP_MINFO(date),            /* extension info */
379 	PHP_DATE_VERSION,                /* extension version */
380 	PHP_MODULE_GLOBALS(date),   /* globals descriptor */
381 	PHP_GINIT(date),            /* globals ctor */
382 	NULL,                       /* globals dtor */
383 	ZEND_MODULE_POST_ZEND_DEACTIVATE_N(date), /* post deactivate */
384 	STANDARD_MODULE_PROPERTIES_EX
385 };
386 /* }}} */
387 
388 
389 /* {{{ PHP_GINIT_FUNCTION */
PHP_GINIT_FUNCTION(date)390 static PHP_GINIT_FUNCTION(date)
391 {
392 	date_globals->default_timezone = NULL;
393 	date_globals->timezone = NULL;
394 	date_globals->tzcache = NULL;
395 }
396 /* }}} */
397 
398 
_php_date_tzinfo_dtor(zval * zv)399 static void _php_date_tzinfo_dtor(zval *zv) /* {{{ */
400 {
401 	timelib_tzinfo *tzi = (timelib_tzinfo*)Z_PTR_P(zv);
402 
403 	timelib_tzinfo_dtor(tzi);
404 } /* }}} */
405 
406 /* {{{ PHP_RINIT_FUNCTION */
PHP_RINIT_FUNCTION(date)407 PHP_RINIT_FUNCTION(date)
408 {
409 	if (DATEG(timezone)) {
410 		efree(DATEG(timezone));
411 	}
412 	DATEG(timezone) = NULL;
413 	DATEG(tzcache) = NULL;
414 	DATEG(last_errors) = NULL;
415 
416 	return SUCCESS;
417 }
418 /* }}} */
419 
420 /* {{{ PHP_RSHUTDOWN_FUNCTION */
PHP_RSHUTDOWN_FUNCTION(date)421 PHP_RSHUTDOWN_FUNCTION(date)
422 {
423 	if (DATEG(timezone)) {
424 		efree(DATEG(timezone));
425 	}
426 	DATEG(timezone) = NULL;
427 
428 	return SUCCESS;
429 }
430 /* }}} */
431 
ZEND_MODULE_POST_ZEND_DEACTIVATE_D(date)432 ZEND_MODULE_POST_ZEND_DEACTIVATE_D(date)
433 {
434 	if (DATEG(tzcache)) {
435 		zend_hash_destroy(DATEG(tzcache));
436 		FREE_HASHTABLE(DATEG(tzcache));
437 		DATEG(tzcache) = NULL;
438 	}
439 
440 	if (DATEG(last_errors)) {
441 		timelib_error_container_dtor(DATEG(last_errors));
442 		DATEG(last_errors) = NULL;
443 	}
444 
445 	return SUCCESS;
446 }
447 
448 #define DATE_TIMEZONEDB      php_date_global_timezone_db ? php_date_global_timezone_db : timelib_builtin_db()
449 
450 /* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(date)451 PHP_MINIT_FUNCTION(date)
452 {
453 	REGISTER_INI_ENTRIES();
454 	date_register_classes();
455 	register_php_date_symbols(module_number);
456 
457 	php_date_global_timezone_db = NULL;
458 	php_date_global_timezone_db_enabled = 0;
459 	DATEG(last_errors) = NULL;
460 	return SUCCESS;
461 }
462 /* }}} */
463 
464 /* {{{ PHP_MSHUTDOWN_FUNCTION */
PHP_MSHUTDOWN_FUNCTION(date)465 PHP_MSHUTDOWN_FUNCTION(date)
466 {
467 	UNREGISTER_INI_ENTRIES();
468 
469 	if (DATEG(last_errors)) {
470 		timelib_error_container_dtor(DATEG(last_errors));
471 	}
472 
473 #ifndef ZTS
474 	DATEG(default_timezone) = NULL;
475 #endif
476 
477 	return SUCCESS;
478 }
479 /* }}} */
480 
481 /* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(date)482 PHP_MINFO_FUNCTION(date)
483 {
484 	const timelib_tzdb *tzdb = DATE_TIMEZONEDB;
485 
486 	php_info_print_table_start();
487 	php_info_print_table_row(2, "date/time support", "enabled");
488 	php_info_print_table_row(2, "timelib version", TIMELIB_ASCII_VERSION);
489 	php_info_print_table_row(2, "\"Olson\" Timezone Database Version", tzdb->version);
490 	php_info_print_table_row(2, "Timezone Database", php_date_global_timezone_db_enabled ? "external" : "internal");
491 	php_info_print_table_row(2, "Default timezone", guess_timezone(tzdb));
492 	php_info_print_table_end();
493 
494 	DISPLAY_INI_ENTRIES();
495 }
496 /* }}} */
497 
498 /* {{{ Timezone Cache functions */
php_date_parse_tzfile(const char * formal_tzname,const timelib_tzdb * tzdb)499 static timelib_tzinfo *php_date_parse_tzfile(const char *formal_tzname, const timelib_tzdb *tzdb)
500 {
501 	timelib_tzinfo *tzi;
502 	int dummy_error_code;
503 
504 	if(!DATEG(tzcache)) {
505 		ALLOC_HASHTABLE(DATEG(tzcache));
506 		zend_hash_init(DATEG(tzcache), 4, NULL, _php_date_tzinfo_dtor, 0);
507 	}
508 
509 	if ((tzi = zend_hash_str_find_ptr(DATEG(tzcache), formal_tzname, strlen(formal_tzname))) != NULL) {
510 		return tzi;
511 	}
512 
513 	tzi = timelib_parse_tzfile(formal_tzname, tzdb, &dummy_error_code);
514 	if (tzi) {
515 		zend_hash_str_add_ptr(DATEG(tzcache), formal_tzname, strlen(formal_tzname), tzi);
516 	}
517 	return tzi;
518 }
519 
php_date_parse_tzfile_wrapper(const char * formal_tzname,const timelib_tzdb * tzdb,int * dummy_error_code)520 static timelib_tzinfo *php_date_parse_tzfile_wrapper(const char *formal_tzname, const timelib_tzdb *tzdb, int *dummy_error_code)
521 {
522 	return php_date_parse_tzfile(formal_tzname, tzdb);
523 }
524 /* }}} */
525 
526 /* Callback to check the date.timezone only when changed increases performance */
527 /* {{{ static PHP_INI_MH(OnUpdate_date_timezone) */
PHP_INI_MH(OnUpdate_date_timezone)528 static PHP_INI_MH(OnUpdate_date_timezone)
529 {
530 	if (new_value && !timelib_timezone_id_is_valid(ZSTR_VAL(new_value), DATE_TIMEZONEDB)) {
531 		php_error_docref(
532 			NULL, E_WARNING,
533 			"Invalid date.timezone value '%s', using '%s' instead",
534 			ZSTR_VAL(new_value),
535 			DATEG(default_timezone) ? DATEG(default_timezone) : "UTC"
536 		);
537 		return FAILURE;
538 	}
539 
540 	if (OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage) == FAILURE) {
541 		return FAILURE;
542 	}
543 
544 	return SUCCESS;
545 }
546 /* }}} */
547 
548 /* {{{ Helper functions */
guess_timezone(const timelib_tzdb * tzdb)549 static const char* guess_timezone(const timelib_tzdb *tzdb)
550 {
551 	/* Checking whether timezone has been set with date_default_timezone_set() */
552 	if (DATEG(timezone) && (strlen(DATEG(timezone))) > 0) {
553 		return DATEG(timezone);
554 	}
555 	/* Check config setting for default timezone */
556 	if (!DATEG(default_timezone)) {
557 		/* Special case: ext/date wasn't initialized yet */
558 		zval *ztz;
559 
560 		if (NULL != (ztz = cfg_get_entry("date.timezone", sizeof("date.timezone")))
561 			&& Z_TYPE_P(ztz) == IS_STRING && Z_STRLEN_P(ztz) > 0 && timelib_timezone_id_is_valid(Z_STRVAL_P(ztz), tzdb)) {
562 			return Z_STRVAL_P(ztz);
563 		}
564 	} else if (*DATEG(default_timezone)) {
565 		return DATEG(default_timezone);
566 	}
567 	/* Fallback to UTC */
568 	return "UTC";
569 }
570 
get_timezone_info(void)571 PHPAPI timelib_tzinfo *get_timezone_info(void)
572 {
573 	timelib_tzinfo *tzi;
574 
575 	const char *tz = guess_timezone(DATE_TIMEZONEDB);
576 	tzi = php_date_parse_tzfile(tz, DATE_TIMEZONEDB);
577 	if (! tzi) {
578 		zend_throw_error(date_ce_date_error, "Timezone database is corrupt. Please file a bug report as this should never happen");
579 	}
580 	return tzi;
581 }
582 
update_property(zend_object * object,zend_string * key,zval * prop_val)583 static void update_property(zend_object *object, zend_string *key, zval *prop_val)
584 {
585 	if (ZSTR_LEN(key) > 0 && ZSTR_VAL(key)[0] == '\0') { // not public
586 		const char *class_name, *prop_name;
587 		size_t prop_name_len;
588 
589 		if (zend_unmangle_property_name_ex(key, &class_name, &prop_name, &prop_name_len) == SUCCESS) {
590 			if (class_name[0] != '*') { // private
591 				zend_string *cname;
592 				zend_class_entry *ce;
593 
594 				cname = zend_string_init(class_name, strlen(class_name), 0);
595 				ce = zend_lookup_class(cname);
596 
597 				if (ce) {
598 					zend_update_property(ce, object, prop_name, prop_name_len, prop_val);
599 				}
600 
601 				zend_string_release_ex(cname, 0);
602 			} else { // protected
603 				zend_update_property(object->ce, object, prop_name, prop_name_len, prop_val);
604 			}
605 		}
606 		return;
607 	}
608 
609 	// public
610 	zend_update_property(object->ce, object, ZSTR_VAL(key), ZSTR_LEN(key), prop_val);
611 }
612 /* }}} */
613 
614 
615 /* {{{ date() and gmdate() data */
616 #include "zend_smart_str.h"
617 
618 static const char * const mon_full_names[] = {
619 	"January", "February", "March", "April",
620 	"May", "June", "July", "August",
621 	"September", "October", "November", "December"
622 };
623 
624 static const char * const mon_short_names[] = {
625 	"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
626 };
627 
628 static const char * const day_full_names[] = {
629 	"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
630 };
631 
632 static const char * const day_short_names[] = {
633 	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
634 };
635 
english_suffix(timelib_sll number)636 static const char *english_suffix(timelib_sll number)
637 {
638 	if (number >= 10 && number <= 19) {
639 		return "th";
640 	} else {
641 		switch (number % 10) {
642 			case 1: return "st";
643 			case 2: return "nd";
644 			case 3: return "rd";
645 		}
646 	}
647 	return "th";
648 }
649 /* }}} */
650 
651 /* {{{ day of week helpers */
php_date_full_day_name(timelib_sll y,timelib_sll m,timelib_sll d)652 static const char *php_date_full_day_name(timelib_sll y, timelib_sll m, timelib_sll d)
653 {
654 	timelib_sll day_of_week = timelib_day_of_week(y, m, d);
655 	if (day_of_week < 0) {
656 		return "Unknown";
657 	}
658 	return day_full_names[day_of_week];
659 }
660 
php_date_short_day_name(timelib_sll y,timelib_sll m,timelib_sll d)661 static const char *php_date_short_day_name(timelib_sll y, timelib_sll m, timelib_sll d)
662 {
663 	timelib_sll day_of_week = timelib_day_of_week(y, m, d);
664 	if (day_of_week < 0) {
665 		return "Unknown";
666 	}
667 	return day_short_names[day_of_week];
668 }
669 /* }}} */
670 
671 /* {{{ date_format - (gm)date helper */
date_format(const char * format,size_t format_len,timelib_time * t,bool localtime)672 static zend_string *date_format(const char *format, size_t format_len, timelib_time *t, bool localtime)
673 {
674 	smart_str            string = {0};
675 	size_t               i;
676 	int                  length = 0;
677 	char                 buffer[97];
678 	timelib_time_offset *offset = NULL;
679 	timelib_sll          isoweek, isoyear;
680 	bool                 rfc_colon;
681 	int                  weekYearSet = 0;
682 
683 	if (!format_len) {
684 		return ZSTR_EMPTY_ALLOC();
685 	}
686 
687 	if (localtime) {
688 		if (t->zone_type == TIMELIB_ZONETYPE_ABBR) {
689 			offset = timelib_time_offset_ctor();
690 			offset->offset = (t->z + (t->dst * 3600));
691 			offset->leap_secs = 0;
692 			offset->is_dst = t->dst;
693 			offset->abbr = timelib_strdup(t->tz_abbr);
694 		} else if (t->zone_type == TIMELIB_ZONETYPE_OFFSET) {
695 			offset = timelib_time_offset_ctor();
696 			offset->offset = (t->z);
697 			offset->leap_secs = 0;
698 			offset->is_dst = 0;
699 			offset->abbr = timelib_malloc(9); /* GMT±xxxx\0 */
700 			snprintf(offset->abbr, 9, "GMT%c%02d%02d",
701 			                          (offset->offset < 0) ? '-' : '+',
702 			                          abs(offset->offset / 3600),
703 			                          abs((offset->offset % 3600) / 60));
704 		} else {
705 			offset = timelib_get_time_zone_info(t->sse, t->tz_info);
706 		}
707 	}
708 
709 	for (i = 0; i < format_len; i++) {
710 		rfc_colon = 0;
711 		switch (format[i]) {
712 			/* day */
713 			case 'd': length = slprintf(buffer, sizeof(buffer), "%02d", (int) t->d); break;
714 			case 'D': length = slprintf(buffer, sizeof(buffer), "%s", php_date_short_day_name(t->y, t->m, t->d)); break;
715 			case 'j': length = slprintf(buffer, sizeof(buffer), "%d", (int) t->d); break;
716 			case 'l': length = slprintf(buffer, sizeof(buffer), "%s", php_date_full_day_name(t->y, t->m, t->d)); break;
717 			case 'S': length = slprintf(buffer, sizeof(buffer), "%s", english_suffix(t->d)); break;
718 			case 'w': length = slprintf(buffer, sizeof(buffer), "%d", (int) timelib_day_of_week(t->y, t->m, t->d)); break;
719 			case 'N': length = slprintf(buffer, sizeof(buffer), "%d", (int) timelib_iso_day_of_week(t->y, t->m, t->d)); break;
720 			case 'z': length = slprintf(buffer, sizeof(buffer), "%d", (int) timelib_day_of_year(t->y, t->m, t->d)); break;
721 
722 			/* week */
723 			case 'W':
724 				if(!weekYearSet) { timelib_isoweek_from_date(t->y, t->m, t->d, &isoweek, &isoyear); weekYearSet = 1; }
725 				length = slprintf(buffer, sizeof(buffer), "%02d", (int) isoweek); break; /* iso weeknr */
726 			case 'o':
727 				if(!weekYearSet) { timelib_isoweek_from_date(t->y, t->m, t->d, &isoweek, &isoyear); weekYearSet = 1; }
728 				length = slprintf(buffer, sizeof(buffer), ZEND_LONG_FMT, (zend_long) isoyear); break; /* iso year */
729 
730 			/* month */
731 			case 'F': length = slprintf(buffer, sizeof(buffer), "%s", mon_full_names[t->m - 1]); break;
732 			case 'm': length = slprintf(buffer, sizeof(buffer), "%02d", (int) t->m); break;
733 			case 'M': length = slprintf(buffer, sizeof(buffer), "%s", mon_short_names[t->m - 1]); break;
734 			case 'n': length = slprintf(buffer, sizeof(buffer), "%d", (int) t->m); break;
735 			case 't': length = slprintf(buffer, sizeof(buffer), "%d", (int) timelib_days_in_month(t->y, t->m)); break;
736 
737 			/* year */
738 			case 'L': length = slprintf(buffer, sizeof(buffer), "%d", timelib_is_leap((int) t->y)); break;
739 			case 'y': length = slprintf(buffer, sizeof(buffer), "%02d", (int) (t->y % 100)); break;
740 			case 'Y': length = slprintf(buffer, sizeof(buffer), "%s%04lld", t->y < 0 ? "-" : "", php_date_llabs((timelib_sll) t->y)); break;
741 			case 'x': length = slprintf(buffer, sizeof(buffer), "%s%04lld", t->y < 0 ? "-" : (t->y >= 10000 ? "+" : ""), php_date_llabs((timelib_sll) t->y)); break;
742 			case 'X': length = slprintf(buffer, sizeof(buffer), "%s%04lld", t->y < 0 ? "-" : "+", php_date_llabs((timelib_sll) t->y)); break;
743 
744 			/* time */
745 			case 'a': length = slprintf(buffer, sizeof(buffer), "%s", t->h >= 12 ? "pm" : "am"); break;
746 			case 'A': length = slprintf(buffer, sizeof(buffer), "%s", t->h >= 12 ? "PM" : "AM"); break;
747 			case 'B': {
748 				int retval = ((((long)t->sse)-(((long)t->sse) - ((((long)t->sse) % 86400) + 3600))) * 10);
749 				if (retval < 0) {
750 					retval += 864000;
751 				}
752 				/* Make sure to do this on a positive int to avoid rounding errors */
753 				retval = (retval / 864)  % 1000;
754 				length = slprintf(buffer, sizeof(buffer), "%03d", retval);
755 				break;
756 			}
757 			case 'g': length = slprintf(buffer, sizeof(buffer), "%d", (t->h % 12) ? (int) t->h % 12 : 12); break;
758 			case 'G': length = slprintf(buffer, sizeof(buffer), "%d", (int) t->h); break;
759 			case 'h': length = slprintf(buffer, sizeof(buffer), "%02d", (t->h % 12) ? (int) t->h % 12 : 12); break;
760 			case 'H': length = slprintf(buffer, sizeof(buffer), "%02d", (int) t->h); break;
761 			case 'i': length = slprintf(buffer, sizeof(buffer), "%02d", (int) t->i); break;
762 			case 's': length = slprintf(buffer, sizeof(buffer), "%02d", (int) t->s); break;
763 			case 'u': length = slprintf(buffer, sizeof(buffer), "%06d", (int) floor(t->us)); break;
764 			case 'v': length = slprintf(buffer, sizeof(buffer), "%03d", (int) floor(t->us / 1000)); break;
765 
766 			/* timezone */
767 			case 'I': length = slprintf(buffer, sizeof(buffer), "%d", localtime ? offset->is_dst : 0); break;
768 			case 'p':
769 				if (!localtime || strcmp(offset->abbr, "UTC") == 0 || strcmp(offset->abbr, "Z") == 0 || strcmp(offset->abbr, "GMT+0000") == 0) {
770 					length = slprintf(buffer, sizeof(buffer), "%s", "Z");
771 					break;
772 				}
773 				ZEND_FALLTHROUGH;
774 			case 'P': rfc_colon = 1; ZEND_FALLTHROUGH;
775 			case 'O': length = slprintf(buffer, sizeof(buffer), "%c%02d%s%02d",
776 											localtime ? ((offset->offset < 0) ? '-' : '+') : '+',
777 											localtime ? abs(offset->offset / 3600) : 0,
778 											rfc_colon ? ":" : "",
779 											localtime ? abs((offset->offset % 3600) / 60) : 0
780 							  );
781 					  break;
782 			case 'T': length = slprintf(buffer, sizeof(buffer), "%s", localtime ? offset->abbr : "GMT"); break;
783 			case 'e': if (!localtime) {
784 					      length = slprintf(buffer, sizeof(buffer), "%s", "UTC");
785 					  } else {
786 						  switch (t->zone_type) {
787 							  case TIMELIB_ZONETYPE_ID:
788 								  length = slprintf(buffer, sizeof(buffer), "%s", t->tz_info->name);
789 								  break;
790 							  case TIMELIB_ZONETYPE_ABBR:
791 								  length = slprintf(buffer, sizeof(buffer), "%s", offset->abbr);
792 								  break;
793 							  case TIMELIB_ZONETYPE_OFFSET:
794 								  length = slprintf(buffer, sizeof(buffer), "%c%02d:%02d",
795 												((offset->offset < 0) ? '-' : '+'),
796 												abs(offset->offset / 3600),
797 												abs((offset->offset % 3600) / 60)
798 										   );
799 								  break;
800 						  }
801 					  }
802 					  break;
803 			case 'Z': length = slprintf(buffer, sizeof(buffer), "%d", localtime ? offset->offset : 0); break;
804 
805 			/* full date/time */
806 			case 'c': length = slprintf(buffer, sizeof(buffer), "%04" ZEND_LONG_FMT_SPEC "-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
807 							                (zend_long) t->y, (int) t->m, (int) t->d,
808 											(int) t->h, (int) t->i, (int) t->s,
809 											localtime ? ((offset->offset < 0) ? '-' : '+') : '+',
810 											localtime ? abs(offset->offset / 3600) : 0,
811 											localtime ? abs((offset->offset % 3600) / 60) : 0
812 							  );
813 					  break;
814 			case 'r': length = slprintf(buffer, sizeof(buffer), "%3s, %02d %3s %04" ZEND_LONG_FMT_SPEC " %02d:%02d:%02d %c%02d%02d",
815 							                php_date_short_day_name(t->y, t->m, t->d),
816 											(int) t->d, mon_short_names[t->m - 1],
817 											(zend_long) t->y, (int) t->h, (int) t->i, (int) t->s,
818 											localtime ? ((offset->offset < 0) ? '-' : '+') : '+',
819 											localtime ? abs(offset->offset / 3600) : 0,
820 											localtime ? abs((offset->offset % 3600) / 60) : 0
821 							  );
822 					  break;
823 			case 'U': length = slprintf(buffer, sizeof(buffer), "%lld", (timelib_sll) t->sse); break;
824 
825 			case '\\': if (i < format_len) i++; ZEND_FALLTHROUGH;
826 
827 			default: buffer[0] = format[i]; buffer[1] = '\0'; length = 1; break;
828 		}
829 		smart_str_appendl(&string, buffer, length);
830 	}
831 
832 	smart_str_0(&string);
833 
834 	if (localtime) {
835 		timelib_time_offset_dtor(offset);
836 	}
837 
838 	return string.s;
839 }
840 
php_format_date_obj(const char * format,size_t format_len,php_date_obj * date_obj)841 PHPAPI zend_string *php_format_date_obj(const char *format, size_t format_len, php_date_obj *date_obj)
842 {
843 	if (!date_obj->time) {
844 		return NULL;
845 	}
846 
847 	return date_format(format, format_len, date_obj->time, date_obj->time->is_localtime);
848 }
849 
php_date(INTERNAL_FUNCTION_PARAMETERS,bool localtime)850 static void php_date(INTERNAL_FUNCTION_PARAMETERS, bool localtime)
851 {
852 	zend_string *format;
853 	zend_long    ts;
854 	bool    ts_is_null = 1;
855 
856 	ZEND_PARSE_PARAMETERS_START(1, 2)
857 		Z_PARAM_STR(format)
858 		Z_PARAM_OPTIONAL
859 		Z_PARAM_LONG_OR_NULL(ts, ts_is_null)
860 	ZEND_PARSE_PARAMETERS_END();
861 
862 	if (ts_is_null) {
863 		ts = php_time();
864 	}
865 
866 	RETURN_STR(php_format_date(ZSTR_VAL(format), ZSTR_LEN(format), ts, localtime));
867 }
868 /* }}} */
869 
php_format_date(const char * format,size_t format_len,time_t ts,bool localtime)870 PHPAPI zend_string *php_format_date(const char *format, size_t format_len, time_t ts, bool localtime) /* {{{ */
871 {
872 	timelib_time   *t;
873 	timelib_tzinfo *tzi;
874 	zend_string *string;
875 
876 	t = timelib_time_ctor();
877 
878 	if (localtime) {
879 		tzi = get_timezone_info();
880 		t->tz_info = tzi;
881 		t->zone_type = TIMELIB_ZONETYPE_ID;
882 		timelib_unixtime2local(t, ts);
883 	} else {
884 		tzi = NULL;
885 		timelib_unixtime2gmt(t, ts);
886 	}
887 
888 	string = date_format(format, format_len, t, localtime);
889 
890 	timelib_time_dtor(t);
891 	return string;
892 }
893 /* }}} */
894 
895 /* {{{ php_idate */
php_idate(char format,time_t ts,bool localtime)896 PHPAPI int php_idate(char format, time_t ts, bool localtime)
897 {
898 	timelib_time   *t;
899 	timelib_tzinfo *tzi;
900 	int retval = -1;
901 	timelib_time_offset *offset = NULL;
902 	timelib_sll isoweek, isoyear;
903 
904 	t = timelib_time_ctor();
905 
906 	if (!localtime) {
907 		tzi = get_timezone_info();
908 		t->tz_info = tzi;
909 		t->zone_type = TIMELIB_ZONETYPE_ID;
910 		timelib_unixtime2local(t, ts);
911 	} else {
912 		tzi = NULL;
913 		timelib_unixtime2gmt(t, ts);
914 	}
915 
916 	if (!localtime) {
917 		if (t->zone_type == TIMELIB_ZONETYPE_ABBR) {
918 			offset = timelib_time_offset_ctor();
919 			offset->offset = (t->z + (t->dst * 3600));
920 			offset->leap_secs = 0;
921 			offset->is_dst = t->dst;
922 			offset->abbr = timelib_strdup(t->tz_abbr);
923 		} else if (t->zone_type == TIMELIB_ZONETYPE_OFFSET) {
924 			offset = timelib_time_offset_ctor();
925 			offset->offset = (t->z + (t->dst * 3600));
926 			offset->leap_secs = 0;
927 			offset->is_dst = t->dst;
928 			offset->abbr = timelib_malloc(9); /* GMT±xxxx\0 */
929 			snprintf(offset->abbr, 9, "GMT%c%02d%02d",
930 			                          (offset->offset < 0) ? '-' : '+',
931 			                          abs(offset->offset / 3600),
932 			                          abs((offset->offset % 3600) / 60));
933 		} else {
934 			offset = timelib_get_time_zone_info(t->sse, t->tz_info);
935 		}
936 	}
937 
938 	timelib_isoweek_from_date(t->y, t->m, t->d, &isoweek, &isoyear);
939 
940 	switch (format) {
941 		/* day */
942 		case 'd': case 'j': retval = (int) t->d; break;
943 
944 		case 'N': retval = (int) timelib_iso_day_of_week(t->y, t->m, t->d); break;
945 		case 'w': retval = (int) timelib_day_of_week(t->y, t->m, t->d); break;
946 		case 'z': retval = (int) timelib_day_of_year(t->y, t->m, t->d); break;
947 
948 		/* week */
949 		case 'W': retval = (int) isoweek; break; /* iso weeknr */
950 
951 		/* month */
952 		case 'm': case 'n': retval = (int) t->m; break;
953 		case 't': retval = (int) timelib_days_in_month(t->y, t->m); break;
954 
955 		/* year */
956 		case 'L': retval = (int) timelib_is_leap((int) t->y); break;
957 		case 'y': retval = (int) (t->y % 100); break;
958 		case 'Y': retval = (int) t->y; break;
959 		case 'o': retval = (int) isoyear; break; /* iso year */
960 
961 		/* Swatch Beat a.k.a. Internet Time */
962 		case 'B':
963 			retval = ((((long)t->sse)-(((long)t->sse) - ((((long)t->sse) % 86400) + 3600))) * 10);
964 			if (retval < 0) {
965 				retval += 864000;
966 			}
967 			/* Make sure to do this on a positive int to avoid rounding errors */
968 			retval = (retval / 864) % 1000;
969 			break;
970 
971 		/* time */
972 		case 'g': case 'h': retval = (int) ((t->h % 12) ? (int) t->h % 12 : 12); break;
973 		case 'H': case 'G': retval = (int) t->h; break;
974 		case 'i': retval = (int) t->i; break;
975 		case 's': retval = (int) t->s; break;
976 
977 		/* timezone */
978 		case 'I': retval = (int) (!localtime ? offset->is_dst : 0); break;
979 		case 'Z': retval = (int) (!localtime ? offset->offset : 0); break;
980 
981 		case 'U': retval = (int) t->sse; break;
982 	}
983 
984 	if (!localtime) {
985 		timelib_time_offset_dtor(offset);
986 	}
987 	timelib_time_dtor(t);
988 
989 	return retval;
990 }
991 /* }}} */
992 
993 /* {{{ Format a local date/time */
PHP_FUNCTION(date)994 PHP_FUNCTION(date)
995 {
996 	php_date(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
997 }
998 /* }}} */
999 
1000 /* {{{ Format a GMT date/time */
PHP_FUNCTION(gmdate)1001 PHP_FUNCTION(gmdate)
1002 {
1003 	php_date(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
1004 }
1005 /* }}} */
1006 
1007 /* {{{ Format a local time/date as integer */
PHP_FUNCTION(idate)1008 PHP_FUNCTION(idate)
1009 {
1010 	zend_string *format;
1011 	zend_long    ts;
1012 	bool    ts_is_null = 1;
1013 	int ret;
1014 
1015 	ZEND_PARSE_PARAMETERS_START(1, 2)
1016 		Z_PARAM_STR(format)
1017 		Z_PARAM_OPTIONAL
1018 		Z_PARAM_LONG_OR_NULL(ts, ts_is_null)
1019 	ZEND_PARSE_PARAMETERS_END();
1020 
1021 	if (ZSTR_LEN(format) != 1) {
1022 		php_error_docref(NULL, E_WARNING, "idate format is one char");
1023 		RETURN_FALSE;
1024 	}
1025 
1026 	if (ts_is_null) {
1027 		ts = php_time();
1028 	}
1029 
1030 	ret = php_idate(ZSTR_VAL(format)[0], ts, 0);
1031 	if (ret == -1) {
1032 		php_error_docref(NULL, E_WARNING, "Unrecognized date format token");
1033 		RETURN_FALSE;
1034 	}
1035 	RETURN_LONG(ret);
1036 }
1037 /* }}} */
1038 
1039 /* {{{ php_date_set_tzdb - NOT THREADSAFE */
php_date_set_tzdb(timelib_tzdb * tzdb)1040 PHPAPI void php_date_set_tzdb(timelib_tzdb *tzdb)
1041 {
1042 	const timelib_tzdb *builtin = timelib_builtin_db();
1043 
1044 	if (php_version_compare(tzdb->version, builtin->version) > 0) {
1045 		php_date_global_timezone_db = tzdb;
1046 		php_date_global_timezone_db_enabled = 1;
1047 	}
1048 }
1049 /* }}} */
1050 
1051 /* {{{ php_parse_date: Backwards compatibility function */
php_parse_date(const char * string,zend_long * now)1052 PHPAPI zend_long php_parse_date(const char *string, zend_long *now)
1053 {
1054 	timelib_time *parsed_time;
1055 	timelib_error_container *error = NULL;
1056 	int           error2;
1057 	zend_long   retval;
1058 
1059 	parsed_time = timelib_strtotime(string, strlen(string), &error, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
1060 	if (error->error_count) {
1061 		timelib_time_dtor(parsed_time);
1062 		timelib_error_container_dtor(error);
1063 		return -1;
1064 	}
1065 	timelib_error_container_dtor(error);
1066 	timelib_update_ts(parsed_time, NULL);
1067 	retval = timelib_date_to_int(parsed_time, &error2);
1068 	timelib_time_dtor(parsed_time);
1069 	if (error2) {
1070 		return -1;
1071 	}
1072 	return retval;
1073 }
1074 /* }}} */
1075 
1076 /* {{{ Convert string representation of date and time to a timestamp */
PHP_FUNCTION(strtotime)1077 PHP_FUNCTION(strtotime)
1078 {
1079 	zend_string *times;
1080 	int parse_error, epoch_does_not_fit_in_zend_long;
1081 	timelib_error_container *error;
1082 	zend_long preset_ts, ts;
1083 	bool preset_ts_is_null = 1;
1084 	timelib_time *t, *now;
1085 	timelib_tzinfo *tzi;
1086 
1087 	ZEND_PARSE_PARAMETERS_START(1, 2)
1088 		Z_PARAM_STR(times)
1089 		Z_PARAM_OPTIONAL
1090 		Z_PARAM_LONG_OR_NULL(preset_ts, preset_ts_is_null)
1091 	ZEND_PARSE_PARAMETERS_END();
1092 
1093 	/* timelib_strtotime() expects the string to not be empty */
1094 	if (ZSTR_LEN(times) == 0) {
1095 		RETURN_FALSE;
1096 	}
1097 
1098 	tzi = get_timezone_info();
1099 	if (!tzi) {
1100 		return;
1101 	}
1102 
1103 	now = timelib_time_ctor();
1104 	now->tz_info = tzi;
1105 	now->zone_type = TIMELIB_ZONETYPE_ID;
1106 	timelib_unixtime2local(now,
1107 		!preset_ts_is_null ? (timelib_sll) preset_ts : (timelib_sll) php_time());
1108 
1109 	t = timelib_strtotime(ZSTR_VAL(times), ZSTR_LEN(times), &error,
1110 		DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
1111 	parse_error = error->error_count;
1112 	timelib_error_container_dtor(error);
1113 	if (parse_error) {
1114 		timelib_time_dtor(now);
1115 		timelib_time_dtor(t);
1116 		RETURN_FALSE;
1117 	}
1118 
1119 	timelib_fill_holes(t, now, TIMELIB_NO_CLONE);
1120 	timelib_update_ts(t, tzi);
1121 	ts = timelib_date_to_int(t, &epoch_does_not_fit_in_zend_long);
1122 
1123 	timelib_time_dtor(now);
1124 	timelib_time_dtor(t);
1125 
1126 	if (epoch_does_not_fit_in_zend_long) {
1127 		php_error_docref(NULL, E_WARNING, "Epoch doesn't fit in a PHP integer");
1128 		RETURN_FALSE;
1129 	}
1130 
1131 	RETURN_LONG(ts);
1132 }
1133 /* }}} */
1134 
1135 /* {{{ php_mktime - (gm)mktime helper */
php_mktime(INTERNAL_FUNCTION_PARAMETERS,bool gmt)1136 PHPAPI void php_mktime(INTERNAL_FUNCTION_PARAMETERS, bool gmt)
1137 {
1138 	zend_long hou, min, sec, mon, day, yea;
1139 	bool min_is_null = 1, sec_is_null = 1, mon_is_null = 1, day_is_null = 1, yea_is_null = 1;
1140 	timelib_time *now;
1141 	timelib_tzinfo *tzi = NULL;
1142 	zend_long ts, adjust_seconds = 0;
1143 	int epoch_does_not_fit_in_zend_long;
1144 
1145 	ZEND_PARSE_PARAMETERS_START(1, 6)
1146 		Z_PARAM_LONG(hou)
1147 		Z_PARAM_OPTIONAL
1148 		Z_PARAM_LONG_OR_NULL(min, min_is_null)
1149 		Z_PARAM_LONG_OR_NULL(sec, sec_is_null)
1150 		Z_PARAM_LONG_OR_NULL(mon, mon_is_null)
1151 		Z_PARAM_LONG_OR_NULL(day, day_is_null)
1152 		Z_PARAM_LONG_OR_NULL(yea, yea_is_null)
1153 	ZEND_PARSE_PARAMETERS_END();
1154 
1155 	/* Initialize structure with current time */
1156 	now = timelib_time_ctor();
1157 	if (gmt) {
1158 		timelib_unixtime2gmt(now, (timelib_sll) php_time());
1159 	} else {
1160 		tzi = get_timezone_info();
1161 		if (!tzi) {
1162 			return;
1163 		}
1164 		now->tz_info = tzi;
1165 		now->zone_type = TIMELIB_ZONETYPE_ID;
1166 		timelib_unixtime2local(now, (timelib_sll) php_time());
1167 	}
1168 
1169 	now->h = hou;
1170 
1171 	if (!min_is_null) {
1172 		now->i = min;
1173 	}
1174 
1175 	if (!sec_is_null) {
1176 		now->s = sec;
1177 	}
1178 
1179 	if (!mon_is_null) {
1180 		now->m = mon;
1181 	}
1182 
1183 	if (!day_is_null) {
1184 		now->d = day;
1185 	}
1186 
1187 	if (!yea_is_null) {
1188 		if (yea >= 0 && yea < 70) {
1189 			yea += 2000;
1190 		} else if (yea >= 70 && yea <= 100) {
1191 			yea += 1900;
1192 		}
1193 		now->y = yea;
1194 	}
1195 
1196 	/* Update the timestamp */
1197 	if (gmt) {
1198 		timelib_update_ts(now, NULL);
1199 	} else {
1200 		timelib_update_ts(now, tzi);
1201 	}
1202 
1203 	/* Clean up and return */
1204 	ts = timelib_date_to_int(now, &epoch_does_not_fit_in_zend_long);
1205 
1206 	if (epoch_does_not_fit_in_zend_long) {
1207 		timelib_time_dtor(now);
1208 		php_error_docref(NULL, E_WARNING, "Epoch doesn't fit in a PHP integer");
1209 		RETURN_FALSE;
1210 	}
1211 
1212 	ts += adjust_seconds;
1213 	timelib_time_dtor(now);
1214 
1215 	RETURN_LONG(ts);
1216 }
1217 /* }}} */
1218 
1219 /* {{{ Get UNIX timestamp for a date */
PHP_FUNCTION(mktime)1220 PHP_FUNCTION(mktime)
1221 {
1222 	php_mktime(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
1223 }
1224 /* }}} */
1225 
1226 /* {{{ Get UNIX timestamp for a GMT date */
PHP_FUNCTION(gmmktime)1227 PHP_FUNCTION(gmmktime)
1228 {
1229 	php_mktime(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
1230 }
1231 /* }}} */
1232 
1233 /* {{{ Returns true(1) if it is a valid date in gregorian calendar */
PHP_FUNCTION(checkdate)1234 PHP_FUNCTION(checkdate)
1235 {
1236 	zend_long m, d, y;
1237 
1238 	ZEND_PARSE_PARAMETERS_START(3, 3)
1239 		Z_PARAM_LONG(m)
1240 		Z_PARAM_LONG(d)
1241 		Z_PARAM_LONG(y)
1242 	ZEND_PARSE_PARAMETERS_END();
1243 
1244 	if (y < 1 || y > 32767 || !timelib_valid_date(y, m, d)) {
1245 		RETURN_FALSE;
1246 	}
1247 	RETURN_TRUE;	/* True : This month, day, year arguments are valid */
1248 }
1249 /* }}} */
1250 
1251 /* {{{ php_strftime - (gm)strftime helper */
php_strftime(INTERNAL_FUNCTION_PARAMETERS,bool gmt)1252 PHPAPI void php_strftime(INTERNAL_FUNCTION_PARAMETERS, bool gmt)
1253 {
1254 	zend_string         *format;
1255 	zend_long            timestamp;
1256 	bool            timestamp_is_null = 1;
1257 	struct tm            ta;
1258 	int                  max_reallocs = 5;
1259 	size_t               buf_len = 256, real_len;
1260 	timelib_time        *ts;
1261 	timelib_tzinfo      *tzi;
1262 	timelib_time_offset *offset = NULL;
1263 	zend_string 		*buf;
1264 
1265 	ZEND_PARSE_PARAMETERS_START(1, 2)
1266 		Z_PARAM_STR(format)
1267 		Z_PARAM_OPTIONAL
1268 		Z_PARAM_LONG_OR_NULL(timestamp, timestamp_is_null)
1269 	ZEND_PARSE_PARAMETERS_END();
1270 
1271 	if (ZSTR_LEN(format) == 0) {
1272 		RETURN_FALSE;
1273 	}
1274 
1275 	if (timestamp_is_null) {
1276 		timestamp = (zend_long) php_time();
1277 	}
1278 
1279 	ts = timelib_time_ctor();
1280 	if (gmt) {
1281 		tzi = NULL;
1282 		timelib_unixtime2gmt(ts, (timelib_sll) timestamp);
1283 	} else {
1284 		tzi = get_timezone_info();
1285 		if (!tzi) {
1286 			return;
1287 		}
1288 		ts->tz_info = tzi;
1289 		ts->zone_type = TIMELIB_ZONETYPE_ID;
1290 		timelib_unixtime2local(ts, (timelib_sll) timestamp);
1291 	}
1292 	ta.tm_sec   = ts->s;
1293 	ta.tm_min   = ts->i;
1294 	ta.tm_hour  = ts->h;
1295 	ta.tm_mday  = ts->d;
1296 	ta.tm_mon   = ts->m - 1;
1297 	ta.tm_year  = ts->y - 1900;
1298 	ta.tm_wday  = timelib_day_of_week(ts->y, ts->m, ts->d);
1299 	ta.tm_yday  = timelib_day_of_year(ts->y, ts->m, ts->d);
1300 	if (gmt) {
1301 		ta.tm_isdst = 0;
1302 #ifdef HAVE_STRUCT_TM_TM_GMTOFF
1303 		ta.tm_gmtoff = 0;
1304 #endif
1305 #ifdef HAVE_STRUCT_TM_TM_ZONE
1306 		ta.tm_zone = "GMT";
1307 #endif
1308 	} else {
1309 		offset = timelib_get_time_zone_info(timestamp, tzi);
1310 
1311 		ta.tm_isdst = offset->is_dst;
1312 #ifdef HAVE_STRUCT_TM_TM_GMTOFF
1313 		ta.tm_gmtoff = offset->offset;
1314 #endif
1315 #ifdef HAVE_STRUCT_TM_TM_ZONE
1316 		ta.tm_zone = offset->abbr;
1317 #endif
1318 	}
1319 
1320 	/* VS2012 crt has a bug where strftime crash with %z and %Z format when the
1321 	   initial buffer is too small. See
1322 	   http://connect.microsoft.com/VisualStudio/feedback/details/759720/vs2012-strftime-crash-with-z-formatting-code */
1323 	buf = zend_string_alloc(buf_len, 0);
1324 	while ((real_len = strftime(ZSTR_VAL(buf), buf_len, ZSTR_VAL(format), &ta)) == buf_len || real_len == 0) {
1325 		buf_len *= 2;
1326 		buf = zend_string_extend(buf, buf_len, 0);
1327 		if (!--max_reallocs) {
1328 			break;
1329 		}
1330 	}
1331 #ifdef PHP_WIN32
1332 	/* VS2012 strftime() returns number of characters, not bytes.
1333 		See VC++11 bug id 766205. */
1334 	if (real_len > 0) {
1335 		real_len = strlen(buf->val);
1336 	}
1337 #endif
1338 
1339 	timelib_time_dtor(ts);
1340 	if (!gmt) {
1341 		timelib_time_offset_dtor(offset);
1342 	}
1343 
1344 	if (real_len && real_len != buf_len) {
1345 		buf = zend_string_truncate(buf, real_len, 0);
1346 		RETURN_NEW_STR(buf);
1347 	}
1348 	zend_string_efree(buf);
1349 	RETURN_FALSE;
1350 }
1351 /* }}} */
1352 
1353 /* {{{ Format a local time/date according to locale settings */
PHP_FUNCTION(strftime)1354 PHP_FUNCTION(strftime)
1355 {
1356 	php_strftime(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
1357 }
1358 /* }}} */
1359 
1360 /* {{{ Format a GMT/UCT time/date according to locale settings */
PHP_FUNCTION(gmstrftime)1361 PHP_FUNCTION(gmstrftime)
1362 {
1363 	php_strftime(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
1364 }
1365 /* }}} */
1366 
1367 /* {{{ Return current UNIX timestamp */
PHP_FUNCTION(time)1368 PHP_FUNCTION(time)
1369 {
1370 	ZEND_PARSE_PARAMETERS_NONE();
1371 
1372 	RETURN_LONG((zend_long)php_time());
1373 }
1374 /* }}} */
1375 
1376 /* {{{ Returns the results of the C system call localtime as an associative array if the associative_array argument is set to 1 other wise it is a regular array */
PHP_FUNCTION(localtime)1377 PHP_FUNCTION(localtime)
1378 {
1379 	zend_long timestamp;
1380 	bool timestamp_is_null = 1;
1381 	bool associative = 0;
1382 	timelib_tzinfo *tzi;
1383 	timelib_time   *ts;
1384 
1385 	ZEND_PARSE_PARAMETERS_START(0, 2)
1386 		Z_PARAM_OPTIONAL
1387 		Z_PARAM_LONG_OR_NULL(timestamp, timestamp_is_null)
1388 		Z_PARAM_BOOL(associative)
1389 	ZEND_PARSE_PARAMETERS_END();
1390 
1391 	if (timestamp_is_null) {
1392 		timestamp = (zend_long) php_time();
1393 	}
1394 
1395 	tzi = get_timezone_info();
1396 	if (!tzi) {
1397 		RETURN_THROWS();
1398 	}
1399 	ts = timelib_time_ctor();
1400 	ts->tz_info = tzi;
1401 	ts->zone_type = TIMELIB_ZONETYPE_ID;
1402 	timelib_unixtime2local(ts, (timelib_sll) timestamp);
1403 
1404 	array_init(return_value);
1405 
1406 	if (associative) {
1407 		add_assoc_long(return_value, "tm_sec",   ts->s);
1408 		add_assoc_long(return_value, "tm_min",   ts->i);
1409 		add_assoc_long(return_value, "tm_hour",  ts->h);
1410 		add_assoc_long(return_value, "tm_mday",  ts->d);
1411 		add_assoc_long(return_value, "tm_mon",   ts->m - 1);
1412 		add_assoc_long(return_value, "tm_year",  ts->y - 1900);
1413 		add_assoc_long(return_value, "tm_wday",  timelib_day_of_week(ts->y, ts->m, ts->d));
1414 		add_assoc_long(return_value, "tm_yday",  timelib_day_of_year(ts->y, ts->m, ts->d));
1415 		add_assoc_long(return_value, "tm_isdst", ts->dst);
1416 	} else {
1417 		add_next_index_long(return_value, ts->s);
1418 		add_next_index_long(return_value, ts->i);
1419 		add_next_index_long(return_value, ts->h);
1420 		add_next_index_long(return_value, ts->d);
1421 		add_next_index_long(return_value, ts->m - 1);
1422 		add_next_index_long(return_value, ts->y- 1900);
1423 		add_next_index_long(return_value, timelib_day_of_week(ts->y, ts->m, ts->d));
1424 		add_next_index_long(return_value, timelib_day_of_year(ts->y, ts->m, ts->d));
1425 		add_next_index_long(return_value, ts->dst);
1426 	}
1427 
1428 	timelib_time_dtor(ts);
1429 }
1430 /* }}} */
1431 
1432 /* {{{ Get date/time information */
PHP_FUNCTION(getdate)1433 PHP_FUNCTION(getdate)
1434 {
1435 	zend_long timestamp;
1436 	bool timestamp_is_null = 1;
1437 	timelib_tzinfo *tzi;
1438 	timelib_time   *ts;
1439 
1440 	ZEND_PARSE_PARAMETERS_START(0, 1)
1441 		Z_PARAM_OPTIONAL
1442 		Z_PARAM_LONG_OR_NULL(timestamp, timestamp_is_null)
1443 	ZEND_PARSE_PARAMETERS_END();
1444 
1445 	if (timestamp_is_null) {
1446 		timestamp = (zend_long) php_time();
1447 	}
1448 
1449 	tzi = get_timezone_info();
1450 	if (!tzi) {
1451 		RETURN_THROWS();
1452 	}
1453 	ts = timelib_time_ctor();
1454 	ts->tz_info = tzi;
1455 	ts->zone_type = TIMELIB_ZONETYPE_ID;
1456 	timelib_unixtime2local(ts, (timelib_sll) timestamp);
1457 
1458 	array_init(return_value);
1459 
1460 	add_assoc_long(return_value, "seconds", ts->s);
1461 	add_assoc_long(return_value, "minutes", ts->i);
1462 	add_assoc_long(return_value, "hours", ts->h);
1463 	add_assoc_long(return_value, "mday", ts->d);
1464 	add_assoc_long(return_value, "wday", timelib_day_of_week(ts->y, ts->m, ts->d));
1465 	add_assoc_long(return_value, "mon", ts->m);
1466 	add_assoc_long(return_value, "year", ts->y);
1467 	add_assoc_long(return_value, "yday", timelib_day_of_year(ts->y, ts->m, ts->d));
1468 	add_assoc_string(return_value, "weekday", php_date_full_day_name(ts->y, ts->m, ts->d));
1469 	add_assoc_string(return_value, "month", mon_full_names[ts->m - 1]);
1470 	add_index_long(return_value, 0, timestamp);
1471 
1472 	timelib_time_dtor(ts);
1473 }
1474 /* }}} */
1475 
create_date_period_datetime(timelib_time * datetime,zend_class_entry * ce,zval * zv)1476 static void create_date_period_datetime(timelib_time *datetime, zend_class_entry *ce, zval *zv)
1477 {
1478 	if (datetime) {
1479 		php_date_obj *date_obj;
1480 
1481 		object_init_ex(zv, ce);
1482 		date_obj = Z_PHPDATE_P(zv);
1483 		date_obj->time = timelib_time_clone(datetime);
1484 	} else {
1485 		ZVAL_NULL(zv);
1486 	}
1487 }
1488 
create_date_period_interval(timelib_rel_time * interval,zval * zv)1489 static void create_date_period_interval(timelib_rel_time *interval, zval *zv)
1490 {
1491 	if (interval) {
1492 		php_interval_obj *interval_obj;
1493 
1494 		object_init_ex(zv, date_ce_interval);
1495 		interval_obj = Z_PHPINTERVAL_P(zv);
1496 		interval_obj->diff = timelib_rel_time_clone(interval);
1497 		interval_obj->initialized = 1;
1498 	} else {
1499 		ZVAL_NULL(zv);
1500 	}
1501 }
1502 
write_date_period_property(zend_object * obj,const char * name,const size_t name_len,zval * zv)1503 static void write_date_period_property(zend_object *obj, const char *name, const size_t name_len, zval *zv)
1504 {
1505 	zend_string *property_name = zend_string_init(name, name_len, 0);
1506 
1507 	zend_std_write_property(obj, property_name, zv, NULL);
1508 
1509 	zval_ptr_dtor(zv);
1510 	zend_string_release(property_name);
1511 }
1512 
initialize_date_period_properties(php_period_obj * period_obj)1513 static void initialize_date_period_properties(php_period_obj *period_obj)
1514 {
1515 	zval zv;
1516 
1517 	if (UNEXPECTED(!period_obj->std.properties)) {
1518 		rebuild_object_properties(&period_obj->std);
1519 	}
1520 
1521 	create_date_period_datetime(period_obj->start, period_obj->start_ce, &zv);
1522 	write_date_period_property(&period_obj->std, "start", sizeof("start") - 1, &zv);
1523 
1524 	create_date_period_datetime(period_obj->current, period_obj->start_ce, &zv);
1525 	write_date_period_property(&period_obj->std, "current", sizeof("current") - 1, &zv);
1526 
1527 	create_date_period_datetime(period_obj->end, period_obj->start_ce, &zv);
1528 	write_date_period_property(&period_obj->std, "end", sizeof("end") - 1, &zv);
1529 
1530 	create_date_period_interval(period_obj->interval, &zv);
1531 	write_date_period_property(&period_obj->std, "interval", sizeof("interval") - 1, &zv);
1532 
1533 	ZVAL_LONG(&zv, (zend_long) period_obj->recurrences);
1534 	write_date_period_property(&period_obj->std, "recurrences", sizeof("recurrences") - 1, &zv);
1535 
1536 	ZVAL_BOOL(&zv, period_obj->include_start_date);
1537 	write_date_period_property(&period_obj->std, "include_start_date", sizeof("include_start_date") - 1, &zv);
1538 
1539 	ZVAL_BOOL(&zv, period_obj->include_end_date);
1540 	write_date_period_property(&period_obj->std, "include_end_date", sizeof("include_end_date") - 1, &zv);
1541 }
1542 
1543 /* define an overloaded iterator structure */
1544 typedef struct {
1545 	zend_object_iterator  intern;
1546 	zval                  current;
1547 	php_period_obj       *object;
1548 	int                   current_index;
1549 } date_period_it;
1550 
1551 /* {{{ date_period_it_invalidate_current */
date_period_it_invalidate_current(zend_object_iterator * iter)1552 static void date_period_it_invalidate_current(zend_object_iterator *iter)
1553 {
1554 	date_period_it *iterator = (date_period_it *)iter;
1555 
1556 	if (Z_TYPE(iterator->current) != IS_UNDEF) {
1557 		zval_ptr_dtor(&iterator->current);
1558 		ZVAL_UNDEF(&iterator->current);
1559 	}
1560 }
1561 /* }}} */
1562 
1563 /* {{{ date_period_it_dtor */
date_period_it_dtor(zend_object_iterator * iter)1564 static void date_period_it_dtor(zend_object_iterator *iter)
1565 {
1566 	date_period_it *iterator = (date_period_it *)iter;
1567 
1568 	date_period_it_invalidate_current(iter);
1569 
1570 	zval_ptr_dtor(&iterator->intern.data);
1571 }
1572 /* }}} */
1573 
1574 /* {{{ date_period_it_has_more */
date_period_it_has_more(zend_object_iterator * iter)1575 static zend_result date_period_it_has_more(zend_object_iterator *iter)
1576 {
1577 	date_period_it *iterator = (date_period_it *)iter;
1578 	php_period_obj *object   = Z_PHPPERIOD_P(&iterator->intern.data);
1579 
1580 	if (object->end) {
1581 		if (object->include_end_date) {
1582 			return object->current->sse <= object->end->sse ? SUCCESS : FAILURE;
1583 		} else {
1584 			return object->current->sse < object->end->sse ? SUCCESS : FAILURE;
1585 		}
1586 	} else {
1587 		return (iterator->current_index < object->recurrences) ? SUCCESS : FAILURE;
1588 	}
1589 }
1590 /* }}} */
1591 
get_base_date_class(zend_class_entry * start_ce)1592 static zend_class_entry *get_base_date_class(zend_class_entry *start_ce)
1593 {
1594 	zend_class_entry *tmp = start_ce;
1595 
1596 	while (tmp != date_ce_date && tmp != date_ce_immutable && tmp->parent) {
1597 		tmp = tmp->parent;
1598 	}
1599 
1600 	return tmp;
1601 }
1602 
1603 /* {{{ date_period_it_current_data */
date_period_it_current_data(zend_object_iterator * iter)1604 static zval *date_period_it_current_data(zend_object_iterator *iter)
1605 {
1606 	date_period_it *iterator = (date_period_it *)iter;
1607 	php_period_obj *object   = Z_PHPPERIOD_P(&iterator->intern.data);
1608 	timelib_time   *it_time = object->current;
1609 	php_date_obj   *newdateobj;
1610 
1611 	/* Create new object */
1612 	php_date_instantiate(get_base_date_class(object->start_ce), &iterator->current);
1613 	newdateobj = Z_PHPDATE_P(&iterator->current);
1614 	newdateobj->time = timelib_time_ctor();
1615 	*newdateobj->time = *it_time;
1616 	if (it_time->tz_abbr) {
1617 		newdateobj->time->tz_abbr = timelib_strdup(it_time->tz_abbr);
1618 	}
1619 	if (it_time->tz_info) {
1620 		newdateobj->time->tz_info = it_time->tz_info;
1621 	}
1622 
1623 	return &iterator->current;
1624 }
1625 /* }}} */
1626 
1627 /* {{{ date_period_it_current_key */
date_period_it_current_key(zend_object_iterator * iter,zval * key)1628 static void date_period_it_current_key(zend_object_iterator *iter, zval *key)
1629 {
1630 	date_period_it *iterator = (date_period_it *)iter;
1631 	ZVAL_LONG(key, iterator->current_index);
1632 }
1633 /* }}} */
1634 
date_period_advance(timelib_time * it_time,timelib_rel_time * interval)1635 static void date_period_advance(timelib_time *it_time, timelib_rel_time *interval)
1636 {
1637 	it_time->have_relative = 1;
1638 	it_time->relative = *interval;
1639 	it_time->sse_uptodate = 0;
1640 	timelib_update_ts(it_time, NULL);
1641 	timelib_update_from_sse(it_time);
1642 }
1643 
1644 /* {{{ date_period_it_move_forward */
date_period_it_move_forward(zend_object_iterator * iter)1645 static void date_period_it_move_forward(zend_object_iterator *iter)
1646 {
1647 	date_period_it *iterator = (date_period_it *)iter;
1648 	php_period_obj *object   = Z_PHPPERIOD_P(&iterator->intern.data);
1649 	timelib_time   *it_time  = object->current;
1650 	zval current_zv;
1651 
1652 	date_period_advance(it_time, object->interval);
1653 
1654 	if (UNEXPECTED(!object->std.properties)) {
1655 		rebuild_object_properties(&object->std);
1656 	}
1657 
1658 	create_date_period_datetime(object->current, object->start_ce, &current_zv);
1659 	zend_string *property_name = ZSTR_INIT_LITERAL("current", 0);
1660 	zend_std_write_property(&object->std, property_name, &current_zv, NULL);
1661 	zval_ptr_dtor(&current_zv);
1662 	zend_string_release(property_name);
1663 
1664 	iterator->current_index++;
1665 	date_period_it_invalidate_current(iter);
1666 }
1667 /* }}} */
1668 
1669 /* {{{ date_period_it_rewind */
date_period_it_rewind(zend_object_iterator * iter)1670 static void date_period_it_rewind(zend_object_iterator *iter)
1671 {
1672 	date_period_it *iterator = (date_period_it *)iter;
1673 
1674 	iterator->current_index = 0;
1675 	if (iterator->object->current) {
1676 		timelib_time_dtor(iterator->object->current);
1677 	}
1678 	if (!iterator->object->start) {
1679 		date_throw_uninitialized_error(date_ce_period);
1680 		return;
1681 	}
1682 
1683 	iterator->object->current = timelib_time_clone(iterator->object->start);
1684 
1685 	if (!iterator->object->include_start_date) {
1686 		date_period_advance(iterator->object->current, iterator->object->interval);
1687 	}
1688 
1689 	date_period_it_invalidate_current(iter);
1690 }
1691 /* }}} */
1692 
1693 /* iterator handler table */
1694 static const zend_object_iterator_funcs date_period_it_funcs = {
1695 	date_period_it_dtor,
1696 	date_period_it_has_more,
1697 	date_period_it_current_data,
1698 	date_period_it_current_key,
1699 	date_period_it_move_forward,
1700 	date_period_it_rewind,
1701 	date_period_it_invalidate_current,
1702 	NULL, /* get_gc */
1703 };
1704 
date_object_period_get_iterator(zend_class_entry * ce,zval * object,int by_ref)1705 static zend_object_iterator *date_object_period_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */
1706 {
1707 	date_period_it *iterator;
1708 
1709 	if (by_ref) {
1710 		zend_throw_error(NULL, "An iterator cannot be used with foreach by reference");
1711 		return NULL;
1712 	}
1713 
1714 	iterator = emalloc(sizeof(date_period_it));
1715 
1716 	zend_iterator_init((zend_object_iterator*)iterator);
1717 
1718 	ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object));
1719 	iterator->intern.funcs = &date_period_it_funcs;
1720 	iterator->object = Z_PHPPERIOD_P(object);
1721 	ZVAL_UNDEF(&iterator->current);
1722 
1723 	return (zend_object_iterator*)iterator;
1724 } /* }}} */
1725 
implement_date_interface_handler(zend_class_entry * interface,zend_class_entry * implementor)1726 static int implement_date_interface_handler(zend_class_entry *interface, zend_class_entry *implementor) /* {{{ */
1727 {
1728 	if (implementor->type == ZEND_USER_CLASS &&
1729 		!instanceof_function(implementor, date_ce_date) &&
1730 		!instanceof_function(implementor, date_ce_immutable)
1731 	) {
1732 		zend_error_noreturn(E_ERROR, "DateTimeInterface can't be implemented by user classes");
1733 	}
1734 
1735 	return SUCCESS;
1736 } /* }}} */
1737 
date_interval_has_property(zend_object * object,zend_string * name,int type,void ** cache_slot)1738 static int date_interval_has_property(zend_object *object, zend_string *name, int type, void **cache_slot) /* {{{ */
1739 {
1740 	php_interval_obj *obj;
1741 	zval rv;
1742 	zval *prop;
1743 	int retval = 0;
1744 
1745 	obj = php_interval_obj_from_obj(object);
1746 
1747 	if (!obj->initialized) {
1748 		retval = zend_std_has_property(object, name, type, cache_slot);
1749 		return retval;
1750 	}
1751 
1752 	prop = date_interval_read_property(object, name, BP_VAR_IS, cache_slot, &rv);
1753 
1754 	if (prop != &EG(uninitialized_zval)) {
1755 		if (type == 2) {
1756 			retval = 1;
1757 		} else if (type == 1) {
1758 			retval = zend_is_true(prop);
1759 		} else if (type == 0) {
1760 			retval = (Z_TYPE_P(prop) != IS_NULL);
1761 		}
1762 	} else {
1763 		retval = zend_std_has_property(object, name, type, cache_slot);
1764 	}
1765 
1766 	return retval;
1767 
1768 }
1769 /* }}} */
1770 
date_register_classes(void)1771 static void date_register_classes(void) /* {{{ */
1772 {
1773 	date_ce_interface = register_class_DateTimeInterface();
1774 	date_ce_interface->interface_gets_implemented = implement_date_interface_handler;
1775 
1776 	date_ce_date = register_class_DateTime(date_ce_interface);
1777 	date_ce_date->create_object = date_object_new_date;
1778 	date_ce_date->default_object_handlers = &date_object_handlers_date;
1779 	memcpy(&date_object_handlers_date, &std_object_handlers, sizeof(zend_object_handlers));
1780 	date_object_handlers_date.offset = XtOffsetOf(php_date_obj, std);
1781 	date_object_handlers_date.free_obj = date_object_free_storage_date;
1782 	date_object_handlers_date.clone_obj = date_object_clone_date;
1783 	date_object_handlers_date.compare = date_object_compare_date;
1784 	date_object_handlers_date.get_properties_for = date_object_get_properties_for;
1785 	date_object_handlers_date.get_gc = date_object_get_gc;
1786 
1787 	date_ce_immutable = register_class_DateTimeImmutable(date_ce_interface);
1788 	date_ce_immutable->create_object = date_object_new_date;
1789 	date_ce_immutable->default_object_handlers = &date_object_handlers_date;
1790 	memcpy(&date_object_handlers_immutable, &std_object_handlers, sizeof(zend_object_handlers));
1791 	date_object_handlers_immutable.clone_obj = date_object_clone_date;
1792 	date_object_handlers_immutable.compare = date_object_compare_date;
1793 	date_object_handlers_immutable.get_properties_for = date_object_get_properties_for;
1794 	date_object_handlers_immutable.get_gc = date_object_get_gc;
1795 
1796 	date_ce_timezone = register_class_DateTimeZone();
1797 	date_ce_timezone->create_object = date_object_new_timezone;
1798 	date_ce_timezone->default_object_handlers = &date_object_handlers_timezone;
1799 	memcpy(&date_object_handlers_timezone, &std_object_handlers, sizeof(zend_object_handlers));
1800 	date_object_handlers_timezone.offset = XtOffsetOf(php_timezone_obj, std);
1801 	date_object_handlers_timezone.free_obj = date_object_free_storage_timezone;
1802 	date_object_handlers_timezone.clone_obj = date_object_clone_timezone;
1803 	date_object_handlers_timezone.get_properties_for = date_object_get_properties_for_timezone;
1804 	date_object_handlers_timezone.get_gc = date_object_get_gc_timezone;
1805 	date_object_handlers_timezone.get_debug_info = date_object_get_debug_info_timezone;
1806 	date_object_handlers_timezone.compare = date_object_compare_timezone;
1807 
1808 	date_ce_interval = register_class_DateInterval();
1809 	date_ce_interval->create_object = date_object_new_interval;
1810 	date_ce_interval->default_object_handlers = &date_object_handlers_interval;
1811 	memcpy(&date_object_handlers_interval, &std_object_handlers, sizeof(zend_object_handlers));
1812 	date_object_handlers_interval.offset = XtOffsetOf(php_interval_obj, std);
1813 	date_object_handlers_interval.free_obj = date_object_free_storage_interval;
1814 	date_object_handlers_interval.clone_obj = date_object_clone_interval;
1815 	date_object_handlers_interval.has_property = date_interval_has_property;
1816 	date_object_handlers_interval.read_property = date_interval_read_property;
1817 	date_object_handlers_interval.write_property = date_interval_write_property;
1818 	date_object_handlers_interval.get_properties = date_object_get_properties_interval;
1819 	date_object_handlers_interval.get_property_ptr_ptr = date_interval_get_property_ptr_ptr;
1820 	date_object_handlers_interval.get_gc = date_object_get_gc_interval;
1821 	date_object_handlers_interval.compare = date_interval_compare_objects;
1822 
1823 	date_ce_period = register_class_DatePeriod(zend_ce_aggregate);
1824 	date_ce_period->create_object = date_object_new_period;
1825 	date_ce_period->default_object_handlers = &date_object_handlers_period;
1826 	date_ce_period->get_iterator = date_object_period_get_iterator;
1827 	memcpy(&date_object_handlers_period, &std_object_handlers, sizeof(zend_object_handlers));
1828 	date_object_handlers_period.offset = XtOffsetOf(php_period_obj, std);
1829 	date_object_handlers_period.free_obj = date_object_free_storage_period;
1830 	date_object_handlers_period.clone_obj = date_object_clone_period;
1831 	date_object_handlers_period.get_gc = date_object_get_gc_period;
1832 	date_object_handlers_period.get_property_ptr_ptr = date_period_get_property_ptr_ptr;
1833 	date_object_handlers_period.read_property = date_period_read_property;
1834 	date_object_handlers_period.write_property = date_period_write_property;
1835 
1836 	date_ce_date_error = register_class_DateError(zend_ce_error);
1837 	date_ce_date_object_error = register_class_DateObjectError(date_ce_date_error);
1838 	date_ce_date_range_error = register_class_DateRangeError(date_ce_date_error);
1839 
1840 	date_ce_date_exception = register_class_DateException(zend_ce_exception);
1841 	date_ce_date_invalid_timezone_exception = register_class_DateInvalidTimeZoneException(date_ce_date_exception);
1842 	date_ce_date_invalid_operation_exception = register_class_DateInvalidOperationException(date_ce_date_exception);
1843 	date_ce_date_malformed_string_exception = register_class_DateMalformedStringException(date_ce_date_exception);
1844 	date_ce_date_malformed_interval_string_exception = register_class_DateMalformedIntervalStringException(date_ce_date_exception);
1845 	date_ce_date_malformed_period_string_exception = register_class_DateMalformedPeriodStringException(date_ce_date_exception);
1846 } /* }}} */
1847 
date_object_new_date(zend_class_entry * class_type)1848 static zend_object *date_object_new_date(zend_class_entry *class_type) /* {{{ */
1849 {
1850 	php_date_obj *intern = zend_object_alloc(sizeof(php_date_obj), class_type);
1851 
1852 	zend_object_std_init(&intern->std, class_type);
1853 	object_properties_init(&intern->std, class_type);
1854 
1855 	return &intern->std;
1856 } /* }}} */
1857 
date_object_clone_date(zend_object * this_ptr)1858 static zend_object *date_object_clone_date(zend_object *this_ptr) /* {{{ */
1859 {
1860 	php_date_obj *old_obj = php_date_obj_from_obj(this_ptr);
1861 	php_date_obj *new_obj = php_date_obj_from_obj(date_object_new_date(old_obj->std.ce));
1862 
1863 	zend_objects_clone_members(&new_obj->std, &old_obj->std);
1864 	if (!old_obj->time) {
1865 		return &new_obj->std;
1866 	}
1867 
1868 	/* this should probably moved to a new `timelib_time *timelime_time_clone(timelib_time *)` */
1869 	new_obj->time = timelib_time_ctor();
1870 	*new_obj->time = *old_obj->time;
1871 	if (old_obj->time->tz_abbr) {
1872 		new_obj->time->tz_abbr = timelib_strdup(old_obj->time->tz_abbr);
1873 	}
1874 	if (old_obj->time->tz_info) {
1875 		new_obj->time->tz_info = old_obj->time->tz_info;
1876 	}
1877 
1878 	return &new_obj->std;
1879 } /* }}} */
1880 
date_clone_immutable(zval * object,zval * new_object)1881 static void date_clone_immutable(zval *object, zval *new_object) /* {{{ */
1882 {
1883 	ZVAL_OBJ(new_object, date_object_clone_date(Z_OBJ_P(object)));
1884 } /* }}} */
1885 
date_object_compare_date(zval * d1,zval * d2)1886 static int date_object_compare_date(zval *d1, zval *d2) /* {{{ */
1887 {
1888 	php_date_obj *o1;
1889 	php_date_obj *o2;
1890 
1891 	ZEND_COMPARE_OBJECTS_FALLBACK(d1, d2);
1892 
1893 	o1 = Z_PHPDATE_P(d1);
1894 	o2 = Z_PHPDATE_P(d2);
1895 
1896 	if (!o1->time || !o2->time) {
1897 		zend_throw_error(date_ce_date_object_error, "Trying to compare an incomplete DateTime or DateTimeImmutable object");
1898 		return ZEND_UNCOMPARABLE;
1899 	}
1900 	if (!o1->time->sse_uptodate) {
1901 		timelib_update_ts(o1->time, o1->time->tz_info);
1902 	}
1903 	if (!o2->time->sse_uptodate) {
1904 		timelib_update_ts(o2->time, o2->time->tz_info);
1905 	}
1906 
1907 	return timelib_time_compare(o1->time, o2->time);
1908 } /* }}} */
1909 
date_object_get_gc(zend_object * object,zval ** table,int * n)1910 static HashTable *date_object_get_gc(zend_object *object, zval **table, int *n) /* {{{ */
1911 {
1912 	*table = NULL;
1913 	*n = 0;
1914 	return zend_std_get_properties(object);
1915 } /* }}} */
1916 
date_object_get_gc_timezone(zend_object * object,zval ** table,int * n)1917 static HashTable *date_object_get_gc_timezone(zend_object *object, zval **table, int *n) /* {{{ */
1918 {
1919 	*table = NULL;
1920 	*n = 0;
1921 	return zend_std_get_properties(object);
1922 } /* }}} */
1923 
date_object_to_hash(php_date_obj * dateobj,HashTable * props)1924 static void date_object_to_hash(php_date_obj *dateobj, HashTable *props)
1925 {
1926 	zval zv;
1927 
1928 	/* first we add the date and time in ISO format */
1929 	ZVAL_STR(&zv, date_format("x-m-d H:i:s.u", sizeof("x-m-d H:i:s.u")-1, dateobj->time, 1));
1930 	zend_hash_str_update(props, "date", sizeof("date")-1, &zv);
1931 
1932 	/* then we add the timezone name (or similar) */
1933 	if (dateobj->time->is_localtime) {
1934 		ZVAL_LONG(&zv, dateobj->time->zone_type);
1935 		zend_hash_str_update(props, "timezone_type", sizeof("timezone_type")-1, &zv);
1936 
1937 		switch (dateobj->time->zone_type) {
1938 			case TIMELIB_ZONETYPE_ID:
1939 				ZVAL_STRING(&zv, dateobj->time->tz_info->name);
1940 				break;
1941 			case TIMELIB_ZONETYPE_OFFSET: {
1942 				zend_string *tmpstr = zend_string_alloc(sizeof("UTC+05:00")-1, 0);
1943 				int utc_offset = dateobj->time->z;
1944 
1945 				ZSTR_LEN(tmpstr) = snprintf(ZSTR_VAL(tmpstr), sizeof("+05:00"), "%c%02d:%02d",
1946 					utc_offset < 0 ? '-' : '+',
1947 					abs(utc_offset / 3600),
1948 					abs(((utc_offset % 3600) / 60)));
1949 
1950 				ZVAL_NEW_STR(&zv, tmpstr);
1951 				}
1952 				break;
1953 			case TIMELIB_ZONETYPE_ABBR:
1954 				ZVAL_STRING(&zv, dateobj->time->tz_abbr);
1955 				break;
1956 		}
1957 		zend_hash_str_update(props, "timezone", sizeof("timezone")-1, &zv);
1958 	}
1959 }
1960 
date_object_get_properties_for(zend_object * object,zend_prop_purpose purpose)1961 static HashTable *date_object_get_properties_for(zend_object *object, zend_prop_purpose purpose) /* {{{ */
1962 {
1963 	HashTable *props;
1964 	php_date_obj *dateobj;
1965 
1966 	switch (purpose) {
1967 		case ZEND_PROP_PURPOSE_DEBUG:
1968 		case ZEND_PROP_PURPOSE_SERIALIZE:
1969 		case ZEND_PROP_PURPOSE_VAR_EXPORT:
1970 		case ZEND_PROP_PURPOSE_JSON:
1971 		case ZEND_PROP_PURPOSE_ARRAY_CAST:
1972 			break;
1973 		default:
1974 			return zend_std_get_properties_for(object, purpose);
1975 	}
1976 
1977 	dateobj = php_date_obj_from_obj(object);
1978 	props = zend_array_dup(zend_std_get_properties(object));
1979 	if (!dateobj->time) {
1980 		return props;
1981 	}
1982 
1983 	date_object_to_hash(dateobj, props);
1984 
1985 	return props;
1986 } /* }}} */
1987 
date_object_new_timezone(zend_class_entry * class_type)1988 static zend_object *date_object_new_timezone(zend_class_entry *class_type) /* {{{ */
1989 {
1990 	php_timezone_obj *intern = zend_object_alloc(sizeof(php_timezone_obj), class_type);
1991 
1992 	zend_object_std_init(&intern->std, class_type);
1993 	object_properties_init(&intern->std, class_type);
1994 
1995 	return &intern->std;
1996 } /* }}} */
1997 
date_object_clone_timezone(zend_object * this_ptr)1998 static zend_object *date_object_clone_timezone(zend_object *this_ptr) /* {{{ */
1999 {
2000 	php_timezone_obj *old_obj = php_timezone_obj_from_obj(this_ptr);
2001 	php_timezone_obj *new_obj = php_timezone_obj_from_obj(date_object_new_timezone(old_obj->std.ce));
2002 
2003 	zend_objects_clone_members(&new_obj->std, &old_obj->std);
2004 	if (!old_obj->initialized) {
2005 		return &new_obj->std;
2006 	}
2007 
2008 	new_obj->type = old_obj->type;
2009 	new_obj->initialized = 1;
2010 	switch (new_obj->type) {
2011 		case TIMELIB_ZONETYPE_ID:
2012 			new_obj->tzi.tz = old_obj->tzi.tz;
2013 			break;
2014 		case TIMELIB_ZONETYPE_OFFSET:
2015 			new_obj->tzi.utc_offset = old_obj->tzi.utc_offset;
2016 			break;
2017 		case TIMELIB_ZONETYPE_ABBR:
2018 			new_obj->tzi.z.utc_offset = old_obj->tzi.z.utc_offset;
2019 			new_obj->tzi.z.dst        = old_obj->tzi.z.dst;
2020 			new_obj->tzi.z.abbr       = timelib_strdup(old_obj->tzi.z.abbr);
2021 			break;
2022 	}
2023 
2024 	return &new_obj->std;
2025 } /* }}} */
2026 
date_object_compare_timezone(zval * tz1,zval * tz2)2027 static int date_object_compare_timezone(zval *tz1, zval *tz2) /* {{{ */
2028 {
2029 	php_timezone_obj *o1, *o2;
2030 
2031 	ZEND_COMPARE_OBJECTS_FALLBACK(tz1, tz2);
2032 
2033 	o1 = Z_PHPTIMEZONE_P(tz1);
2034 	o2 = Z_PHPTIMEZONE_P(tz2);
2035 
2036 	if (!o1->initialized || !o2->initialized) {
2037 		zend_throw_error(date_ce_date_object_error, "Trying to compare uninitialized DateTimeZone objects");
2038 		return 1;
2039 	}
2040 
2041 	if (o1->type != o2->type) {
2042 		zend_throw_error(date_ce_date_exception, "Cannot compare two different kinds of DateTimeZone objects");
2043 		return ZEND_UNCOMPARABLE;
2044 	}
2045 
2046 	switch (o1->type) {
2047 		case TIMELIB_ZONETYPE_OFFSET:
2048 			return o1->tzi.utc_offset == o2->tzi.utc_offset ? 0 : 1;
2049 		case TIMELIB_ZONETYPE_ABBR:
2050 			return strcmp(o1->tzi.z.abbr, o2->tzi.z.abbr) ? 1 : 0;
2051 		case TIMELIB_ZONETYPE_ID:
2052 			return strcmp(o1->tzi.tz->name, o2->tzi.tz->name) ? 1 : 0;
2053 		EMPTY_SWITCH_DEFAULT_CASE();
2054 	}
2055 } /* }}} */
2056 
php_timezone_to_string(php_timezone_obj * tzobj,zval * zv)2057 static void php_timezone_to_string(php_timezone_obj *tzobj, zval *zv)
2058 {
2059 	switch (tzobj->type) {
2060 		case TIMELIB_ZONETYPE_ID:
2061 			ZVAL_STRING(zv, tzobj->tzi.tz->name);
2062 			break;
2063 		case TIMELIB_ZONETYPE_OFFSET: {
2064 			timelib_sll utc_offset = tzobj->tzi.utc_offset;
2065 			int seconds = utc_offset % 60;
2066 			size_t size;
2067 			const char *format;
2068 			if (seconds == 0) {
2069 				size = sizeof("+05:00");
2070 				format = "%c%02d:%02d";
2071 			} else {
2072 				size = sizeof("+05:00:01");
2073 				format = "%c%02d:%02d:%02d";
2074 			}
2075 			zend_string *tmpstr = zend_string_alloc(size - 1, 0);
2076 
2077 			/* Note: if seconds == 0, the seconds argument will be excessive and therefore ignored. */
2078 			ZSTR_LEN(tmpstr) = snprintf(ZSTR_VAL(tmpstr), size, format,
2079 				utc_offset < 0 ? '-' : '+',
2080 				abs((int)(utc_offset / 3600)),
2081 				abs((int)(utc_offset % 3600) / 60),
2082 				abs(seconds));
2083 
2084 			ZVAL_NEW_STR(zv, tmpstr);
2085 			}
2086 			break;
2087 		case TIMELIB_ZONETYPE_ABBR:
2088 			ZVAL_STRING(zv, tzobj->tzi.z.abbr);
2089 			break;
2090 	}
2091 }
2092 
date_timezone_object_to_hash(php_timezone_obj * tzobj,HashTable * props)2093 static void date_timezone_object_to_hash(php_timezone_obj *tzobj, HashTable *props)
2094 {
2095 	zval zv;
2096 
2097 	ZVAL_LONG(&zv, tzobj->type);
2098 	zend_hash_str_update(props, "timezone_type", strlen("timezone_type"), &zv);
2099 
2100 	php_timezone_to_string(tzobj, &zv);
2101 	zend_hash_str_update(props, "timezone", strlen("timezone"), &zv);
2102 }
2103 
date_object_get_properties_for_timezone(zend_object * object,zend_prop_purpose purpose)2104 static HashTable *date_object_get_properties_for_timezone(zend_object *object, zend_prop_purpose purpose) /* {{{ */
2105 {
2106 	HashTable *props;
2107 	php_timezone_obj *tzobj;
2108 
2109 	switch (purpose) {
2110 		case ZEND_PROP_PURPOSE_DEBUG:
2111 		case ZEND_PROP_PURPOSE_SERIALIZE:
2112 		case ZEND_PROP_PURPOSE_VAR_EXPORT:
2113 		case ZEND_PROP_PURPOSE_JSON:
2114 		case ZEND_PROP_PURPOSE_ARRAY_CAST:
2115 			break;
2116 		default:
2117 			return zend_std_get_properties_for(object, purpose);
2118 	}
2119 
2120 	tzobj = php_timezone_obj_from_obj(object);
2121 	props = zend_array_dup(zend_std_get_properties(object));
2122 	if (!tzobj->initialized) {
2123 		return props;
2124 	}
2125 
2126 	date_timezone_object_to_hash(tzobj, props);
2127 
2128 	return props;
2129 } /* }}} */
2130 
date_object_get_debug_info_timezone(zend_object * object,int * is_temp)2131 static HashTable *date_object_get_debug_info_timezone(zend_object *object, int *is_temp) /* {{{ */
2132 {
2133 	HashTable *ht, *props;
2134 	zval zv;
2135 	php_timezone_obj *tzobj;
2136 
2137 	tzobj = php_timezone_obj_from_obj(object);
2138 	props = zend_std_get_properties(object);
2139 
2140 	*is_temp = 1;
2141 	ht = zend_array_dup(props);
2142 
2143 	ZVAL_LONG(&zv, tzobj->type);
2144 	zend_hash_str_update(ht, "timezone_type", sizeof("timezone_type")-1, &zv);
2145 
2146 	php_timezone_to_string(tzobj, &zv);
2147 	zend_hash_str_update(ht, "timezone", sizeof("timezone")-1, &zv);
2148 
2149 	return ht;
2150 } /* }}} */
2151 
date_object_new_interval(zend_class_entry * class_type)2152 static zend_object *date_object_new_interval(zend_class_entry *class_type) /* {{{ */
2153 {
2154 	php_interval_obj *intern = zend_object_alloc(sizeof(php_interval_obj), class_type);
2155 
2156 	zend_object_std_init(&intern->std, class_type);
2157 	object_properties_init(&intern->std, class_type);
2158 
2159 	return &intern->std;
2160 } /* }}} */
2161 
date_object_clone_interval(zend_object * this_ptr)2162 static zend_object *date_object_clone_interval(zend_object *this_ptr) /* {{{ */
2163 {
2164 	php_interval_obj *old_obj = php_interval_obj_from_obj(this_ptr);
2165 	php_interval_obj *new_obj = php_interval_obj_from_obj(date_object_new_interval(old_obj->std.ce));
2166 
2167 	zend_objects_clone_members(&new_obj->std, &old_obj->std);
2168 	new_obj->civil_or_wall = old_obj->civil_or_wall;
2169 	new_obj->from_string = old_obj->from_string;
2170 	if (old_obj->date_string) {
2171 		new_obj->date_string = zend_string_copy(old_obj->date_string);
2172 	}
2173 	new_obj->initialized = old_obj->initialized;
2174 	if (old_obj->diff) {
2175 		new_obj->diff = timelib_rel_time_clone(old_obj->diff);
2176 	}
2177 
2178 	return &new_obj->std;
2179 } /* }}} */
2180 
date_object_get_gc_interval(zend_object * object,zval ** table,int * n)2181 static HashTable *date_object_get_gc_interval(zend_object *object, zval **table, int *n) /* {{{ */
2182 {
2183 
2184 	*table = NULL;
2185 	*n = 0;
2186 	return zend_std_get_properties(object);
2187 } /* }}} */
2188 
date_interval_object_to_hash(php_interval_obj * intervalobj,HashTable * props)2189 static void date_interval_object_to_hash(php_interval_obj *intervalobj, HashTable *props)
2190 {
2191 	zval zv;
2192 
2193 	/* Records whether this is a special relative interval that needs to be recreated from a string */
2194 	if (intervalobj->from_string) {
2195 		ZVAL_BOOL(&zv, (bool)intervalobj->from_string);
2196 		zend_hash_str_update(props, "from_string", strlen("from_string"), &zv);
2197 		ZVAL_STR_COPY(&zv, intervalobj->date_string);
2198 		zend_hash_str_update(props, "date_string", strlen("date_string"), &zv);
2199 		return;
2200 	}
2201 
2202 #define PHP_DATE_INTERVAL_ADD_PROPERTY(n,f) \
2203 	ZVAL_LONG(&zv, (zend_long)intervalobj->diff->f); \
2204 	zend_hash_str_update(props, n, sizeof(n)-1, &zv);
2205 
2206 	PHP_DATE_INTERVAL_ADD_PROPERTY("y", y);
2207 	PHP_DATE_INTERVAL_ADD_PROPERTY("m", m);
2208 	PHP_DATE_INTERVAL_ADD_PROPERTY("d", d);
2209 	PHP_DATE_INTERVAL_ADD_PROPERTY("h", h);
2210 	PHP_DATE_INTERVAL_ADD_PROPERTY("i", i);
2211 	PHP_DATE_INTERVAL_ADD_PROPERTY("s", s);
2212 	ZVAL_DOUBLE(&zv, (double)intervalobj->diff->us / 1000000.0);
2213 	zend_hash_str_update(props, "f", sizeof("f") - 1, &zv);
2214 	PHP_DATE_INTERVAL_ADD_PROPERTY("invert", invert);
2215 	if (intervalobj->diff->days != TIMELIB_UNSET) {
2216 		PHP_DATE_INTERVAL_ADD_PROPERTY("days", days);
2217 	} else {
2218 		ZVAL_FALSE(&zv);
2219 		zend_hash_str_update(props, "days", sizeof("days")-1, &zv);
2220 	}
2221 	ZVAL_BOOL(&zv, (bool)intervalobj->from_string);
2222 	zend_hash_str_update(props, "from_string", strlen("from_string"), &zv);
2223 
2224 #undef PHP_DATE_INTERVAL_ADD_PROPERTY
2225 }
2226 
date_object_get_properties_interval(zend_object * object)2227 static HashTable *date_object_get_properties_interval(zend_object *object) /* {{{ */
2228 {
2229 	HashTable *props;
2230 	php_interval_obj *intervalobj;
2231 
2232 	intervalobj = php_interval_obj_from_obj(object);
2233 	props = zend_std_get_properties(object);
2234 	if (!intervalobj->initialized) {
2235 		return props;
2236 	}
2237 
2238 	date_interval_object_to_hash(intervalobj, props);
2239 
2240 	return props;
2241 } /* }}} */
2242 
date_object_new_period(zend_class_entry * class_type)2243 static zend_object *date_object_new_period(zend_class_entry *class_type) /* {{{ */
2244 {
2245 	php_period_obj *intern = zend_object_alloc(sizeof(php_period_obj), class_type);
2246 
2247 	zend_object_std_init(&intern->std, class_type);
2248 	object_properties_init(&intern->std, class_type);
2249 
2250 	return &intern->std;
2251 } /* }}} */
2252 
date_object_clone_period(zend_object * this_ptr)2253 static zend_object *date_object_clone_period(zend_object *this_ptr) /* {{{ */
2254 {
2255 	php_period_obj *old_obj = php_period_obj_from_obj(this_ptr);
2256 	php_period_obj *new_obj = php_period_obj_from_obj(date_object_new_period(old_obj->std.ce));
2257 
2258 	zend_objects_clone_members(&new_obj->std, &old_obj->std);
2259 	new_obj->initialized = old_obj->initialized;
2260 	new_obj->recurrences = old_obj->recurrences;
2261 	new_obj->include_start_date = old_obj->include_start_date;
2262 	new_obj->include_end_date = old_obj->include_end_date;
2263 	new_obj->start_ce = old_obj->start_ce;
2264 
2265 	if (old_obj->start) {
2266 		new_obj->start = timelib_time_clone(old_obj->start);
2267 	}
2268 	if (old_obj->current) {
2269 		new_obj->current = timelib_time_clone(old_obj->current);
2270 	}
2271 	if (old_obj->end) {
2272 		new_obj->end = timelib_time_clone(old_obj->end);
2273 	}
2274 	if (old_obj->interval) {
2275 		new_obj->interval = timelib_rel_time_clone(old_obj->interval);
2276 	}
2277 	return &new_obj->std;
2278 } /* }}} */
2279 
date_object_free_storage_date(zend_object * object)2280 static void date_object_free_storage_date(zend_object *object) /* {{{ */
2281 {
2282 	php_date_obj *intern = php_date_obj_from_obj(object);
2283 
2284 	if (intern->time) {
2285 		timelib_time_dtor(intern->time);
2286 	}
2287 
2288 	zend_object_std_dtor(&intern->std);
2289 } /* }}} */
2290 
date_object_free_storage_timezone(zend_object * object)2291 static void date_object_free_storage_timezone(zend_object *object) /* {{{ */
2292 {
2293 	php_timezone_obj *intern = php_timezone_obj_from_obj(object);
2294 
2295 	if (intern->type == TIMELIB_ZONETYPE_ABBR) {
2296 		timelib_free(intern->tzi.z.abbr);
2297 	}
2298 	zend_object_std_dtor(&intern->std);
2299 } /* }}} */
2300 
date_object_free_storage_interval(zend_object * object)2301 static void date_object_free_storage_interval(zend_object *object) /* {{{ */
2302 {
2303 	php_interval_obj *intern = php_interval_obj_from_obj(object);
2304 
2305 	if (intern->date_string) {
2306 		zend_string_release(intern->date_string);
2307 		intern->date_string = NULL;
2308 	}
2309 	timelib_rel_time_dtor(intern->diff);
2310 	zend_object_std_dtor(&intern->std);
2311 } /* }}} */
2312 
date_object_free_storage_period(zend_object * object)2313 static void date_object_free_storage_period(zend_object *object) /* {{{ */
2314 {
2315 	php_period_obj *intern = php_period_obj_from_obj(object);
2316 
2317 	if (intern->start) {
2318 		timelib_time_dtor(intern->start);
2319 	}
2320 
2321 	if (intern->current) {
2322 		timelib_time_dtor(intern->current);
2323 	}
2324 
2325 	if (intern->end) {
2326 		timelib_time_dtor(intern->end);
2327 	}
2328 
2329 	timelib_rel_time_dtor(intern->interval);
2330 	zend_object_std_dtor(&intern->std);
2331 } /* }}} */
2332 
add_common_properties(HashTable * myht,zend_object * zobj)2333 static void add_common_properties(HashTable *myht, zend_object *zobj)
2334 {
2335 	HashTable *common;
2336 	zend_string *name;
2337 	zval *prop;
2338 
2339 	common = zend_std_get_properties(zobj);
2340 
2341 	ZEND_HASH_MAP_FOREACH_STR_KEY_VAL_IND(common, name, prop) {
2342 		if (zend_hash_add(myht, name, prop) != NULL) {
2343 			Z_TRY_ADDREF_P(prop);
2344 		}
2345 	} ZEND_HASH_FOREACH_END();
2346 }
2347 
2348 /* Advanced Interface */
php_date_instantiate(zend_class_entry * pce,zval * object)2349 PHPAPI zval *php_date_instantiate(zend_class_entry *pce, zval *object) /* {{{ */
2350 {
2351 	object_init_ex(object, pce);
2352 	return object;
2353 } /* }}} */
2354 
2355 /* Helper function used to store the latest found warnings and errors while
2356  * parsing, from either strtotime or parse_from_format. */
update_errors_warnings(timelib_error_container ** last_errors)2357 static void update_errors_warnings(timelib_error_container **last_errors) /* {{{ */
2358 {
2359 	if (DATEG(last_errors)) {
2360 		timelib_error_container_dtor(DATEG(last_errors));
2361 		DATEG(last_errors) = NULL;
2362 	}
2363 
2364 	if (last_errors == NULL || (*last_errors) == NULL) {
2365 		return;
2366 	}
2367 
2368 	if ((*last_errors)->warning_count || (*last_errors)->error_count) {
2369 		DATEG(last_errors) = *last_errors;
2370 		return;
2371 	}
2372 
2373 	timelib_error_container_dtor(*last_errors);
2374 	*last_errors = NULL;
2375 } /* }}} */
2376 
php_date_set_time_fraction(timelib_time * time,int microsecond)2377 static void php_date_set_time_fraction(timelib_time *time, int microsecond)
2378 {
2379 	time->us = microsecond;
2380 }
2381 
php_date_get_current_time_with_fraction(time_t * sec,suseconds_t * usec)2382 static void php_date_get_current_time_with_fraction(time_t *sec, suseconds_t *usec)
2383 {
2384 #ifdef HAVE_GETTIMEOFDAY
2385 	struct timeval tp = {0}; /* For setting microsecond */
2386 
2387 	gettimeofday(&tp, NULL);
2388 	*sec = tp.tv_sec;
2389 	*usec = tp.tv_usec;
2390 #else
2391 	*sec = time(NULL);
2392 	*usec = 0;
2393 #endif
2394 }
2395 
php_date_initialize(php_date_obj * dateobj,const char * time_str,size_t time_str_len,const char * format,zval * timezone_object,int flags)2396 PHPAPI bool php_date_initialize(php_date_obj *dateobj, const char *time_str, size_t time_str_len, const char *format, zval *timezone_object, int flags) /* {{{ */
2397 {
2398 	timelib_time   *now;
2399 	timelib_tzinfo *tzi = NULL;
2400 	timelib_error_container *err = NULL;
2401 	int type = TIMELIB_ZONETYPE_ID, new_dst = 0;
2402 	char *new_abbr = NULL;
2403 	timelib_sll new_offset = 0;
2404 	time_t sec;
2405 	suseconds_t usec;
2406 	int options = 0;
2407 
2408 	if (dateobj->time) {
2409 		timelib_time_dtor(dateobj->time);
2410 	}
2411 	if (format) {
2412 		if (time_str_len == 0) {
2413 			time_str = "";
2414 		}
2415 		dateobj->time = timelib_parse_from_format(format, time_str, time_str_len, &err, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
2416 	} else {
2417 		if (time_str_len == 0) {
2418 			time_str = "now";
2419 			time_str_len = sizeof("now") - 1;
2420 		}
2421 		dateobj->time = timelib_strtotime(time_str, time_str_len, &err, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
2422 	}
2423 
2424 	/* update last errors and warnings */
2425 	update_errors_warnings(&err);
2426 
2427 	/* If called from a constructor throw an exception */
2428 	if ((flags & PHP_DATE_INIT_CTOR) && err && err->error_count) {
2429 		/* spit out the first library error message, at least */
2430 		zend_throw_exception_ex(date_ce_date_malformed_string_exception, 0, "Failed to parse time string (%s) at position %d (%c): %s", time_str,
2431 			err->error_messages[0].position, err->error_messages[0].character ? err->error_messages[0].character : ' ', err->error_messages[0].message);
2432 	}
2433 	if (err && err->error_count) {
2434 		timelib_time_dtor(dateobj->time);
2435 		dateobj->time = 0;
2436 		return 0;
2437 	}
2438 
2439 	if (timezone_object) {
2440 		php_timezone_obj *tzobj;
2441 
2442 		tzobj = Z_PHPTIMEZONE_P(timezone_object);
2443 		switch (tzobj->type) {
2444 			case TIMELIB_ZONETYPE_ID:
2445 				tzi = tzobj->tzi.tz;
2446 				break;
2447 			case TIMELIB_ZONETYPE_OFFSET:
2448 				new_offset = tzobj->tzi.utc_offset;
2449 				break;
2450 			case TIMELIB_ZONETYPE_ABBR:
2451 				new_offset = tzobj->tzi.z.utc_offset;
2452 				new_dst    = tzobj->tzi.z.dst;
2453 				new_abbr   = timelib_strdup(tzobj->tzi.z.abbr);
2454 				break;
2455 		}
2456 		type = tzobj->type;
2457 	} else if (dateobj->time->tz_info) {
2458 		tzi = dateobj->time->tz_info;
2459 	} else {
2460 		tzi = get_timezone_info();
2461 		if (!tzi) {
2462 			return 0;
2463 		}
2464 	}
2465 
2466 	now = timelib_time_ctor();
2467 	now->zone_type = type;
2468 	switch (type) {
2469 		case TIMELIB_ZONETYPE_ID:
2470 			now->tz_info = tzi;
2471 			break;
2472 		case TIMELIB_ZONETYPE_OFFSET:
2473 			now->z = new_offset;
2474 			break;
2475 		case TIMELIB_ZONETYPE_ABBR:
2476 			now->z = new_offset;
2477 			now->dst = new_dst;
2478 			now->tz_abbr = new_abbr;
2479 			break;
2480 	}
2481 	php_date_get_current_time_with_fraction(&sec, &usec);
2482 	timelib_unixtime2local(now, (timelib_sll) sec);
2483 	php_date_set_time_fraction(now, usec);
2484 
2485 	if (!format
2486 	 && time_str_len == sizeof("now") - 1
2487 	 && memcmp(time_str, "now", sizeof("now") - 1) == 0) {
2488 		timelib_time_dtor(dateobj->time);
2489 		dateobj->time = now;
2490 		return 1;
2491 	}
2492 
2493 	options = TIMELIB_NO_CLONE;
2494 	if (flags & PHP_DATE_INIT_FORMAT) {
2495 		options |= TIMELIB_OVERRIDE_TIME;
2496 	}
2497 	timelib_fill_holes(dateobj->time, now, options);
2498 
2499 	timelib_update_ts(dateobj->time, tzi);
2500 	timelib_update_from_sse(dateobj->time);
2501 
2502 	dateobj->time->have_relative = 0;
2503 
2504 	timelib_time_dtor(now);
2505 
2506 	return 1;
2507 } /* }}} */
2508 
php_date_initialize_from_ts_long(php_date_obj * dateobj,zend_long sec,int usec)2509 PHPAPI void php_date_initialize_from_ts_long(php_date_obj *dateobj, zend_long sec, int usec) /* {{{ */
2510 {
2511 	dateobj->time = timelib_time_ctor();
2512 	dateobj->time->zone_type = TIMELIB_ZONETYPE_OFFSET;
2513 
2514 	timelib_unixtime2gmt(dateobj->time, (timelib_sll)sec);
2515 	timelib_update_ts(dateobj->time, NULL);
2516 	php_date_set_time_fraction(dateobj->time, usec);
2517 } /* }}} */
2518 
php_date_initialize_from_ts_double(php_date_obj * dateobj,double ts)2519 PHPAPI bool php_date_initialize_from_ts_double(php_date_obj *dateobj, double ts) /* {{{ */
2520 {
2521 	double sec_dval = trunc(ts);
2522 	zend_long sec;
2523 	int usec;
2524 
2525 	if (UNEXPECTED(isnan(sec_dval) || !PHP_DATE_DOUBLE_FITS_LONG(sec_dval))) {
2526 		zend_argument_error(
2527 			date_ce_date_range_error,
2528 			1,
2529 			"must be a finite number between " TIMELIB_LONG_FMT " and " TIMELIB_LONG_FMT ".999999, %g given",
2530 			TIMELIB_LONG_MIN,
2531 			TIMELIB_LONG_MAX,
2532 			ts
2533 		);
2534 		return false;
2535 	}
2536 
2537 	sec = (zend_long)sec_dval;
2538 	usec = (int) round(fmod(ts, 1) * 1000000);
2539 
2540 	if (UNEXPECTED(abs(usec) == 1000000)) {
2541 		sec += usec > 0 ? 1 : -1;
2542 		usec = 0;
2543 	}
2544 
2545 	if (UNEXPECTED(usec < 0)) {
2546 		if (UNEXPECTED(sec == TIMELIB_LONG_MIN)) {
2547 			zend_argument_error(
2548 				date_ce_date_range_error,
2549 				1,
2550 				"must be a finite number between " TIMELIB_LONG_FMT " and " TIMELIB_LONG_FMT ".999999, %g given",
2551 				TIMELIB_LONG_MIN,
2552 				TIMELIB_LONG_MAX,
2553 				ts
2554 			);
2555 			return false;
2556 		}
2557 
2558 		sec = sec - 1;
2559 		usec = 1000000 + usec;
2560 	}
2561 
2562 	php_date_initialize_from_ts_long(dateobj, sec, usec);
2563 
2564 	return true;
2565 } /* }}} */
2566 
2567 /* {{{ Returns new DateTime object */
PHP_FUNCTION(date_create)2568 PHP_FUNCTION(date_create)
2569 {
2570 	zval           *timezone_object = NULL;
2571 	char           *time_str = NULL;
2572 	size_t          time_str_len = 0;
2573 
2574 	ZEND_PARSE_PARAMETERS_START(0, 2)
2575 		Z_PARAM_OPTIONAL
2576 		Z_PARAM_STRING(time_str, time_str_len)
2577 		Z_PARAM_OBJECT_OF_CLASS_OR_NULL(timezone_object, date_ce_timezone)
2578 	ZEND_PARSE_PARAMETERS_END();
2579 
2580 	php_date_instantiate(date_ce_date, return_value);
2581 	if (!php_date_initialize(Z_PHPDATE_P(return_value), time_str, time_str_len, NULL, timezone_object, 0)) {
2582 		zval_ptr_dtor(return_value);
2583 		RETURN_FALSE;
2584 	}
2585 }
2586 /* }}} */
2587 
2588 /* {{{ Returns new DateTimeImmutable object */
PHP_FUNCTION(date_create_immutable)2589 PHP_FUNCTION(date_create_immutable)
2590 {
2591 	zval           *timezone_object = NULL;
2592 	char           *time_str = NULL;
2593 	size_t          time_str_len = 0;
2594 
2595 	ZEND_PARSE_PARAMETERS_START(0, 2)
2596 		Z_PARAM_OPTIONAL
2597 		Z_PARAM_STRING(time_str, time_str_len)
2598 		Z_PARAM_OBJECT_OF_CLASS_OR_NULL(timezone_object, date_ce_timezone)
2599 	ZEND_PARSE_PARAMETERS_END();
2600 
2601 	php_date_instantiate(date_ce_immutable, return_value);
2602 	if (!php_date_initialize(Z_PHPDATE_P(return_value), time_str, time_str_len, NULL, timezone_object, 0)) {
2603 		zval_ptr_dtor(return_value);
2604 		RETURN_FALSE;
2605 	}
2606 }
2607 /* }}} */
2608 
2609 /* {{{ Returns new DateTime object formatted according to the specified format */
PHP_FUNCTION(date_create_from_format)2610 PHP_FUNCTION(date_create_from_format)
2611 {
2612 	zval           *timezone_object = NULL;
2613 	char           *time_str = NULL, *format_str = NULL;
2614 	size_t          time_str_len = 0, format_str_len = 0;
2615 
2616 	ZEND_PARSE_PARAMETERS_START(2, 3)
2617 		Z_PARAM_STRING(format_str, format_str_len)
2618 		Z_PARAM_PATH(time_str, time_str_len)
2619 		Z_PARAM_OPTIONAL
2620 		Z_PARAM_OBJECT_OF_CLASS_OR_NULL(timezone_object, date_ce_timezone)
2621 	ZEND_PARSE_PARAMETERS_END();
2622 
2623 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_date, return_value);
2624 	if (!php_date_initialize(Z_PHPDATE_P(return_value), time_str, time_str_len, format_str, timezone_object, PHP_DATE_INIT_FORMAT)) {
2625 		zval_ptr_dtor(return_value);
2626 		RETURN_FALSE;
2627 	}
2628 }
2629 /* }}} */
2630 
2631 /* {{{ Returns new DateTimeImmutable object formatted according to the specified format */
PHP_FUNCTION(date_create_immutable_from_format)2632 PHP_FUNCTION(date_create_immutable_from_format)
2633 {
2634 	zval           *timezone_object = NULL;
2635 	char           *time_str = NULL, *format_str = NULL;
2636 	size_t          time_str_len = 0, format_str_len = 0;
2637 
2638 	ZEND_PARSE_PARAMETERS_START(2, 3)
2639 		Z_PARAM_STRING(format_str, format_str_len)
2640 		Z_PARAM_PATH(time_str, time_str_len)
2641 		Z_PARAM_OPTIONAL
2642 		Z_PARAM_OBJECT_OF_CLASS_OR_NULL(timezone_object, date_ce_timezone)
2643 	ZEND_PARSE_PARAMETERS_END();
2644 
2645 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_immutable, return_value);
2646 	if (!php_date_initialize(Z_PHPDATE_P(return_value), time_str, time_str_len, format_str, timezone_object, PHP_DATE_INIT_FORMAT)) {
2647 		zval_ptr_dtor(return_value);
2648 		RETURN_FALSE;
2649 	}
2650 }
2651 /* }}} */
2652 
2653 /* {{{ Creates new DateTime object */
PHP_METHOD(DateTime,__construct)2654 PHP_METHOD(DateTime, __construct)
2655 {
2656 	zval *timezone_object = NULL;
2657 	char *time_str = NULL;
2658 	size_t time_str_len = 0;
2659 
2660 	ZEND_PARSE_PARAMETERS_START(0, 2)
2661 		Z_PARAM_OPTIONAL
2662 		Z_PARAM_STRING(time_str, time_str_len)
2663 		Z_PARAM_OBJECT_OF_CLASS_OR_NULL(timezone_object, date_ce_timezone)
2664 	ZEND_PARSE_PARAMETERS_END();
2665 
2666 	php_date_initialize(Z_PHPDATE_P(ZEND_THIS), time_str, time_str_len, NULL, timezone_object, PHP_DATE_INIT_CTOR);
2667 }
2668 /* }}} */
2669 
2670 /* {{{ Creates new DateTimeImmutable object */
PHP_METHOD(DateTimeImmutable,__construct)2671 PHP_METHOD(DateTimeImmutable, __construct)
2672 {
2673 	zval *timezone_object = NULL;
2674 	char *time_str = NULL;
2675 	size_t time_str_len = 0;
2676 
2677 	ZEND_PARSE_PARAMETERS_START(0, 2)
2678 		Z_PARAM_OPTIONAL
2679 		Z_PARAM_STRING(time_str, time_str_len)
2680 		Z_PARAM_OBJECT_OF_CLASS_OR_NULL(timezone_object, date_ce_timezone)
2681 	ZEND_PARSE_PARAMETERS_END();
2682 
2683 	php_date_initialize(Z_PHPDATE_P(ZEND_THIS), time_str, time_str_len, NULL, timezone_object, PHP_DATE_INIT_CTOR);
2684 }
2685 /* }}} */
2686 
2687 /* {{{ Creates new DateTime object from an existing immutable DateTimeImmutable object. */
PHP_METHOD(DateTime,createFromImmutable)2688 PHP_METHOD(DateTime, createFromImmutable)
2689 {
2690 	zval *datetimeimmutable_object = NULL;
2691 	php_date_obj *new_obj = NULL;
2692 	php_date_obj *old_obj = NULL;
2693 
2694 	ZEND_PARSE_PARAMETERS_START(1, 1)
2695 		Z_PARAM_OBJECT_OF_CLASS(datetimeimmutable_object, date_ce_immutable)
2696 	ZEND_PARSE_PARAMETERS_END();
2697 
2698 	old_obj = Z_PHPDATE_P(datetimeimmutable_object);
2699 	DATE_CHECK_INITIALIZED(old_obj->time, Z_OBJCE_P(datetimeimmutable_object));
2700 
2701 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_date, return_value);
2702 	new_obj = Z_PHPDATE_P(return_value);
2703 
2704 	new_obj->time = timelib_time_clone(old_obj->time);
2705 }
2706 /* }}} */
2707 
2708 /* {{{ Creates new DateTime object from an existing DateTimeInterface object. */
PHP_METHOD(DateTime,createFromInterface)2709 PHP_METHOD(DateTime, createFromInterface)
2710 {
2711 	zval *datetimeinterface_object = NULL;
2712 	php_date_obj *new_obj = NULL;
2713 	php_date_obj *old_obj = NULL;
2714 
2715 	ZEND_PARSE_PARAMETERS_START(1, 1)
2716 		Z_PARAM_OBJECT_OF_CLASS(datetimeinterface_object, date_ce_interface)
2717 	ZEND_PARSE_PARAMETERS_END();
2718 
2719 	old_obj = Z_PHPDATE_P(datetimeinterface_object);
2720 	DATE_CHECK_INITIALIZED(old_obj->time, Z_OBJCE_P(datetimeinterface_object));
2721 
2722 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_date, return_value);
2723 	new_obj = Z_PHPDATE_P(return_value);
2724 
2725 	new_obj->time = timelib_time_clone(old_obj->time);
2726 }
2727 /* }}} */
2728 
2729 /* {{{ Creates new DateTime object from given unix timetamp */
PHP_METHOD(DateTime,createFromTimestamp)2730 PHP_METHOD(DateTime, createFromTimestamp)
2731 {
2732 	zval         *value;
2733 	zval         new_object;
2734 	php_date_obj *new_dateobj;
2735 
2736 	ZEND_PARSE_PARAMETERS_START(1, 1)
2737 		Z_PARAM_NUMBER(value)
2738 	ZEND_PARSE_PARAMETERS_END();
2739 
2740 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_date, &new_object);
2741 	new_dateobj = Z_PHPDATE_P(&new_object);
2742 
2743 	switch (Z_TYPE_P(value)) {
2744 		case IS_LONG:
2745 			php_date_initialize_from_ts_long(new_dateobj, Z_LVAL_P(value), 0);
2746 			break;
2747 
2748 		case IS_DOUBLE:
2749 			if (!php_date_initialize_from_ts_double(new_dateobj, Z_DVAL_P(value))) {
2750 				zval_ptr_dtor(&new_object);
2751 				RETURN_THROWS();
2752 			}
2753 			break;
2754 
2755 		EMPTY_SWITCH_DEFAULT_CASE();
2756 	}
2757 
2758 	RETURN_OBJ(Z_OBJ(new_object));
2759 }
2760 /* }}} */
2761 
2762 /* {{{ Creates new DateTimeImmutable object from an existing mutable DateTime object. */
PHP_METHOD(DateTimeImmutable,createFromMutable)2763 PHP_METHOD(DateTimeImmutable, createFromMutable)
2764 {
2765 	zval *datetime_object = NULL;
2766 	php_date_obj *new_obj = NULL;
2767 	php_date_obj *old_obj = NULL;
2768 
2769 	ZEND_PARSE_PARAMETERS_START(1, 1)
2770 		Z_PARAM_OBJECT_OF_CLASS(datetime_object, date_ce_date)
2771 	ZEND_PARSE_PARAMETERS_END();
2772 
2773 	old_obj = Z_PHPDATE_P(datetime_object);
2774 	DATE_CHECK_INITIALIZED(old_obj->time, Z_OBJCE_P(datetime_object));
2775 
2776 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_immutable, return_value);
2777 	new_obj = Z_PHPDATE_P(return_value);
2778 
2779 	new_obj->time = timelib_time_clone(old_obj->time);
2780 }
2781 /* }}} */
2782 
2783 /* {{{ Creates new DateTimeImmutable object from an existing DateTimeInterface object. */
PHP_METHOD(DateTimeImmutable,createFromInterface)2784 PHP_METHOD(DateTimeImmutable, createFromInterface)
2785 {
2786 	zval *datetimeinterface_object = NULL;
2787 	php_date_obj *new_obj = NULL;
2788 	php_date_obj *old_obj = NULL;
2789 
2790 	ZEND_PARSE_PARAMETERS_START(1, 1)
2791 		Z_PARAM_OBJECT_OF_CLASS(datetimeinterface_object, date_ce_interface)
2792 	ZEND_PARSE_PARAMETERS_END();
2793 
2794 	old_obj = Z_PHPDATE_P(datetimeinterface_object);
2795 	DATE_CHECK_INITIALIZED(old_obj->time, Z_OBJCE_P(datetimeinterface_object));
2796 
2797 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_immutable, return_value);
2798 	new_obj = Z_PHPDATE_P(return_value);
2799 
2800 	new_obj->time = timelib_time_clone(old_obj->time);
2801 }
2802 /* }}} */
2803 
2804 /* {{{ Creates new DateTimeImmutable object from given unix timestamp */
PHP_METHOD(DateTimeImmutable,createFromTimestamp)2805 PHP_METHOD(DateTimeImmutable, createFromTimestamp)
2806 {
2807 	zval         *value;
2808 	zval         new_object;
2809 	php_date_obj *new_dateobj;
2810 
2811 	ZEND_PARSE_PARAMETERS_START(1, 1)
2812 		Z_PARAM_NUMBER(value)
2813 	ZEND_PARSE_PARAMETERS_END();
2814 
2815 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_immutable, &new_object);
2816 	new_dateobj = Z_PHPDATE_P(&new_object);
2817 
2818 	switch (Z_TYPE_P(value)) {
2819 		case IS_LONG:
2820 			php_date_initialize_from_ts_long(new_dateobj, Z_LVAL_P(value), 0);
2821 			break;
2822 
2823 		case IS_DOUBLE:
2824 			if (!php_date_initialize_from_ts_double(new_dateobj, Z_DVAL_P(value))) {
2825 				zval_ptr_dtor(&new_object);
2826 				RETURN_THROWS();
2827 			}
2828 			break;
2829 
2830 		EMPTY_SWITCH_DEFAULT_CASE();
2831 	}
2832 
2833 	RETURN_OBJ(Z_OBJ(new_object));
2834 }
2835 /* }}} */
2836 
php_date_initialize_from_hash(php_date_obj ** dateobj,HashTable * myht)2837 static bool php_date_initialize_from_hash(php_date_obj **dateobj, HashTable *myht)
2838 {
2839 	zval             *z_date;
2840 	zval             *z_timezone_type;
2841 	zval             *z_timezone;
2842 	zval              tmp_obj;
2843 	timelib_tzinfo   *tzi;
2844 
2845 	z_date = zend_hash_str_find(myht, "date", sizeof("date")-1);
2846 	if (!z_date || Z_TYPE_P(z_date) != IS_STRING) {
2847 		return false;
2848 	}
2849 
2850 	z_timezone_type = zend_hash_str_find(myht, "timezone_type", sizeof("timezone_type")-1);
2851 	if (!z_timezone_type || Z_TYPE_P(z_timezone_type) != IS_LONG) {
2852 		return false;
2853 	}
2854 
2855 	z_timezone = zend_hash_str_find(myht, "timezone", sizeof("timezone")-1);
2856 	if (!z_timezone || Z_TYPE_P(z_timezone) != IS_STRING) {
2857 		return false;
2858 	}
2859 
2860 	switch (Z_LVAL_P(z_timezone_type)) {
2861 		case TIMELIB_ZONETYPE_OFFSET:
2862 		case TIMELIB_ZONETYPE_ABBR: {
2863 			zend_string *tmp = zend_string_concat3(
2864 				Z_STRVAL_P(z_date), Z_STRLEN_P(z_date), " ", 1,
2865 				Z_STRVAL_P(z_timezone), Z_STRLEN_P(z_timezone));
2866 			bool ret = php_date_initialize(*dateobj, ZSTR_VAL(tmp), ZSTR_LEN(tmp), NULL, NULL, 0);
2867 			zend_string_release(tmp);
2868 			return ret;
2869 		}
2870 
2871 		case TIMELIB_ZONETYPE_ID: {
2872 			bool ret;
2873 			php_timezone_obj *tzobj;
2874 
2875 			tzi = php_date_parse_tzfile(Z_STRVAL_P(z_timezone), DATE_TIMEZONEDB);
2876 
2877 			if (tzi == NULL) {
2878 				return false;
2879 			}
2880 
2881 			tzobj = Z_PHPTIMEZONE_P(php_date_instantiate(date_ce_timezone, &tmp_obj));
2882 			tzobj->type = TIMELIB_ZONETYPE_ID;
2883 			tzobj->tzi.tz = tzi;
2884 			tzobj->initialized = 1;
2885 
2886 			ret = php_date_initialize(*dateobj, Z_STRVAL_P(z_date), Z_STRLEN_P(z_date), NULL, &tmp_obj, 0);
2887 			zval_ptr_dtor(&tmp_obj);
2888 			return ret;
2889 		}
2890 	}
2891 	return false;
2892 } /* }}} */
2893 
2894 /* {{{ */
PHP_METHOD(DateTime,__set_state)2895 PHP_METHOD(DateTime, __set_state)
2896 {
2897 	php_date_obj     *dateobj;
2898 	zval             *array;
2899 	HashTable        *myht;
2900 
2901 	ZEND_PARSE_PARAMETERS_START(1, 1)
2902 		Z_PARAM_ARRAY(array)
2903 	ZEND_PARSE_PARAMETERS_END();
2904 
2905 	myht = Z_ARRVAL_P(array);
2906 
2907 	php_date_instantiate(date_ce_date, return_value);
2908 	dateobj = Z_PHPDATE_P(return_value);
2909 	if (!php_date_initialize_from_hash(&dateobj, myht)) {
2910 		zend_throw_error(NULL, "Invalid serialization data for DateTime object");
2911 		RETURN_THROWS();
2912 	}
2913 }
2914 /* }}} */
2915 
2916 /* {{{ */
PHP_METHOD(DateTimeImmutable,__set_state)2917 PHP_METHOD(DateTimeImmutable, __set_state)
2918 {
2919 	php_date_obj     *dateobj;
2920 	zval             *array;
2921 	HashTable        *myht;
2922 
2923 	ZEND_PARSE_PARAMETERS_START(1, 1)
2924 		Z_PARAM_ARRAY(array)
2925 	ZEND_PARSE_PARAMETERS_END();
2926 
2927 	myht = Z_ARRVAL_P(array);
2928 
2929 	php_date_instantiate(date_ce_immutable, return_value);
2930 	dateobj = Z_PHPDATE_P(return_value);
2931 	if (!php_date_initialize_from_hash(&dateobj, myht)) {
2932 		zend_throw_error(NULL, "Invalid serialization data for DateTimeImmutable object");
2933 		RETURN_THROWS();
2934 	}
2935 }
2936 /* }}} */
2937 
2938 /* {{{ */
PHP_METHOD(DateTime,__serialize)2939 PHP_METHOD(DateTime, __serialize)
2940 {
2941 	zval             *object =