xref: /PHP-8.4/ext/date/php_date.c (revision f9453a88)
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_attributes.h"
24 #include "zend_interfaces.h"
25 #include "zend_exceptions.h"
26 #include "lib/timelib.h"
27 #include "lib/timelib_private.h"
28 #ifndef PHP_WIN32
29 #include <time.h>
30 #else
31 #include "win32/time.h"
32 #endif
33 
34 #ifdef PHP_WIN32
php_date_llabs(__int64 i)35 static __inline __int64 php_date_llabs( __int64 i ) { return i >= 0? i: -i; }
36 #elif defined(__GNUC__) && __GNUC__ < 3
php_date_llabs(__int64_t i)37 static __inline __int64_t php_date_llabs( __int64_t i ) { return i >= 0 ? i : -i; }
38 #else
php_date_llabs(long long i)39 static inline long long php_date_llabs( long long i ) { return i >= 0 ? i : -i; }
40 #endif
41 
42 #ifdef PHP_WIN32
43 #define DATE_I64_BUF_LEN 65
44 # define DATE_I64A(i, s, len) _i64toa_s(i, s, len, 10)
45 # define DATE_A64I(i, s) i = _atoi64(s)
46 #else
47 #define DATE_I64_BUF_LEN 65
48 # define DATE_I64A(i, s, len) \
49 	do { \
50 		int st = snprintf(s, len, "%lld", i); \
51 		s[st] = '\0'; \
52 	} while (0);
53 #define DATE_A64I(i, s) i = strtoll(s, NULL, 10)
54 #endif
55 
php_time(void)56 PHPAPI time_t php_time(void)
57 {
58 #ifdef HAVE_GETTIMEOFDAY
59 	struct timeval tm;
60 
61 	if (UNEXPECTED(gettimeofday(&tm, NULL) != SUCCESS)) {
62 		/* fallback, can't reasonably happen */
63 		return time(NULL);
64 	}
65 
66 	return tm.tv_sec;
67 #else
68 	return time(NULL);
69 #endif
70 }
71 
72 /*
73  * RFC822, Section 5.1: http://www.ietf.org/rfc/rfc822.txt
74  *  date-time   =  [ day "," ] date time        ; dd mm yy hh:mm:ss zzz
75  *  day         =  "Mon"  / "Tue" /  "Wed"  / "Thu"  /  "Fri"  / "Sat" /  "Sun"
76  *  date        =  1*2DIGIT month 2DIGIT        ; day month year e.g. 20 Jun 82
77  *  month       =  "Jan"  /  "Feb" /  "Mar"  /  "Apr"  /  "May"  /  "Jun" /  "Jul"  /  "Aug"  /  "Sep"  /  "Oct" /  "Nov"  /  "Dec"
78  *  time        =  hour zone                    ; ANSI and Military
79  *  hour        =  2DIGIT ":" 2DIGIT [":" 2DIGIT] ; 00:00:00 - 23:59:59
80  *  zone        =  "UT"  / "GMT"  /  "EST" / "EDT"  /  "CST" / "CDT"  /  "MST" / "MDT"  /  "PST" / "PDT"  /  1ALPHA  / ( ("+" / "-") 4DIGIT )
81  */
82 #define DATE_FORMAT_RFC822   "D, d M y H:i:s O"
83 
84 /*
85  * RFC850, Section 2.1.4: http://www.ietf.org/rfc/rfc850.txt
86  *  Format must be acceptable both to the ARPANET and to the getdate routine.
87  *  One format that is acceptable to both is Weekday, DD-Mon-YY HH:MM:SS TIMEZONE
88  *  TIMEZONE can be any timezone name (3 or more letters)
89  */
90 #define DATE_FORMAT_RFC850   "l, d-M-y H:i:s T"
91 
92 /*
93  * RFC1036, Section 2.1.2: http://www.ietf.org/rfc/rfc1036.txt
94  *  Its format must be acceptable both in RFC-822 and to the getdate(3)
95  *  Wdy, DD Mon YY HH:MM:SS TIMEZONE
96  *  There is no hope of having a complete list of timezones.  Universal
97  *  Time (GMT), the North American timezones (PST, PDT, MST, MDT, CST,
98  *  CDT, EST, EDT) and the +/-hhmm offset specified in RFC-822 should be supported.
99  */
100 #define DATE_FORMAT_RFC1036  "D, d M y H:i:s O"
101 
102 /*
103  * RFC1123, Section 5.2.14: http://www.ietf.org/rfc/rfc1123.txt
104  *  RFC-822 Date and Time Specification: RFC-822 Section 5
105  *  The syntax for the date is hereby changed to: date = 1*2DIGIT month 2*4DIGIT
106  */
107 #define DATE_FORMAT_RFC1123  "D, d M Y H:i:s O"
108 
109 /*
110  * RFC7231, Section 7.1.1: http://tools.ietf.org/html/rfc7231
111  */
112 #define DATE_FORMAT_RFC7231  "D, d M Y H:i:s \\G\\M\\T"
113 
114 /*
115  * RFC2822, Section 3.3: http://www.ietf.org/rfc/rfc2822.txt
116  *  FWS             =       ([*WSP CRLF] 1*WSP) /   ; Folding white space
117  *  CFWS            =       *([FWS] comment) (([FWS] comment) / FWS)
118  *
119  *  date-time       =       [ day-of-week "," ] date FWS time [CFWS]
120  *  day-of-week     =       ([FWS] day-name)
121  *  day-name        =       "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
122  *  date            =       day month year
123  *  year            =       4*DIGIT
124  *  month           =       (FWS month-name FWS)
125  *  month-name      =       "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
126  *  day             =       ([FWS] 1*2DIGIT)
127  *  time            =       time-of-day FWS zone
128  *  time-of-day     =       hour ":" minute [ ":" second ]
129  *  hour            =       2DIGIT
130  *  minute          =       2DIGIT
131  *  second          =       2DIGIT
132  *  zone            =       (( "+" / "-" ) 4DIGIT)
133  */
134 #define DATE_FORMAT_RFC2822  "D, d M Y H:i:s O"
135 
136 /*
137  * RFC3339, Section 5.6: http://www.ietf.org/rfc/rfc3339.txt
138  *  date-fullyear   = 4DIGIT
139  *  date-month      = 2DIGIT  ; 01-12
140  *  date-mday       = 2DIGIT  ; 01-28, 01-29, 01-30, 01-31 based on month/year
141  *
142  *  time-hour       = 2DIGIT  ; 00-23
143  *  time-minute     = 2DIGIT  ; 00-59
144  *  time-second     = 2DIGIT  ; 00-58, 00-59, 00-60 based on leap second rules
145  *
146  *  time-secfrac    = "." 1*DIGIT
147  *  time-numoffset  = ("+" / "-") time-hour ":" time-minute
148  *  time-offset     = "Z" / time-numoffset
149  *
150  *  partial-time    = time-hour ":" time-minute ":" time-second [time-secfrac]
151  *  full-date       = date-fullyear "-" date-month "-" date-mday
152  *  full-time       = partial-time time-offset
153  *
154  *  date-time       = full-date "T" full-time
155  */
156 #define DATE_FORMAT_RFC3339  "Y-m-d\\TH:i:sP"
157 
158 /*
159  * This format does not technically match the ISO 8601 standard, as it does not
160  * use : in the UTC offset format specifier. This is kept for BC reasons. The
161  * DATE_FORMAT_ISO8601_EXPANDED format does correct this, as well as adding
162  * support for years out side of the traditional 0000-9999 range.
163  */
164 #define DATE_FORMAT_ISO8601  "Y-m-d\\TH:i:sO"
165 
166 /* ISO 8601:2004(E)
167  *
168  * Section 3.5 Expansion:
169  * By mutual agreement of the partners in information interchange, it is
170  * permitted to expand the component identifying the calendar year, which is
171  * otherwise limited to four digits. This enables reference to dates and times
172  * in calendar years outside the range supported by complete representations,
173  * i.e. before the start of the year [0000] or after the end of the year
174  * [9999]."
175  *
176  * Section 4.1.2.4 Expanded representations:
177  * If, by agreement, expanded representations are used, the formats shall be as
178  * specified below. The interchange parties shall agree the additional number of
179  * digits in the time element year. In the examples below it has been agreed to
180  * expand the time element year with two digits.
181  * Extended format: ±YYYYY-MM-DD
182  * Example: +001985-04-12
183  *
184  * PHP's year expansion digits are variable.
185  */
186 #define DATE_FORMAT_ISO8601_EXPANDED    "X-m-d\\TH:i:sP"
187 
188 /* Internal Only
189  * This format only extends the year when needed, keeping the 'P' format with
190  * colon for UTC offsets
191  */
192 #define DATE_FORMAT_ISO8601_LARGE_YEAR  "x-m-d\\TH:i:sP"
193 
194 /*
195  * RFC3339, Appendix A: http://www.ietf.org/rfc/rfc3339.txt
196  *  ISO 8601 also requires (in section 5.3.1.3) that a decimal fraction
197  *  be proceeded by a "0" if less than unity.  Annex B.2 of ISO 8601
198  *  gives examples where the decimal fractions are not preceded by a "0".
199  *  This grammar assumes section 5.3.1.3 is correct and that Annex B.2 is
200  *  in error.
201  */
202 #define DATE_FORMAT_RFC3339_EXTENDED  "Y-m-d\\TH:i:s.vP"
203 
204 /*
205  * This comes from various sources that like to contradict. I'm going with the
206  * format here because of:
207  * http://msdn.microsoft.com/en-us/library/windows/desktop/aa384321%28v=vs.85%29.aspx
208  * and http://curl.haxx.se/rfc/cookie_spec.html
209  */
210 #define DATE_FORMAT_COOKIE   "l, d-M-Y H:i:s T"
211 
212 #define SUNFUNCS_RET_TIMESTAMP 0
213 #define SUNFUNCS_RET_STRING    1
214 #define SUNFUNCS_RET_DOUBLE    2
215 
216 #define PHP_DATE_TIMEZONE_GROUP_AFRICA     0x0001
217 #define PHP_DATE_TIMEZONE_GROUP_AMERICA    0x0002
218 #define PHP_DATE_TIMEZONE_GROUP_ANTARCTICA 0x0004
219 #define PHP_DATE_TIMEZONE_GROUP_ARCTIC     0x0008
220 #define PHP_DATE_TIMEZONE_GROUP_ASIA       0x0010
221 #define PHP_DATE_TIMEZONE_GROUP_ATLANTIC   0x0020
222 #define PHP_DATE_TIMEZONE_GROUP_AUSTRALIA  0x0040
223 #define PHP_DATE_TIMEZONE_GROUP_EUROPE     0x0080
224 #define PHP_DATE_TIMEZONE_GROUP_INDIAN     0x0100
225 #define PHP_DATE_TIMEZONE_GROUP_PACIFIC    0x0200
226 #define PHP_DATE_TIMEZONE_GROUP_UTC        0x0400
227 #define PHP_DATE_TIMEZONE_GROUP_ALL        0x07FF
228 #define PHP_DATE_TIMEZONE_GROUP_ALL_W_BC   0x0FFF
229 #define PHP_DATE_TIMEZONE_PER_COUNTRY      0x1000
230 
231 #define PHP_DATE_PERIOD_EXCLUDE_START_DATE 0x0001
232 #define PHP_DATE_PERIOD_INCLUDE_END_DATE   0x0002
233 
234 #include "php_date_arginfo.h"
235 
236 static const char* guess_timezone(const timelib_tzdb *tzdb);
237 static void date_register_classes(void);
238 /* }}} */
239 
240 ZEND_DECLARE_MODULE_GLOBALS(date)
241 static PHP_GINIT_FUNCTION(date);
242 
243 /* True global */
244 timelib_tzdb *php_date_global_timezone_db;
245 int php_date_global_timezone_db_enabled;
246 
247 #define DATE_DEFAULT_LATITUDE "31.7667"
248 #define DATE_DEFAULT_LONGITUDE "35.2333"
249 
250 /* on 90'50; common sunset declaration (start of sun body appear) */
251 #define DATE_SUNSET_ZENITH "90.833333"
252 
253 /* on 90'50; common sunrise declaration (sun body disappeared) */
254 #define DATE_SUNRISE_ZENITH "90.833333"
255 
256 static PHP_INI_MH(OnUpdate_date_timezone);
257 
258 /* {{{ INI Settings */
259 PHP_INI_BEGIN()
260 	STD_PHP_INI_ENTRY("date.timezone", "UTC", PHP_INI_ALL, OnUpdate_date_timezone, default_timezone, zend_date_globals, date_globals)
261 	PHP_INI_ENTRY("date.default_latitude",           DATE_DEFAULT_LATITUDE,        PHP_INI_ALL, NULL)
262 	PHP_INI_ENTRY("date.default_longitude",          DATE_DEFAULT_LONGITUDE,       PHP_INI_ALL, NULL)
263 	PHP_INI_ENTRY("date.sunset_zenith",              DATE_SUNSET_ZENITH,           PHP_INI_ALL, NULL)
264 	PHP_INI_ENTRY("date.sunrise_zenith",             DATE_SUNRISE_ZENITH,          PHP_INI_ALL, NULL)
265 PHP_INI_END()
266 /* }}} */
267 
268 static zend_class_entry *date_ce_date, *date_ce_timezone, *date_ce_interval, *date_ce_period;
269 static zend_class_entry *date_ce_immutable, *date_ce_interface;
270 static zend_class_entry *date_ce_date_error, *date_ce_date_object_error, *date_ce_date_range_error;
271 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;
272 
273 
php_date_get_date_ce(void)274 PHPAPI zend_class_entry *php_date_get_date_ce(void)
275 {
276 	return date_ce_date;
277 }
278 
php_date_get_immutable_ce(void)279 PHPAPI zend_class_entry *php_date_get_immutable_ce(void)
280 {
281 	return date_ce_immutable;
282 }
283 
php_date_get_interface_ce(void)284 PHPAPI zend_class_entry *php_date_get_interface_ce(void)
285 {
286 	return date_ce_interface;
287 }
288 
php_date_get_timezone_ce(void)289 PHPAPI zend_class_entry *php_date_get_timezone_ce(void)
290 {
291 	return date_ce_timezone;
292 }
293 
php_date_get_interval_ce(void)294 PHPAPI zend_class_entry *php_date_get_interval_ce(void)
295 {
296 	return date_ce_interval;
297 }
298 
php_date_get_period_ce(void)299 PHPAPI zend_class_entry *php_date_get_period_ce(void)
300 {
301 	return date_ce_period;
302 }
303 
304 static zend_object_handlers date_object_handlers_date;
305 static zend_object_handlers date_object_handlers_immutable;
306 static zend_object_handlers date_object_handlers_timezone;
307 static zend_object_handlers date_object_handlers_interval;
308 static zend_object_handlers date_object_handlers_period;
309 
date_throw_uninitialized_error(zend_class_entry * ce)310 static void date_throw_uninitialized_error(zend_class_entry *ce)
311 {
312 	if (ce->type == ZEND_INTERNAL_CLASS) {
313 		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));
314 	} else {
315 		zend_class_entry *ce_ptr = ce;
316 		while (ce_ptr && ce_ptr->parent && ce_ptr->type == ZEND_USER_CLASS) {
317 			ce_ptr = ce_ptr->parent;
318 		}
319 		if (ce_ptr->type != ZEND_INTERNAL_CLASS) {
320 			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));
321 			return;
322 		}
323 		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));
324 	}
325 }
326 
327 #define DATE_CHECK_INITIALIZED(member, ce) \
328 	if (UNEXPECTED(!member)) { \
329 		date_throw_uninitialized_error(ce); \
330 		RETURN_THROWS(); \
331 	}
332 
333 static void date_object_free_storage_date(zend_object *object);
334 static void date_object_free_storage_timezone(zend_object *object);
335 static void date_object_free_storage_interval(zend_object *object);
336 static void date_object_free_storage_period(zend_object *object);
337 
338 static zend_object *date_object_new_date(zend_class_entry *class_type);
339 static zend_object *date_object_new_timezone(zend_class_entry *class_type);
340 static zend_object *date_object_new_interval(zend_class_entry *class_type);
341 static zend_object *date_object_new_period(zend_class_entry *class_type);
342 
343 static zend_object *date_object_clone_date(zend_object *this_ptr);
344 static zend_object *date_object_clone_timezone(zend_object *this_ptr);
345 static zend_object *date_object_clone_interval(zend_object *this_ptr);
346 static zend_object *date_object_clone_period(zend_object *this_ptr);
347 
348 static int date_object_compare_date(zval *d1, zval *d2);
349 static HashTable *date_object_get_gc(zend_object *object, zval **table, int *n);
350 static HashTable *date_object_get_properties_for(zend_object *object, zend_prop_purpose purpose);
351 static HashTable *date_object_get_gc_interval(zend_object *object, zval **table, int *n);
352 static HashTable *date_object_get_properties_interval(zend_object *object);
353 static HashTable *date_object_get_gc_period(zend_object *object, zval **table, int *n);
354 static HashTable *date_object_get_properties_for_timezone(zend_object *object, zend_prop_purpose purpose);
355 static HashTable *date_object_get_gc_timezone(zend_object *object, zval **table, int *n);
356 static HashTable *date_object_get_debug_info_timezone(zend_object *object, int *is_temp);
357 static void php_timezone_to_string(php_timezone_obj *tzobj, zval *zv);
358 
359 static int date_interval_compare_objects(zval *o1, zval *o2);
360 static zval *date_interval_read_property(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv);
361 static zval *date_interval_write_property(zend_object *object, zend_string *member, zval *value, void **cache_slot);
362 static zval *date_interval_get_property_ptr_ptr(zend_object *object, zend_string *member, int type, void **cache_slot);
363 static zval *date_period_read_property(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv);
364 static zval *date_period_write_property(zend_object *object, zend_string *name, zval *value, void **cache_slot);
365 static zval *date_period_get_property_ptr_ptr(zend_object *object, zend_string *name, int type, void **cache_slot);
366 
367 static int date_object_compare_timezone(zval *tz1, zval *tz2);
368 
369 /* {{{ Module struct */
370 zend_module_entry date_module_entry = {
371 	STANDARD_MODULE_HEADER_EX,
372 	NULL,
373 	NULL,
374 	"date",                     /* extension name */
375 	ext_functions,              /* function list */
376 	PHP_MINIT(date),            /* process startup */
377 	PHP_MSHUTDOWN(date),        /* process shutdown */
378 	PHP_RINIT(date),            /* request startup */
379 	PHP_RSHUTDOWN(date),        /* request shutdown */
380 	PHP_MINFO(date),            /* extension info */
381 	PHP_DATE_VERSION,                /* extension version */
382 	PHP_MODULE_GLOBALS(date),   /* globals descriptor */
383 	PHP_GINIT(date),            /* globals ctor */
384 	NULL,                       /* globals dtor */
385 	ZEND_MODULE_POST_ZEND_DEACTIVATE_N(date), /* post deactivate */
386 	STANDARD_MODULE_PROPERTIES_EX
387 };
388 /* }}} */
389 
390 
391 /* {{{ PHP_GINIT_FUNCTION */
PHP_GINIT_FUNCTION(date)392 static PHP_GINIT_FUNCTION(date)
393 {
394 	date_globals->default_timezone = NULL;
395 	date_globals->timezone = NULL;
396 	date_globals->tzcache = NULL;
397 }
398 /* }}} */
399 
400 
_php_date_tzinfo_dtor(zval * zv)401 static void _php_date_tzinfo_dtor(zval *zv) /* {{{ */
402 {
403 	timelib_tzinfo *tzi = (timelib_tzinfo*)Z_PTR_P(zv);
404 
405 	timelib_tzinfo_dtor(tzi);
406 } /* }}} */
407 
408 /* {{{ PHP_RINIT_FUNCTION */
PHP_RINIT_FUNCTION(date)409 PHP_RINIT_FUNCTION(date)
410 {
411 	if (DATEG(timezone)) {
412 		efree(DATEG(timezone));
413 	}
414 	DATEG(timezone) = NULL;
415 	DATEG(tzcache) = NULL;
416 	DATEG(last_errors) = NULL;
417 
418 	return SUCCESS;
419 }
420 /* }}} */
421 
422 /* {{{ PHP_RSHUTDOWN_FUNCTION */
PHP_RSHUTDOWN_FUNCTION(date)423 PHP_RSHUTDOWN_FUNCTION(date)
424 {
425 	if (DATEG(timezone)) {
426 		efree(DATEG(timezone));
427 	}
428 	DATEG(timezone) = NULL;
429 
430 	return SUCCESS;
431 }
432 /* }}} */
433 
ZEND_MODULE_POST_ZEND_DEACTIVATE_D(date)434 ZEND_MODULE_POST_ZEND_DEACTIVATE_D(date)
435 {
436 	if (DATEG(tzcache)) {
437 		zend_hash_destroy(DATEG(tzcache));
438 		FREE_HASHTABLE(DATEG(tzcache));
439 		DATEG(tzcache) = NULL;
440 	}
441 
442 	if (DATEG(last_errors)) {
443 		timelib_error_container_dtor(DATEG(last_errors));
444 		DATEG(last_errors) = NULL;
445 	}
446 
447 	return SUCCESS;
448 }
449 
450 #define DATE_TIMEZONEDB      php_date_global_timezone_db ? php_date_global_timezone_db : timelib_builtin_db()
451 
452 /* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(date)453 PHP_MINIT_FUNCTION(date)
454 {
455 	REGISTER_INI_ENTRIES();
456 	date_register_classes();
457 	register_php_date_symbols(module_number);
458 
459 	php_date_global_timezone_db = NULL;
460 	php_date_global_timezone_db_enabled = 0;
461 	DATEG(last_errors) = NULL;
462 	return SUCCESS;
463 }
464 /* }}} */
465 
466 /* {{{ PHP_MSHUTDOWN_FUNCTION */
PHP_MSHUTDOWN_FUNCTION(date)467 PHP_MSHUTDOWN_FUNCTION(date)
468 {
469 	UNREGISTER_INI_ENTRIES();
470 
471 	if (DATEG(last_errors)) {
472 		timelib_error_container_dtor(DATEG(last_errors));
473 	}
474 
475 #ifndef ZTS
476 	DATEG(default_timezone) = NULL;
477 #endif
478 
479 	return SUCCESS;
480 }
481 /* }}} */
482 
483 /* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(date)484 PHP_MINFO_FUNCTION(date)
485 {
486 	const timelib_tzdb *tzdb = DATE_TIMEZONEDB;
487 
488 	php_info_print_table_start();
489 	php_info_print_table_row(2, "date/time support", "enabled");
490 	php_info_print_table_row(2, "timelib version", TIMELIB_ASCII_VERSION);
491 	php_info_print_table_row(2, "\"Olson\" Timezone Database Version", tzdb->version);
492 	php_info_print_table_row(2, "Timezone Database", php_date_global_timezone_db_enabled ? "external" : "internal");
493 	php_info_print_table_row(2, "Default timezone", guess_timezone(tzdb));
494 	php_info_print_table_end();
495 
496 	DISPLAY_INI_ENTRIES();
497 }
498 /* }}} */
499 
500 /* {{{ Timezone Cache functions */
php_date_parse_tzfile(const char * formal_tzname,const timelib_tzdb * tzdb)501 static timelib_tzinfo *php_date_parse_tzfile(const char *formal_tzname, const timelib_tzdb *tzdb)
502 {
503 	timelib_tzinfo *tzi;
504 	int dummy_error_code;
505 
506 	if(!DATEG(tzcache)) {
507 		ALLOC_HASHTABLE(DATEG(tzcache));
508 		zend_hash_init(DATEG(tzcache), 4, NULL, _php_date_tzinfo_dtor, 0);
509 	}
510 
511 	if ((tzi = zend_hash_str_find_ptr(DATEG(tzcache), formal_tzname, strlen(formal_tzname))) != NULL) {
512 		return tzi;
513 	}
514 
515 	tzi = timelib_parse_tzfile(formal_tzname, tzdb, &dummy_error_code);
516 	if (tzi) {
517 		zend_hash_str_add_ptr(DATEG(tzcache), formal_tzname, strlen(formal_tzname), tzi);
518 	}
519 	return tzi;
520 }
521 
php_date_parse_tzfile_wrapper(const char * formal_tzname,const timelib_tzdb * tzdb,int * dummy_error_code)522 static timelib_tzinfo *php_date_parse_tzfile_wrapper(const char *formal_tzname, const timelib_tzdb *tzdb, int *dummy_error_code)
523 {
524 	return php_date_parse_tzfile(formal_tzname, tzdb);
525 }
526 /* }}} */
527 
528 /* Callback to check the date.timezone only when changed increases performance */
529 /* {{{ static PHP_INI_MH(OnUpdate_date_timezone) */
PHP_INI_MH(OnUpdate_date_timezone)530 static PHP_INI_MH(OnUpdate_date_timezone)
531 {
532 	if (new_value && !timelib_timezone_id_is_valid(ZSTR_VAL(new_value), DATE_TIMEZONEDB)) {
533 		php_error_docref(
534 			NULL, E_WARNING,
535 			"Invalid date.timezone value '%s', using '%s' instead",
536 			ZSTR_VAL(new_value),
537 			DATEG(default_timezone) ? DATEG(default_timezone) : "UTC"
538 		);
539 		return FAILURE;
540 	}
541 
542 	if (OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage) == FAILURE) {
543 		return FAILURE;
544 	}
545 
546 	return SUCCESS;
547 }
548 /* }}} */
549 
550 /* {{{ Helper functions */
guess_timezone(const timelib_tzdb * tzdb)551 static const char* guess_timezone(const timelib_tzdb *tzdb)
552 {
553 	/* Checking whether timezone has been set with date_default_timezone_set() */
554 	if (DATEG(timezone) && (strlen(DATEG(timezone))) > 0) {
555 		return DATEG(timezone);
556 	}
557 	/* Check config setting for default timezone */
558 	if (!DATEG(default_timezone)) {
559 		/* Special case: ext/date wasn't initialized yet */
560 		zval *ztz;
561 
562 		if (NULL != (ztz = cfg_get_entry("date.timezone", sizeof("date.timezone")))
563 			&& Z_TYPE_P(ztz) == IS_STRING && Z_STRLEN_P(ztz) > 0 && timelib_timezone_id_is_valid(Z_STRVAL_P(ztz), tzdb)) {
564 			return Z_STRVAL_P(ztz);
565 		}
566 	} else if (*DATEG(default_timezone)) {
567 		return DATEG(default_timezone);
568 	}
569 	/* Fallback to UTC */
570 	return "UTC";
571 }
572 
get_timezone_info(void)573 PHPAPI timelib_tzinfo *get_timezone_info(void)
574 {
575 	timelib_tzinfo *tzi;
576 
577 	const char *tz = guess_timezone(DATE_TIMEZONEDB);
578 	tzi = php_date_parse_tzfile(tz, DATE_TIMEZONEDB);
579 	if (! tzi) {
580 		zend_throw_error(date_ce_date_error, "Timezone database is corrupt. Please file a bug report as this should never happen");
581 	}
582 	return tzi;
583 }
584 
update_property(zend_object * object,zend_string * key,zval * prop_val)585 static void update_property(zend_object *object, zend_string *key, zval *prop_val)
586 {
587 	if (ZSTR_LEN(key) > 0 && ZSTR_VAL(key)[0] == '\0') { // not public
588 		const char *class_name, *prop_name;
589 		size_t prop_name_len;
590 
591 		if (zend_unmangle_property_name_ex(key, &class_name, &prop_name, &prop_name_len) == SUCCESS) {
592 			if (class_name[0] != '*') { // private
593 				zend_string *cname;
594 				zend_class_entry *ce;
595 
596 				cname = zend_string_init(class_name, strlen(class_name), 0);
597 				ce = zend_lookup_class(cname);
598 
599 				if (ce) {
600 					zend_update_property(ce, object, prop_name, prop_name_len, prop_val);
601 				}
602 
603 				zend_string_release_ex(cname, 0);
604 			} else { // protected
605 				zend_update_property(object->ce, object, prop_name, prop_name_len, prop_val);
606 			}
607 		}
608 		return;
609 	}
610 
611 	// public
612 	zend_update_property(object->ce, object, ZSTR_VAL(key), ZSTR_LEN(key), prop_val);
613 }
614 /* }}} */
615 
616 
617 /* {{{ date() and gmdate() data */
618 #include "zend_smart_str.h"
619 
620 static const char * const mon_full_names[] = {
621 	"January", "February", "March", "April",
622 	"May", "June", "July", "August",
623 	"September", "October", "November", "December"
624 };
625 
626 static const char * const mon_short_names[] = {
627 	"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
628 };
629 
630 static const char * const day_full_names[] = {
631 	"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
632 };
633 
634 static const char * const day_short_names[] = {
635 	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
636 };
637 
english_suffix(timelib_sll number)638 static const char *english_suffix(timelib_sll number)
639 {
640 	if (number >= 10 && number <= 19) {
641 		return "th";
642 	} else {
643 		switch (number % 10) {
644 			case 1: return "st";
645 			case 2: return "nd";
646 			case 3: return "rd";
647 		}
648 	}
649 	return "th";
650 }
651 /* }}} */
652 
653 /* {{{ day of week helpers */
php_date_full_day_name(timelib_sll y,timelib_sll m,timelib_sll d)654 static const char *php_date_full_day_name(timelib_sll y, timelib_sll m, timelib_sll d)
655 {
656 	timelib_sll day_of_week = timelib_day_of_week(y, m, d);
657 	if (day_of_week < 0) {
658 		return "Unknown";
659 	}
660 	return day_full_names[day_of_week];
661 }
662 
php_date_short_day_name(timelib_sll y,timelib_sll m,timelib_sll d)663 static const char *php_date_short_day_name(timelib_sll y, timelib_sll m, timelib_sll d)
664 {
665 	timelib_sll day_of_week = timelib_day_of_week(y, m, d);
666 	if (day_of_week < 0) {
667 		return "Unknown";
668 	}
669 	return day_short_names[day_of_week];
670 }
671 /* }}} */
672 
673 /* {{{ date_format - (gm)date helper */
date_format(const char * format,size_t format_len,timelib_time * t,bool localtime)674 static zend_string *date_format(const char *format, size_t format_len, timelib_time *t, bool localtime)
675 {
676 	smart_str            string = {0};
677 	size_t               i;
678 	int                  length = 0;
679 	char                 buffer[97];
680 	timelib_time_offset *offset = NULL;
681 	timelib_sll          isoweek, isoyear;
682 	bool                 rfc_colon;
683 	int                  weekYearSet = 0;
684 
685 	if (!format_len) {
686 		return ZSTR_EMPTY_ALLOC();
687 	}
688 
689 	if (localtime) {
690 		if (t->zone_type == TIMELIB_ZONETYPE_ABBR) {
691 			offset = timelib_time_offset_ctor();
692 			offset->offset = (t->z + (t->dst * 3600));
693 			offset->leap_secs = 0;
694 			offset->is_dst = t->dst;
695 			offset->abbr = timelib_strdup(t->tz_abbr);
696 		} else if (t->zone_type == TIMELIB_ZONETYPE_OFFSET) {
697 			offset = timelib_time_offset_ctor();
698 			offset->offset = (t->z);
699 			offset->leap_secs = 0;
700 			offset->is_dst = 0;
701 			offset->abbr = timelib_malloc(9); /* GMT±xxxx\0 */
702 			snprintf(offset->abbr, 9, "GMT%c%02d%02d",
703 			                          (offset->offset < 0) ? '-' : '+',
704 			                          abs(offset->offset / 3600),
705 			                          abs((offset->offset % 3600) / 60));
706 		} else if (t->zone_type == TIMELIB_ZONETYPE_ID) {
707 			offset = timelib_get_time_zone_info(t->sse, t->tz_info);
708 		} else {
709 			/* Shouldn't happen, but code defensively */
710 			offset = timelib_time_offset_ctor();
711 		}
712 	}
713 
714 	for (i = 0; i < format_len; i++) {
715 		rfc_colon = 0;
716 		switch (format[i]) {
717 			/* day */
718 			case 'd': length = slprintf(buffer, sizeof(buffer), "%02d", (int) t->d); break;
719 			case 'D': length = slprintf(buffer, sizeof(buffer), "%s", php_date_short_day_name(t->y, t->m, t->d)); break;
720 			case 'j': length = slprintf(buffer, sizeof(buffer), "%d", (int) t->d); break;
721 			case 'l': length = slprintf(buffer, sizeof(buffer), "%s", php_date_full_day_name(t->y, t->m, t->d)); break;
722 			case 'S': length = slprintf(buffer, sizeof(buffer), "%s", english_suffix(t->d)); break;
723 			case 'w': length = slprintf(buffer, sizeof(buffer), "%d", (int) timelib_day_of_week(t->y, t->m, t->d)); break;
724 			case 'N': length = slprintf(buffer, sizeof(buffer), "%d", (int) timelib_iso_day_of_week(t->y, t->m, t->d)); break;
725 			case 'z': length = slprintf(buffer, sizeof(buffer), "%d", (int) timelib_day_of_year(t->y, t->m, t->d)); break;
726 
727 			/* week */
728 			case 'W':
729 				if(!weekYearSet) { timelib_isoweek_from_date(t->y, t->m, t->d, &isoweek, &isoyear); weekYearSet = 1; }
730 				length = slprintf(buffer, sizeof(buffer), "%02d", (int) isoweek); break; /* iso weeknr */
731 			case 'o':
732 				if(!weekYearSet) { timelib_isoweek_from_date(t->y, t->m, t->d, &isoweek, &isoyear); weekYearSet = 1; }
733 				length = slprintf(buffer, sizeof(buffer), ZEND_LONG_FMT, (zend_long) isoyear); break; /* iso year */
734 
735 			/* month */
736 			case 'F': length = slprintf(buffer, sizeof(buffer), "%s", mon_full_names[t->m - 1]); break;
737 			case 'm': length = slprintf(buffer, sizeof(buffer), "%02d", (int) t->m); break;
738 			case 'M': length = slprintf(buffer, sizeof(buffer), "%s", mon_short_names[t->m - 1]); break;
739 			case 'n': length = slprintf(buffer, sizeof(buffer), "%d", (int) t->m); break;
740 			case 't': length = slprintf(buffer, sizeof(buffer), "%d", (int) timelib_days_in_month(t->y, t->m)); break;
741 
742 			/* year */
743 			case 'L': length = slprintf(buffer, sizeof(buffer), "%d", timelib_is_leap((int) t->y)); break;
744 			case 'y': length = slprintf(buffer, sizeof(buffer), "%02d", (int) (t->y % 100)); break;
745 			case 'Y': length = slprintf(buffer, sizeof(buffer), "%s%04lld", t->y < 0 ? "-" : "", php_date_llabs((timelib_sll) t->y)); break;
746 			case 'x': length = slprintf(buffer, sizeof(buffer), "%s%04lld", t->y < 0 ? "-" : (t->y >= 10000 ? "+" : ""), php_date_llabs((timelib_sll) t->y)); break;
747 			case 'X': length = slprintf(buffer, sizeof(buffer), "%s%04lld", t->y < 0 ? "-" : "+", php_date_llabs((timelib_sll) t->y)); break;
748 
749 			/* time */
750 			case 'a': length = slprintf(buffer, sizeof(buffer), "%s", t->h >= 12 ? "pm" : "am"); break;
751 			case 'A': length = slprintf(buffer, sizeof(buffer), "%s", t->h >= 12 ? "PM" : "AM"); break;
752 			case 'B': {
753 				int retval = ((((long)t->sse)-(((long)t->sse) - ((((long)t->sse) % 86400) + 3600))) * 10);
754 				if (retval < 0) {
755 					retval += 864000;
756 				}
757 				/* Make sure to do this on a positive int to avoid rounding errors */
758 				retval = (retval / 864)  % 1000;
759 				length = slprintf(buffer, sizeof(buffer), "%03d", retval);
760 				break;
761 			}
762 			case 'g': length = slprintf(buffer, sizeof(buffer), "%d", (t->h % 12) ? (int) t->h % 12 : 12); break;
763 			case 'G': length = slprintf(buffer, sizeof(buffer), "%d", (int) t->h); break;
764 			case 'h': length = slprintf(buffer, sizeof(buffer), "%02d", (t->h % 12) ? (int) t->h % 12 : 12); break;
765 			case 'H': length = slprintf(buffer, sizeof(buffer), "%02d", (int) t->h); break;
766 			case 'i': length = slprintf(buffer, sizeof(buffer), "%02d", (int) t->i); break;
767 			case 's': length = slprintf(buffer, sizeof(buffer), "%02d", (int) t->s); break;
768 			case 'u': length = slprintf(buffer, sizeof(buffer), "%06d", (int) floor(t->us)); break;
769 			case 'v': length = slprintf(buffer, sizeof(buffer), "%03d", (int) floor(t->us / 1000)); break;
770 
771 			/* timezone */
772 			case 'I': length = slprintf(buffer, sizeof(buffer), "%d", localtime ? offset->is_dst : 0); break;
773 			case 'p':
774 				if (!localtime || strcmp(offset->abbr, "UTC") == 0 || strcmp(offset->abbr, "Z") == 0 || strcmp(offset->abbr, "GMT+0000") == 0) {
775 					length = slprintf(buffer, sizeof(buffer), "%s", "Z");
776 					break;
777 				}
778 				ZEND_FALLTHROUGH;
779 			case 'P': rfc_colon = 1; ZEND_FALLTHROUGH;
780 			case 'O': length = slprintf(buffer, sizeof(buffer), "%c%02d%s%02d",
781 											localtime ? ((offset->offset < 0) ? '-' : '+') : '+',
782 											localtime ? abs(offset->offset / 3600) : 0,
783 											rfc_colon ? ":" : "",
784 											localtime ? abs((offset->offset % 3600) / 60) : 0
785 							  );
786 					  break;
787 			case 'T': length = slprintf(buffer, sizeof(buffer), "%s", localtime ? offset->abbr : "GMT"); break;
788 			case 'e': if (!localtime) {
789 					      length = slprintf(buffer, sizeof(buffer), "%s", "UTC");
790 					  } else {
791 						  switch (t->zone_type) {
792 							  case TIMELIB_ZONETYPE_ID:
793 								  length = slprintf(buffer, sizeof(buffer), "%s", t->tz_info->name);
794 								  break;
795 							  case TIMELIB_ZONETYPE_ABBR:
796 								  length = slprintf(buffer, sizeof(buffer), "%s", offset->abbr);
797 								  break;
798 							  case TIMELIB_ZONETYPE_OFFSET:
799 								  length = slprintf(buffer, sizeof(buffer), "%c%02d:%02d",
800 												((offset->offset < 0) ? '-' : '+'),
801 												abs(offset->offset / 3600),
802 												abs((offset->offset % 3600) / 60)
803 										   );
804 								  break;
805 						  }
806 					  }
807 					  break;
808 			case 'Z': length = slprintf(buffer, sizeof(buffer), "%d", localtime ? offset->offset : 0); break;
809 
810 			/* full date/time */
811 			case 'c': length = slprintf(buffer, sizeof(buffer), "%04" ZEND_LONG_FMT_SPEC "-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
812 							                (zend_long) t->y, (int) t->m, (int) t->d,
813 											(int) t->h, (int) t->i, (int) t->s,
814 											localtime ? ((offset->offset < 0) ? '-' : '+') : '+',
815 											localtime ? abs(offset->offset / 3600) : 0,
816 											localtime ? abs((offset->offset % 3600) / 60) : 0
817 							  );
818 					  break;
819 			case 'r': length = slprintf(buffer, sizeof(buffer), "%3s, %02d %3s %04" ZEND_LONG_FMT_SPEC " %02d:%02d:%02d %c%02d%02d",
820 							                php_date_short_day_name(t->y, t->m, t->d),
821 											(int) t->d, mon_short_names[t->m - 1],
822 											(zend_long) t->y, (int) t->h, (int) t->i, (int) t->s,
823 											localtime ? ((offset->offset < 0) ? '-' : '+') : '+',
824 											localtime ? abs(offset->offset / 3600) : 0,
825 											localtime ? abs((offset->offset % 3600) / 60) : 0
826 							  );
827 					  break;
828 			case 'U': length = slprintf(buffer, sizeof(buffer), "%lld", (timelib_sll) t->sse); break;
829 
830 			case '\\': if (i < format_len) i++; ZEND_FALLTHROUGH;
831 
832 			default: buffer[0] = format[i]; buffer[1] = '\0'; length = 1; break;
833 		}
834 		smart_str_appendl(&string, buffer, length);
835 	}
836 
837 	smart_str_0(&string);
838 
839 	if (localtime) {
840 		timelib_time_offset_dtor(offset);
841 	}
842 
843 	return string.s;
844 }
845 
php_format_date_obj(const char * format,size_t format_len,php_date_obj * date_obj)846 PHPAPI zend_string *php_format_date_obj(const char *format, size_t format_len, php_date_obj *date_obj)
847 {
848 	if (!date_obj->time) {
849 		return NULL;
850 	}
851 
852 	return date_format(format, format_len, date_obj->time, date_obj->time->is_localtime);
853 }
854 
php_date(INTERNAL_FUNCTION_PARAMETERS,bool localtime)855 static void php_date(INTERNAL_FUNCTION_PARAMETERS, bool localtime)
856 {
857 	zend_string *format;
858 	zend_long    ts;
859 	bool    ts_is_null = 1;
860 
861 	ZEND_PARSE_PARAMETERS_START(1, 2)
862 		Z_PARAM_STR(format)
863 		Z_PARAM_OPTIONAL
864 		Z_PARAM_LONG_OR_NULL(ts, ts_is_null)
865 	ZEND_PARSE_PARAMETERS_END();
866 
867 	if (ts_is_null) {
868 		ts = php_time();
869 	}
870 
871 	RETURN_STR(php_format_date(ZSTR_VAL(format), ZSTR_LEN(format), ts, localtime));
872 }
873 /* }}} */
874 
php_format_date(const char * format,size_t format_len,time_t ts,bool localtime)875 PHPAPI zend_string *php_format_date(const char *format, size_t format_len, time_t ts, bool localtime) /* {{{ */
876 {
877 	timelib_time   *t;
878 	timelib_tzinfo *tzi;
879 	zend_string *string;
880 
881 	t = timelib_time_ctor();
882 
883 	if (localtime) {
884 		tzi = get_timezone_info();
885 		t->tz_info = tzi;
886 		t->zone_type = TIMELIB_ZONETYPE_ID;
887 		timelib_unixtime2local(t, ts);
888 	} else {
889 		tzi = NULL;
890 		timelib_unixtime2gmt(t, ts);
891 	}
892 
893 	string = date_format(format, format_len, t, localtime);
894 
895 	timelib_time_dtor(t);
896 	return string;
897 }
898 /* }}} */
899 
900 /* {{{ php_idate */
php_idate(char format,time_t ts,bool localtime)901 PHPAPI int php_idate(char format, time_t ts, bool localtime)
902 {
903 	timelib_time   *t;
904 	timelib_tzinfo *tzi;
905 	int retval = -1;
906 	timelib_time_offset *offset = NULL;
907 	timelib_sll isoweek, isoyear;
908 
909 	t = timelib_time_ctor();
910 
911 	if (!localtime) {
912 		tzi = get_timezone_info();
913 		t->tz_info = tzi;
914 		t->zone_type = TIMELIB_ZONETYPE_ID;
915 		timelib_unixtime2local(t, ts);
916 	} else {
917 		tzi = NULL;
918 		timelib_unixtime2gmt(t, ts);
919 	}
920 
921 	if (!localtime) {
922 		if (t->zone_type == TIMELIB_ZONETYPE_ABBR) {
923 			offset = timelib_time_offset_ctor();
924 			offset->offset = (t->z + (t->dst * 3600));
925 			offset->leap_secs = 0;
926 			offset->is_dst = t->dst;
927 			offset->abbr = timelib_strdup(t->tz_abbr);
928 		} else if (t->zone_type == TIMELIB_ZONETYPE_OFFSET) {
929 			offset = timelib_time_offset_ctor();
930 			offset->offset = (t->z + (t->dst * 3600));
931 			offset->leap_secs = 0;
932 			offset->is_dst = t->dst;
933 			offset->abbr = timelib_malloc(9); /* GMT±xxxx\0 */
934 			snprintf(offset->abbr, 9, "GMT%c%02d%02d",
935 			                          (offset->offset < 0) ? '-' : '+',
936 			                          abs(offset->offset / 3600),
937 			                          abs((offset->offset % 3600) / 60));
938 		} else {
939 			offset = timelib_get_time_zone_info(t->sse, t->tz_info);
940 		}
941 	}
942 
943 	timelib_isoweek_from_date(t->y, t->m, t->d, &isoweek, &isoyear);
944 
945 	switch (format) {
946 		/* day */
947 		case 'd': case 'j': retval = (int) t->d; break;
948 
949 		case 'N': retval = (int) timelib_iso_day_of_week(t->y, t->m, t->d); break;
950 		case 'w': retval = (int) timelib_day_of_week(t->y, t->m, t->d); break;
951 		case 'z': retval = (int) timelib_day_of_year(t->y, t->m, t->d); break;
952 
953 		/* week */
954 		case 'W': retval = (int) isoweek; break; /* iso weeknr */
955 
956 		/* month */
957 		case 'm': case 'n': retval = (int) t->m; break;
958 		case 't': retval = (int) timelib_days_in_month(t->y, t->m); break;
959 
960 		/* year */
961 		case 'L': retval = (int) timelib_is_leap((int) t->y); break;
962 		case 'y': retval = (int) (t->y % 100); break;
963 		case 'Y': retval = (int) t->y; break;
964 		case 'o': retval = (int) isoyear; break; /* iso year */
965 
966 		/* Swatch Beat a.k.a. Internet Time */
967 		case 'B':
968 			retval = ((((long)t->sse)-(((long)t->sse) - ((((long)t->sse) % 86400) + 3600))) * 10);
969 			if (retval < 0) {
970 				retval += 864000;
971 			}
972 			/* Make sure to do this on a positive int to avoid rounding errors */
973 			retval = (retval / 864) % 1000;
974 			break;
975 
976 		/* time */
977 		case 'g': case 'h': retval = (int) ((t->h % 12) ? (int) t->h % 12 : 12); break;
978 		case 'H': case 'G': retval = (int) t->h; break;
979 		case 'i': retval = (int) t->i; break;
980 		case 's': retval = (int) t->s; break;
981 
982 		/* timezone */
983 		case 'I': retval = (int) (!localtime ? offset->is_dst : 0); break;
984 		case 'Z': retval = (int) (!localtime ? offset->offset : 0); break;
985 
986 		case 'U': retval = (int) t->sse; break;
987 	}
988 
989 	if (!localtime) {
990 		timelib_time_offset_dtor(offset);
991 	}
992 	timelib_time_dtor(t);
993 
994 	return retval;
995 }
996 /* }}} */
997 
998 /* {{{ Format a local date/time */
PHP_FUNCTION(date)999 PHP_FUNCTION(date)
1000 {
1001 	php_date(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
1002 }
1003 /* }}} */
1004 
1005 /* {{{ Format a GMT date/time */
PHP_FUNCTION(gmdate)1006 PHP_FUNCTION(gmdate)
1007 {
1008 	php_date(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
1009 }
1010 /* }}} */
1011 
1012 /* {{{ Format a local time/date as integer */
PHP_FUNCTION(idate)1013 PHP_FUNCTION(idate)
1014 {
1015 	zend_string *format;
1016 	zend_long    ts;
1017 	bool    ts_is_null = 1;
1018 	int ret;
1019 
1020 	ZEND_PARSE_PARAMETERS_START(1, 2)
1021 		Z_PARAM_STR(format)
1022 		Z_PARAM_OPTIONAL
1023 		Z_PARAM_LONG_OR_NULL(ts, ts_is_null)
1024 	ZEND_PARSE_PARAMETERS_END();
1025 
1026 	if (ZSTR_LEN(format) != 1) {
1027 		php_error_docref(NULL, E_WARNING, "idate format is one char");
1028 		RETURN_FALSE;
1029 	}
1030 
1031 	if (ts_is_null) {
1032 		ts = php_time();
1033 	}
1034 
1035 	ret = php_idate(ZSTR_VAL(format)[0], ts, 0);
1036 	if (ret == -1) {
1037 		php_error_docref(NULL, E_WARNING, "Unrecognized date format token");
1038 		RETURN_FALSE;
1039 	}
1040 	RETURN_LONG(ret);
1041 }
1042 /* }}} */
1043 
1044 /* {{{ php_date_set_tzdb - NOT THREADSAFE */
php_date_set_tzdb(timelib_tzdb * tzdb)1045 PHPAPI void php_date_set_tzdb(timelib_tzdb *tzdb)
1046 {
1047 	const timelib_tzdb *builtin = timelib_builtin_db();
1048 
1049 	if (php_version_compare(tzdb->version, builtin->version) > 0) {
1050 		php_date_global_timezone_db = tzdb;
1051 		php_date_global_timezone_db_enabled = 1;
1052 	}
1053 }
1054 /* }}} */
1055 
1056 /* {{{ php_parse_date: Backwards compatibility function */
php_parse_date(const char * string,zend_long * now)1057 PHPAPI zend_long php_parse_date(const char *string, zend_long *now)
1058 {
1059 	timelib_time *parsed_time;
1060 	timelib_error_container *error = NULL;
1061 	int           error2;
1062 	zend_long   retval;
1063 
1064 	parsed_time = timelib_strtotime(string, strlen(string), &error, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
1065 	if (error->error_count) {
1066 		timelib_time_dtor(parsed_time);
1067 		timelib_error_container_dtor(error);
1068 		return -1;
1069 	}
1070 	timelib_error_container_dtor(error);
1071 	timelib_update_ts(parsed_time, NULL);
1072 	retval = timelib_date_to_int(parsed_time, &error2);
1073 	timelib_time_dtor(parsed_time);
1074 	if (error2) {
1075 		return -1;
1076 	}
1077 	return retval;
1078 }
1079 /* }}} */
1080 
1081 /* {{{ Convert string representation of date and time to a timestamp */
PHP_FUNCTION(strtotime)1082 PHP_FUNCTION(strtotime)
1083 {
1084 	zend_string *times;
1085 	int parse_error, epoch_does_not_fit_in_zend_long;
1086 	timelib_error_container *error;
1087 	zend_long preset_ts, ts;
1088 	bool preset_ts_is_null = 1;
1089 	timelib_time *t, *now;
1090 	timelib_tzinfo *tzi;
1091 
1092 	ZEND_PARSE_PARAMETERS_START(1, 2)
1093 		Z_PARAM_STR(times)
1094 		Z_PARAM_OPTIONAL
1095 		Z_PARAM_LONG_OR_NULL(preset_ts, preset_ts_is_null)
1096 	ZEND_PARSE_PARAMETERS_END();
1097 
1098 	/* timelib_strtotime() expects the string to not be empty */
1099 	if (ZSTR_LEN(times) == 0) {
1100 		RETURN_FALSE;
1101 	}
1102 
1103 	tzi = get_timezone_info();
1104 	if (!tzi) {
1105 		return;
1106 	}
1107 
1108 	now = timelib_time_ctor();
1109 	now->tz_info = tzi;
1110 	now->zone_type = TIMELIB_ZONETYPE_ID;
1111 	timelib_unixtime2local(now,
1112 		!preset_ts_is_null ? (timelib_sll) preset_ts : (timelib_sll) php_time());
1113 
1114 	t = timelib_strtotime(ZSTR_VAL(times), ZSTR_LEN(times), &error,
1115 		DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
1116 	parse_error = error->error_count;
1117 	timelib_error_container_dtor(error);
1118 	if (parse_error) {
1119 		timelib_time_dtor(now);
1120 		timelib_time_dtor(t);
1121 		RETURN_FALSE;
1122 	}
1123 
1124 	timelib_fill_holes(t, now, TIMELIB_NO_CLONE);
1125 	timelib_update_ts(t, tzi);
1126 	ts = timelib_date_to_int(t, &epoch_does_not_fit_in_zend_long);
1127 
1128 	timelib_time_dtor(now);
1129 	timelib_time_dtor(t);
1130 
1131 	if (epoch_does_not_fit_in_zend_long) {
1132 		php_error_docref(NULL, E_WARNING, "Epoch doesn't fit in a PHP integer");
1133 		RETURN_FALSE;
1134 	}
1135 
1136 	RETURN_LONG(ts);
1137 }
1138 /* }}} */
1139 
1140 /* {{{ php_mktime - (gm)mktime helper */
php_mktime(INTERNAL_FUNCTION_PARAMETERS,bool gmt)1141 PHPAPI void php_mktime(INTERNAL_FUNCTION_PARAMETERS, bool gmt)
1142 {
1143 	zend_long hou, min, sec, mon, day, yea;
1144 	bool min_is_null = 1, sec_is_null = 1, mon_is_null = 1, day_is_null = 1, yea_is_null = 1;
1145 	timelib_time *now;
1146 	timelib_tzinfo *tzi = NULL;
1147 	zend_long ts, adjust_seconds = 0;
1148 	int epoch_does_not_fit_in_zend_long;
1149 
1150 	ZEND_PARSE_PARAMETERS_START(1, 6)
1151 		Z_PARAM_LONG(hou)
1152 		Z_PARAM_OPTIONAL
1153 		Z_PARAM_LONG_OR_NULL(min, min_is_null)
1154 		Z_PARAM_LONG_OR_NULL(sec, sec_is_null)
1155 		Z_PARAM_LONG_OR_NULL(mon, mon_is_null)
1156 		Z_PARAM_LONG_OR_NULL(day, day_is_null)
1157 		Z_PARAM_LONG_OR_NULL(yea, yea_is_null)
1158 	ZEND_PARSE_PARAMETERS_END();
1159 
1160 	/* Initialize structure with current time */
1161 	now = timelib_time_ctor();
1162 	if (gmt) {
1163 		timelib_unixtime2gmt(now, (timelib_sll) php_time());
1164 	} else {
1165 		tzi = get_timezone_info();
1166 		if (!tzi) {
1167 			return;
1168 		}
1169 		now->tz_info = tzi;
1170 		now->zone_type = TIMELIB_ZONETYPE_ID;
1171 		timelib_unixtime2local(now, (timelib_sll) php_time());
1172 	}
1173 
1174 	now->h = hou;
1175 
1176 	if (!min_is_null) {
1177 		now->i = min;
1178 	}
1179 
1180 	if (!sec_is_null) {
1181 		now->s = sec;
1182 	}
1183 
1184 	if (!mon_is_null) {
1185 		now->m = mon;
1186 	}
1187 
1188 	if (!day_is_null) {
1189 		now->d = day;
1190 	}
1191 
1192 	if (!yea_is_null) {
1193 		if (yea >= 0 && yea < 70) {
1194 			yea += 2000;
1195 		} else if (yea >= 70 && yea <= 100) {
1196 			yea += 1900;
1197 		}
1198 		now->y = yea;
1199 	}
1200 
1201 	/* Update the timestamp */
1202 	if (gmt) {
1203 		timelib_update_ts(now, NULL);
1204 	} else {
1205 		timelib_update_ts(now, tzi);
1206 	}
1207 
1208 	/* Clean up and return */
1209 	ts = timelib_date_to_int(now, &epoch_does_not_fit_in_zend_long);
1210 
1211 	if (epoch_does_not_fit_in_zend_long) {
1212 		timelib_time_dtor(now);
1213 		php_error_docref(NULL, E_WARNING, "Epoch doesn't fit in a PHP integer");
1214 		RETURN_FALSE;
1215 	}
1216 
1217 	ts += adjust_seconds;
1218 	timelib_time_dtor(now);
1219 
1220 	RETURN_LONG(ts);
1221 }
1222 /* }}} */
1223 
1224 /* {{{ Get UNIX timestamp for a date */
PHP_FUNCTION(mktime)1225 PHP_FUNCTION(mktime)
1226 {
1227 	php_mktime(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
1228 }
1229 /* }}} */
1230 
1231 /* {{{ Get UNIX timestamp for a GMT date */
PHP_FUNCTION(gmmktime)1232 PHP_FUNCTION(gmmktime)
1233 {
1234 	php_mktime(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
1235 }
1236 /* }}} */
1237 
1238 /* {{{ Returns true(1) if it is a valid date in gregorian calendar */
PHP_FUNCTION(checkdate)1239 PHP_FUNCTION(checkdate)
1240 {
1241 	zend_long m, d, y;
1242 
1243 	ZEND_PARSE_PARAMETERS_START(3, 3)
1244 		Z_PARAM_LONG(m)
1245 		Z_PARAM_LONG(d)
1246 		Z_PARAM_LONG(y)
1247 	ZEND_PARSE_PARAMETERS_END();
1248 
1249 	if (y < 1 || y > 32767 || !timelib_valid_date(y, m, d)) {
1250 		RETURN_FALSE;
1251 	}
1252 	RETURN_TRUE;	/* True : This month, day, year arguments are valid */
1253 }
1254 /* }}} */
1255 
1256 /* {{{ php_strftime - (gm)strftime helper */
php_strftime(INTERNAL_FUNCTION_PARAMETERS,bool gmt)1257 PHPAPI void php_strftime(INTERNAL_FUNCTION_PARAMETERS, bool gmt)
1258 {
1259 	zend_string         *format;
1260 	zend_long            timestamp;
1261 	bool            timestamp_is_null = 1;
1262 	struct tm            ta;
1263 	int                  max_reallocs = 5;
1264 	size_t               buf_len = 256, real_len;
1265 	timelib_time        *ts;
1266 	timelib_tzinfo      *tzi;
1267 	timelib_time_offset *offset = NULL;
1268 	zend_string 		*buf;
1269 
1270 	ZEND_PARSE_PARAMETERS_START(1, 2)
1271 		Z_PARAM_STR(format)
1272 		Z_PARAM_OPTIONAL
1273 		Z_PARAM_LONG_OR_NULL(timestamp, timestamp_is_null)
1274 	ZEND_PARSE_PARAMETERS_END();
1275 
1276 	if (ZSTR_LEN(format) == 0) {
1277 		RETURN_FALSE;
1278 	}
1279 
1280 	if (timestamp_is_null) {
1281 		timestamp = (zend_long) php_time();
1282 	}
1283 
1284 	ts = timelib_time_ctor();
1285 	if (gmt) {
1286 		tzi = NULL;
1287 		timelib_unixtime2gmt(ts, (timelib_sll) timestamp);
1288 	} else {
1289 		tzi = get_timezone_info();
1290 		if (!tzi) {
1291 			return;
1292 		}
1293 		ts->tz_info = tzi;
1294 		ts->zone_type = TIMELIB_ZONETYPE_ID;
1295 		timelib_unixtime2local(ts, (timelib_sll) timestamp);
1296 	}
1297 	ta.tm_sec   = ts->s;
1298 	ta.tm_min   = ts->i;
1299 	ta.tm_hour  = ts->h;
1300 	ta.tm_mday  = ts->d;
1301 	ta.tm_mon   = ts->m - 1;
1302 	ta.tm_year  = ts->y - 1900;
1303 	ta.tm_wday  = timelib_day_of_week(ts->y, ts->m, ts->d);
1304 	ta.tm_yday  = timelib_day_of_year(ts->y, ts->m, ts->d);
1305 	if (gmt) {
1306 		ta.tm_isdst = 0;
1307 #ifdef HAVE_STRUCT_TM_TM_GMTOFF
1308 		ta.tm_gmtoff = 0;
1309 #endif
1310 #ifdef HAVE_STRUCT_TM_TM_ZONE
1311 		ta.tm_zone = "GMT";
1312 #endif
1313 	} else {
1314 		offset = timelib_get_time_zone_info(timestamp, tzi);
1315 
1316 		ta.tm_isdst = offset->is_dst;
1317 #ifdef HAVE_STRUCT_TM_TM_GMTOFF
1318 		ta.tm_gmtoff = offset->offset;
1319 #endif
1320 #ifdef HAVE_STRUCT_TM_TM_ZONE
1321 		ta.tm_zone = offset->abbr;
1322 #endif
1323 	}
1324 
1325 	/* VS2012 crt has a bug where strftime crash with %z and %Z format when the
1326 	   initial buffer is too small. See
1327 	   http://connect.microsoft.com/VisualStudio/feedback/details/759720/vs2012-strftime-crash-with-z-formatting-code */
1328 	buf = zend_string_alloc(buf_len, 0);
1329 	while ((real_len = strftime(ZSTR_VAL(buf), buf_len, ZSTR_VAL(format), &ta)) == buf_len || real_len == 0) {
1330 		buf_len *= 2;
1331 		buf = zend_string_extend(buf, buf_len, 0);
1332 		if (!--max_reallocs) {
1333 			break;
1334 		}
1335 	}
1336 #ifdef PHP_WIN32
1337 	/* VS2012 strftime() returns number of characters, not bytes.
1338 		See VC++11 bug id 766205. */
1339 	if (real_len > 0) {
1340 		real_len = strlen(buf->val);
1341 	}
1342 #endif
1343 
1344 	timelib_time_dtor(ts);
1345 	if (!gmt) {
1346 		timelib_time_offset_dtor(offset);
1347 	}
1348 
1349 	if (real_len && real_len != buf_len) {
1350 		buf = zend_string_truncate(buf, real_len, 0);
1351 		RETURN_NEW_STR(buf);
1352 	}
1353 	zend_string_efree(buf);
1354 	RETURN_FALSE;
1355 }
1356 /* }}} */
1357 
1358 /* {{{ Format a local time/date according to locale settings */
PHP_FUNCTION(strftime)1359 PHP_FUNCTION(strftime)
1360 {
1361 	php_strftime(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
1362 }
1363 /* }}} */
1364 
1365 /* {{{ Format a GMT/UCT time/date according to locale settings */
PHP_FUNCTION(gmstrftime)1366 PHP_FUNCTION(gmstrftime)
1367 {
1368 	php_strftime(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
1369 }
1370 /* }}} */
1371 
1372 /* {{{ Return current UNIX timestamp */
PHP_FUNCTION(time)1373 PHP_FUNCTION(time)
1374 {
1375 	ZEND_PARSE_PARAMETERS_NONE();
1376 
1377 	RETURN_LONG((zend_long)php_time());
1378 }
1379 /* }}} */
1380 
1381 /* {{{ 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)1382 PHP_FUNCTION(localtime)
1383 {
1384 	zend_long timestamp;
1385 	bool timestamp_is_null = 1;
1386 	bool associative = 0;
1387 	timelib_tzinfo *tzi;
1388 	timelib_time   *ts;
1389 
1390 	ZEND_PARSE_PARAMETERS_START(0, 2)
1391 		Z_PARAM_OPTIONAL
1392 		Z_PARAM_LONG_OR_NULL(timestamp, timestamp_is_null)
1393 		Z_PARAM_BOOL(associative)
1394 	ZEND_PARSE_PARAMETERS_END();
1395 
1396 	if (timestamp_is_null) {
1397 		timestamp = (zend_long) php_time();
1398 	}
1399 
1400 	tzi = get_timezone_info();
1401 	if (!tzi) {
1402 		RETURN_THROWS();
1403 	}
1404 	ts = timelib_time_ctor();
1405 	ts->tz_info = tzi;
1406 	ts->zone_type = TIMELIB_ZONETYPE_ID;
1407 	timelib_unixtime2local(ts, (timelib_sll) timestamp);
1408 
1409 	array_init(return_value);
1410 
1411 	if (associative) {
1412 		add_assoc_long(return_value, "tm_sec",   ts->s);
1413 		add_assoc_long(return_value, "tm_min",   ts->i);
1414 		add_assoc_long(return_value, "tm_hour",  ts->h);
1415 		add_assoc_long(return_value, "tm_mday",  ts->d);
1416 		add_assoc_long(return_value, "tm_mon",   ts->m - 1);
1417 		add_assoc_long(return_value, "tm_year",  ts->y - 1900);
1418 		add_assoc_long(return_value, "tm_wday",  timelib_day_of_week(ts->y, ts->m, ts->d));
1419 		add_assoc_long(return_value, "tm_yday",  timelib_day_of_year(ts->y, ts->m, ts->d));
1420 		add_assoc_long(return_value, "tm_isdst", ts->dst);
1421 	} else {
1422 		add_next_index_long(return_value, ts->s);
1423 		add_next_index_long(return_value, ts->i);
1424 		add_next_index_long(return_value, ts->h);
1425 		add_next_index_long(return_value, ts->d);
1426 		add_next_index_long(return_value, ts->m - 1);
1427 		add_next_index_long(return_value, ts->y- 1900);
1428 		add_next_index_long(return_value, timelib_day_of_week(ts->y, ts->m, ts->d));
1429 		add_next_index_long(return_value, timelib_day_of_year(ts->y, ts->m, ts->d));
1430 		add_next_index_long(return_value, ts->dst);
1431 	}
1432 
1433 	timelib_time_dtor(ts);
1434 }
1435 /* }}} */
1436 
1437 /* {{{ Get date/time information */
PHP_FUNCTION(getdate)1438 PHP_FUNCTION(getdate)
1439 {
1440 	zend_long timestamp;
1441 	bool timestamp_is_null = 1;
1442 	timelib_tzinfo *tzi;
1443 	timelib_time   *ts;
1444 
1445 	ZEND_PARSE_PARAMETERS_START(0, 1)
1446 		Z_PARAM_OPTIONAL
1447 		Z_PARAM_LONG_OR_NULL(timestamp, timestamp_is_null)
1448 	ZEND_PARSE_PARAMETERS_END();
1449 
1450 	if (timestamp_is_null) {
1451 		timestamp = (zend_long) php_time();
1452 	}
1453 
1454 	tzi = get_timezone_info();
1455 	if (!tzi) {
1456 		RETURN_THROWS();
1457 	}
1458 	ts = timelib_time_ctor();
1459 	ts->tz_info = tzi;
1460 	ts->zone_type = TIMELIB_ZONETYPE_ID;
1461 	timelib_unixtime2local(ts, (timelib_sll) timestamp);
1462 
1463 	array_init(return_value);
1464 
1465 	add_assoc_long(return_value, "seconds", ts->s);
1466 	add_assoc_long(return_value, "minutes", ts->i);
1467 	add_assoc_long(return_value, "hours", ts->h);
1468 	add_assoc_long(return_value, "mday", ts->d);
1469 	add_assoc_long(return_value, "wday", timelib_day_of_week(ts->y, ts->m, ts->d));
1470 	add_assoc_long(return_value, "mon", ts->m);
1471 	add_assoc_long(return_value, "year", ts->y);
1472 	add_assoc_long(return_value, "yday", timelib_day_of_year(ts->y, ts->m, ts->d));
1473 	add_assoc_string(return_value, "weekday", php_date_full_day_name(ts->y, ts->m, ts->d));
1474 	add_assoc_string(return_value, "month", mon_full_names[ts->m - 1]);
1475 	add_index_long(return_value, 0, timestamp);
1476 
1477 	timelib_time_dtor(ts);
1478 }
1479 /* }}} */
1480 
create_date_period_datetime(timelib_time * datetime,zend_class_entry * ce,zval * zv)1481 static void create_date_period_datetime(timelib_time *datetime, zend_class_entry *ce, zval *zv)
1482 {
1483 	if (datetime) {
1484 		php_date_obj *date_obj;
1485 
1486 		object_init_ex(zv, ce);
1487 		date_obj = Z_PHPDATE_P(zv);
1488 		date_obj->time = timelib_time_clone(datetime);
1489 	} else {
1490 		ZVAL_NULL(zv);
1491 	}
1492 }
1493 
create_date_period_interval(timelib_rel_time * interval,zval * zv)1494 static void create_date_period_interval(timelib_rel_time *interval, zval *zv)
1495 {
1496 	if (interval) {
1497 		php_interval_obj *interval_obj;
1498 
1499 		object_init_ex(zv, date_ce_interval);
1500 		interval_obj = Z_PHPINTERVAL_P(zv);
1501 		interval_obj->diff = timelib_rel_time_clone(interval);
1502 		interval_obj->initialized = 1;
1503 	} else {
1504 		ZVAL_NULL(zv);
1505 	}
1506 }
1507 
write_date_period_property(zend_object * obj,const char * name,const size_t name_len,zval * zv)1508 static void write_date_period_property(zend_object *obj, const char *name, const size_t name_len, zval *zv)
1509 {
1510 	zend_string *property_name = zend_string_init(name, name_len, 0);
1511 
1512 	zend_std_write_property(obj, property_name, zv, NULL);
1513 
1514 	zval_ptr_dtor(zv);
1515 	zend_string_release(property_name);
1516 }
1517 
initialize_date_period_properties(php_period_obj * period_obj)1518 static void initialize_date_period_properties(php_period_obj *period_obj)
1519 {
1520 	zval zv;
1521 
1522 	/* rebuild properties */
1523 	zend_std_get_properties_ex(&period_obj->std);
1524 
1525 	create_date_period_datetime(period_obj->start, period_obj->start_ce, &zv);
1526 	write_date_period_property(&period_obj->std, "start", sizeof("start") - 1, &zv);
1527 
1528 	create_date_period_datetime(period_obj->current, period_obj->start_ce, &zv);
1529 	write_date_period_property(&period_obj->std, "current", sizeof("current") - 1, &zv);
1530 
1531 	create_date_period_datetime(period_obj->end, period_obj->start_ce, &zv);
1532 	write_date_period_property(&period_obj->std, "end", sizeof("end") - 1, &zv);
1533 
1534 	create_date_period_interval(period_obj->interval, &zv);
1535 	write_date_period_property(&period_obj->std, "interval", sizeof("interval") - 1, &zv);
1536 
1537 	ZVAL_LONG(&zv, (zend_long) period_obj->recurrences);
1538 	write_date_period_property(&period_obj->std, "recurrences", sizeof("recurrences") - 1, &zv);
1539 
1540 	ZVAL_BOOL(&zv, period_obj->include_start_date);
1541 	write_date_period_property(&period_obj->std, "include_start_date", sizeof("include_start_date") - 1, &zv);
1542 
1543 	ZVAL_BOOL(&zv, period_obj->include_end_date);
1544 	write_date_period_property(&period_obj->std, "include_end_date", sizeof("include_end_date") - 1, &zv);
1545 }
1546 
1547 /* define an overloaded iterator structure */
1548 typedef struct {
1549 	zend_object_iterator  intern;
1550 	zval                  current;
1551 	php_period_obj       *object;
1552 	int                   current_index;
1553 } date_period_it;
1554 
1555 /* {{{ date_period_it_invalidate_current */
date_period_it_invalidate_current(zend_object_iterator * iter)1556 static void date_period_it_invalidate_current(zend_object_iterator *iter)
1557 {
1558 	date_period_it *iterator = (date_period_it *)iter;
1559 
1560 	if (Z_TYPE(iterator->current) != IS_UNDEF) {
1561 		zval_ptr_dtor(&iterator->current);
1562 		ZVAL_UNDEF(&iterator->current);
1563 	}
1564 }
1565 /* }}} */
1566 
1567 /* {{{ date_period_it_dtor */
date_period_it_dtor(zend_object_iterator * iter)1568 static void date_period_it_dtor(zend_object_iterator *iter)
1569 {
1570 	date_period_it *iterator = (date_period_it *)iter;
1571 
1572 	date_period_it_invalidate_current(iter);
1573 
1574 	zval_ptr_dtor(&iterator->intern.data);
1575 }
1576 /* }}} */
1577 
1578 /* {{{ date_period_it_has_more */
date_period_it_has_more(zend_object_iterator * iter)1579 static zend_result date_period_it_has_more(zend_object_iterator *iter)
1580 {
1581 	date_period_it *iterator = (date_period_it *)iter;
1582 	php_period_obj *object   = Z_PHPPERIOD_P(&iterator->intern.data);
1583 
1584 	if (object->end) {
1585 		if (object->current->sse == object->end->sse) {
1586 			if (object->include_end_date) {
1587 				return object->current->us <= object->end->us ? SUCCESS : FAILURE;
1588 			} else {
1589 				return object->current->us < object->end->us ? SUCCESS : FAILURE;
1590 			}
1591 		}
1592 
1593 		return object->current->sse < object->end->sse ? SUCCESS : FAILURE;
1594 	} else {
1595 		return (iterator->current_index < object->recurrences) ? SUCCESS : FAILURE;
1596 	}
1597 }
1598 /* }}} */
1599 
get_base_date_class(zend_class_entry * start_ce)1600 static zend_class_entry *get_base_date_class(zend_class_entry *start_ce)
1601 {
1602 	zend_class_entry *tmp = start_ce;
1603 
1604 	while (tmp != date_ce_date && tmp != date_ce_immutable && tmp->parent) {
1605 		tmp = tmp->parent;
1606 	}
1607 
1608 	return tmp;
1609 }
1610 
1611 /* {{{ date_period_it_current_data */
date_period_it_current_data(zend_object_iterator * iter)1612 static zval *date_period_it_current_data(zend_object_iterator *iter)
1613 {
1614 	date_period_it *iterator = (date_period_it *)iter;
1615 	php_period_obj *object   = Z_PHPPERIOD_P(&iterator->intern.data);
1616 	timelib_time   *it_time = object->current;
1617 	php_date_obj   *newdateobj;
1618 
1619 	/* Create new object */
1620 	php_date_instantiate(get_base_date_class(object->start_ce), &iterator->current);
1621 	newdateobj = Z_PHPDATE_P(&iterator->current);
1622 	newdateobj->time = timelib_time_ctor();
1623 	*newdateobj->time = *it_time;
1624 	if (it_time->tz_abbr) {
1625 		newdateobj->time->tz_abbr = timelib_strdup(it_time->tz_abbr);
1626 	}
1627 	if (it_time->tz_info) {
1628 		newdateobj->time->tz_info = it_time->tz_info;
1629 	}
1630 
1631 	return &iterator->current;
1632 }
1633 /* }}} */
1634 
1635 /* {{{ date_period_it_current_key */
date_period_it_current_key(zend_object_iterator * iter,zval * key)1636 static void date_period_it_current_key(zend_object_iterator *iter, zval *key)
1637 {
1638 	date_period_it *iterator = (date_period_it *)iter;
1639 	ZVAL_LONG(key, iterator->current_index);
1640 }
1641 /* }}} */
1642 
date_period_advance(timelib_time * it_time,timelib_rel_time * interval)1643 static void date_period_advance(timelib_time *it_time, timelib_rel_time *interval)
1644 {
1645 	it_time->have_relative = 1;
1646 	it_time->relative = *interval;
1647 	it_time->sse_uptodate = 0;
1648 	timelib_update_ts(it_time, NULL);
1649 	timelib_update_from_sse(it_time);
1650 }
1651 
1652 /* {{{ date_period_it_move_forward */
date_period_it_move_forward(zend_object_iterator * iter)1653 static void date_period_it_move_forward(zend_object_iterator *iter)
1654 {
1655 	date_period_it *iterator = (date_period_it *)iter;
1656 	php_period_obj *object   = Z_PHPPERIOD_P(&iterator->intern.data);
1657 	timelib_time   *it_time  = object->current;
1658 	zval current_zv;
1659 
1660 	date_period_advance(it_time, object->interval);
1661 
1662 	/* rebuild properties */
1663 	zend_std_get_properties_ex(&object->std);
1664 
1665 	create_date_period_datetime(object->current, object->start_ce, &current_zv);
1666 	zend_string *property_name = ZSTR_INIT_LITERAL("current", 0);
1667 	zend_std_write_property(&object->std, property_name, &current_zv, NULL);
1668 	zval_ptr_dtor(&current_zv);
1669 	zend_string_release(property_name);
1670 
1671 	iterator->current_index++;
1672 	date_period_it_invalidate_current(iter);
1673 }
1674 /* }}} */
1675 
1676 /* {{{ date_period_it_rewind */
date_period_it_rewind(zend_object_iterator * iter)1677 static void date_period_it_rewind(zend_object_iterator *iter)
1678 {
1679 	date_period_it *iterator = (date_period_it *)iter;
1680 
1681 	iterator->current_index = 0;
1682 	if (iterator->object->current) {
1683 		timelib_time_dtor(iterator->object->current);
1684 	}
1685 	if (!iterator->object->start) {
1686 		date_throw_uninitialized_error(date_ce_period);
1687 		return;
1688 	}
1689 
1690 	iterator->object->current = timelib_time_clone(iterator->object->start);
1691 
1692 	if (!iterator->object->include_start_date) {
1693 		date_period_advance(iterator->object->current, iterator->object->interval);
1694 	}
1695 
1696 	date_period_it_invalidate_current(iter);
1697 }
1698 /* }}} */
1699 
1700 /* iterator handler table */
1701 static const zend_object_iterator_funcs date_period_it_funcs = {
1702 	date_period_it_dtor,
1703 	date_period_it_has_more,
1704 	date_period_it_current_data,
1705 	date_period_it_current_key,
1706 	date_period_it_move_forward,
1707 	date_period_it_rewind,
1708 	date_period_it_invalidate_current,
1709 	NULL, /* get_gc */
1710 };
1711 
date_object_period_get_iterator(zend_class_entry * ce,zval * object,int by_ref)1712 static zend_object_iterator *date_object_period_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */
1713 {
1714 	date_period_it *iterator;
1715 
1716 	if (by_ref) {
1717 		zend_throw_error(NULL, "An iterator cannot be used with foreach by reference");
1718 		return NULL;
1719 	}
1720 
1721 	iterator = emalloc(sizeof(date_period_it));
1722 
1723 	zend_iterator_init((zend_object_iterator*)iterator);
1724 
1725 	ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object));
1726 	iterator->intern.funcs = &date_period_it_funcs;
1727 	iterator->object = Z_PHPPERIOD_P(object);
1728 	ZVAL_UNDEF(&iterator->current);
1729 
1730 	return (zend_object_iterator*)iterator;
1731 } /* }}} */
1732 
implement_date_interface_handler(zend_class_entry * interface,zend_class_entry * implementor)1733 static int implement_date_interface_handler(zend_class_entry *interface, zend_class_entry *implementor) /* {{{ */
1734 {
1735 	if (implementor->type == ZEND_USER_CLASS &&
1736 		!instanceof_function(implementor, date_ce_date) &&
1737 		!instanceof_function(implementor, date_ce_immutable)
1738 	) {
1739 		zend_error_noreturn(E_ERROR, "DateTimeInterface can't be implemented by user classes");
1740 	}
1741 
1742 	return SUCCESS;
1743 } /* }}} */
1744 
date_interval_has_property(zend_object * object,zend_string * name,int type,void ** cache_slot)1745 static int date_interval_has_property(zend_object *object, zend_string *name, int type, void **cache_slot) /* {{{ */
1746 {
1747 	php_interval_obj *obj;
1748 	zval rv;
1749 	zval *prop;
1750 	int retval = 0;
1751 
1752 	obj = php_interval_obj_from_obj(object);
1753 
1754 	if (!obj->initialized) {
1755 		retval = zend_std_has_property(object, name, type, cache_slot);
1756 		return retval;
1757 	}
1758 
1759 	prop = date_interval_read_property(object, name, BP_VAR_IS, cache_slot, &rv);
1760 
1761 	if (prop != &EG(uninitialized_zval)) {
1762 		if (type == 2) {
1763 			retval = 1;
1764 		} else if (type == 1) {
1765 			retval = zend_is_true(prop);
1766 		} else if (type == 0) {
1767 			retval = (Z_TYPE_P(prop) != IS_NULL);
1768 		}
1769 	} else {
1770 		retval = zend_std_has_property(object, name, type, cache_slot);
1771 	}
1772 
1773 	return retval;
1774 
1775 }
1776 /* }}} */
1777 
date_register_classes(void)1778 static void date_register_classes(void) /* {{{ */
1779 {
1780 	date_ce_interface = register_class_DateTimeInterface();
1781 	date_ce_interface->interface_gets_implemented = implement_date_interface_handler;
1782 
1783 	date_ce_date = register_class_DateTime(date_ce_interface);
1784 	date_ce_date->create_object = date_object_new_date;
1785 	date_ce_date->default_object_handlers = &date_object_handlers_date;
1786 	memcpy(&date_object_handlers_date, &std_object_handlers, sizeof(zend_object_handlers));
1787 	date_object_handlers_date.offset = XtOffsetOf(php_date_obj, std);
1788 	date_object_handlers_date.free_obj = date_object_free_storage_date;
1789 	date_object_handlers_date.clone_obj = date_object_clone_date;
1790 	date_object_handlers_date.compare = date_object_compare_date;
1791 	date_object_handlers_date.get_properties_for = date_object_get_properties_for;
1792 	date_object_handlers_date.get_gc = date_object_get_gc;
1793 
1794 	date_ce_immutable = register_class_DateTimeImmutable(date_ce_interface);
1795 	date_ce_immutable->create_object = date_object_new_date;
1796 	date_ce_immutable->default_object_handlers = &date_object_handlers_date;
1797 	memcpy(&date_object_handlers_immutable, &std_object_handlers, sizeof(zend_object_handlers));
1798 	date_object_handlers_immutable.clone_obj = date_object_clone_date;
1799 	date_object_handlers_immutable.compare = date_object_compare_date;
1800 	date_object_handlers_immutable.get_properties_for = date_object_get_properties_for;
1801 	date_object_handlers_immutable.get_gc = date_object_get_gc;
1802 
1803 	date_ce_timezone = register_class_DateTimeZone();
1804 	date_ce_timezone->create_object = date_object_new_timezone;
1805 	date_ce_timezone->default_object_handlers = &date_object_handlers_timezone;
1806 	memcpy(&date_object_handlers_timezone, &std_object_handlers, sizeof(zend_object_handlers));
1807 	date_object_handlers_timezone.offset = XtOffsetOf(php_timezone_obj, std);
1808 	date_object_handlers_timezone.free_obj = date_object_free_storage_timezone;
1809 	date_object_handlers_timezone.clone_obj = date_object_clone_timezone;
1810 	date_object_handlers_timezone.get_properties_for = date_object_get_properties_for_timezone;
1811 	date_object_handlers_timezone.get_gc = date_object_get_gc_timezone;
1812 	date_object_handlers_timezone.get_debug_info = date_object_get_debug_info_timezone;
1813 	date_object_handlers_timezone.compare = date_object_compare_timezone;
1814 
1815 	date_ce_interval = register_class_DateInterval();
1816 	date_ce_interval->create_object = date_object_new_interval;
1817 	date_ce_interval->default_object_handlers = &date_object_handlers_interval;
1818 	memcpy(&date_object_handlers_interval, &std_object_handlers, sizeof(zend_object_handlers));
1819 	date_object_handlers_interval.offset = XtOffsetOf(php_interval_obj, std);
1820 	date_object_handlers_interval.free_obj = date_object_free_storage_interval;
1821 	date_object_handlers_interval.clone_obj = date_object_clone_interval;
1822 	date_object_handlers_interval.has_property = date_interval_has_property;
1823 	date_object_handlers_interval.read_property = date_interval_read_property;
1824 	date_object_handlers_interval.write_property = date_interval_write_property;
1825 	date_object_handlers_interval.get_properties = date_object_get_properties_interval;
1826 	date_object_handlers_interval.get_property_ptr_ptr = date_interval_get_property_ptr_ptr;
1827 	date_object_handlers_interval.get_gc = date_object_get_gc_interval;
1828 	date_object_handlers_interval.compare = date_interval_compare_objects;
1829 
1830 	date_ce_period = register_class_DatePeriod(zend_ce_aggregate);
1831 	date_ce_period->create_object = date_object_new_period;
1832 	date_ce_period->default_object_handlers = &date_object_handlers_period;
1833 	date_ce_period->get_iterator = date_object_period_get_iterator;
1834 	memcpy(&date_object_handlers_period, &std_object_handlers, sizeof(zend_object_handlers));
1835 	date_object_handlers_period.offset = XtOffsetOf(php_period_obj, std);
1836 	date_object_handlers_period.free_obj = date_object_free_storage_period;
1837 	date_object_handlers_period.clone_obj = date_object_clone_period;
1838 	date_object_handlers_period.get_gc = date_object_get_gc_period;
1839 	date_object_handlers_period.get_property_ptr_ptr = date_period_get_property_ptr_ptr;
1840 	date_object_handlers_period.read_property = date_period_read_property;
1841 	date_object_handlers_period.write_property = date_period_write_property;
1842 
1843 	date_ce_date_error = register_class_DateError(zend_ce_error);
1844 	date_ce_date_object_error = register_class_DateObjectError(date_ce_date_error);
1845 	date_ce_date_range_error = register_class_DateRangeError(date_ce_date_error);
1846 
1847 	date_ce_date_exception = register_class_DateException(zend_ce_exception);
1848 	date_ce_date_invalid_timezone_exception = register_class_DateInvalidTimeZoneException(date_ce_date_exception);
1849 	date_ce_date_invalid_operation_exception = register_class_DateInvalidOperationException(date_ce_date_exception);
1850 	date_ce_date_malformed_string_exception = register_class_DateMalformedStringException(date_ce_date_exception);
1851 	date_ce_date_malformed_interval_string_exception = register_class_DateMalformedIntervalStringException(date_ce_date_exception);
1852 	date_ce_date_malformed_period_string_exception = register_class_DateMalformedPeriodStringException(date_ce_date_exception);
1853 } /* }}} */
1854 
date_object_new_date(zend_class_entry * class_type)1855 static zend_object *date_object_new_date(zend_class_entry *class_type) /* {{{ */
1856 {
1857 	php_date_obj *intern = zend_object_alloc(sizeof(php_date_obj), class_type);
1858 
1859 	zend_object_std_init(&intern->std, class_type);
1860 	object_properties_init(&intern->std, class_type);
1861 
1862 	return &intern->std;
1863 } /* }}} */
1864 
date_object_clone_date(zend_object * this_ptr)1865 static zend_object *date_object_clone_date(zend_object *this_ptr) /* {{{ */
1866 {
1867 	php_date_obj *old_obj = php_date_obj_from_obj(this_ptr);
1868 	php_date_obj *new_obj = php_date_obj_from_obj(date_object_new_date(old_obj->std.ce));
1869 
1870 	zend_objects_clone_members(&new_obj->std, &old_obj->std);
1871 	if (!old_obj->time) {
1872 		return &new_obj->std;
1873 	}
1874 
1875 	/* this should probably moved to a new `timelib_time *timelime_time_clone(timelib_time *)` */
1876 	new_obj->time = timelib_time_ctor();
1877 	*new_obj->time = *old_obj->time;
1878 	if (old_obj->time->tz_abbr) {
1879 		new_obj->time->tz_abbr = timelib_strdup(old_obj->time->tz_abbr);
1880 	}
1881 	if (old_obj->time->tz_info) {
1882 		new_obj->time->tz_info = old_obj->time->tz_info;
1883 	}
1884 
1885 	return &new_obj->std;
1886 } /* }}} */
1887 
date_clone_immutable(zval * object,zval * new_object)1888 static void date_clone_immutable(zval *object, zval *new_object) /* {{{ */
1889 {
1890 	ZVAL_OBJ(new_object, date_object_clone_date(Z_OBJ_P(object)));
1891 } /* }}} */
1892 
date_object_compare_date(zval * d1,zval * d2)1893 static int date_object_compare_date(zval *d1, zval *d2) /* {{{ */
1894 {
1895 	php_date_obj *o1;
1896 	php_date_obj *o2;
1897 
1898 	ZEND_COMPARE_OBJECTS_FALLBACK(d1, d2);
1899 
1900 	o1 = Z_PHPDATE_P(d1);
1901 	o2 = Z_PHPDATE_P(d2);
1902 
1903 	if (!o1->time || !o2->time) {
1904 		zend_throw_error(date_ce_date_object_error, "Trying to compare an incomplete DateTime or DateTimeImmutable object");
1905 		return ZEND_UNCOMPARABLE;
1906 	}
1907 	if (!o1->time->sse_uptodate) {
1908 		timelib_update_ts(o1->time, o1->time->tz_info);
1909 	}
1910 	if (!o2->time->sse_uptodate) {
1911 		timelib_update_ts(o2->time, o2->time->tz_info);
1912 	}
1913 
1914 	return timelib_time_compare(o1->time, o2->time);
1915 } /* }}} */
1916 
date_object_get_gc(zend_object * object,zval ** table,int * n)1917 static HashTable *date_object_get_gc(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_get_gc_timezone(zend_object * object,zval ** table,int * n)1924 static HashTable *date_object_get_gc_timezone(zend_object *object, zval **table, int *n) /* {{{ */
1925 {
1926 	*table = NULL;
1927 	*n = 0;
1928 	return zend_std_get_properties(object);
1929 } /* }}} */
1930 
date_object_to_hash(php_date_obj * dateobj,HashTable * props)1931 static void date_object_to_hash(php_date_obj *dateobj, HashTable *props)
1932 {
1933 	zval zv;
1934 
1935 	/* first we add the date and time in ISO format */
1936 	ZVAL_STR(&zv, date_format("x-m-d H:i:s.u", sizeof("x-m-d H:i:s.u")-1, dateobj->time, 1));
1937 	zend_hash_str_update(props, "date", sizeof("date")-1, &zv);
1938 
1939 	/* then we add the timezone name (or similar) */
1940 	if (dateobj->time->is_localtime) {
1941 		ZVAL_LONG(&zv, dateobj->time->zone_type);
1942 		zend_hash_str_update(props, "timezone_type", sizeof("timezone_type")-1, &zv);
1943 
1944 		switch (dateobj->time->zone_type) {
1945 			case TIMELIB_ZONETYPE_ID:
1946 				ZVAL_STRING(&zv, dateobj->time->tz_info->name);
1947 				break;
1948 			case TIMELIB_ZONETYPE_OFFSET: {
1949 				zend_string *tmpstr = zend_string_alloc(sizeof("UTC+05:00")-1, 0);
1950 				int utc_offset = dateobj->time->z;
1951 
1952 				ZSTR_LEN(tmpstr) = snprintf(ZSTR_VAL(tmpstr), sizeof("+05:00"), "%c%02d:%02d",
1953 					utc_offset < 0 ? '-' : '+',
1954 					abs(utc_offset / 3600),
1955 					abs(((utc_offset % 3600) / 60)));
1956 
1957 				ZVAL_NEW_STR(&zv, tmpstr);
1958 				}
1959 				break;
1960 			case TIMELIB_ZONETYPE_ABBR:
1961 				ZVAL_STRING(&zv, dateobj->time->tz_abbr);
1962 				break;
1963 		}
1964 		zend_hash_str_update(props, "timezone", sizeof("timezone")-1, &zv);
1965 	}
1966 }
1967 
date_object_get_properties_for(zend_object * object,zend_prop_purpose purpose)1968 static HashTable *date_object_get_properties_for(zend_object *object, zend_prop_purpose purpose) /* {{{ */
1969 {
1970 	HashTable *props;
1971 	php_date_obj *dateobj;
1972 
1973 	switch (purpose) {
1974 		case ZEND_PROP_PURPOSE_DEBUG:
1975 		case ZEND_PROP_PURPOSE_SERIALIZE:
1976 		case ZEND_PROP_PURPOSE_VAR_EXPORT:
1977 		case ZEND_PROP_PURPOSE_JSON:
1978 		case ZEND_PROP_PURPOSE_ARRAY_CAST:
1979 			break;
1980 		default:
1981 			return zend_std_get_properties_for(object, purpose);
1982 	}
1983 
1984 	dateobj = php_date_obj_from_obj(object);
1985 	props = zend_array_dup(zend_std_get_properties(object));
1986 	if (!dateobj->time) {
1987 		return props;
1988 	}
1989 
1990 	date_object_to_hash(dateobj, props);
1991 
1992 	return props;
1993 } /* }}} */
1994 
date_object_new_timezone(zend_class_entry * class_type)1995 static zend_object *date_object_new_timezone(zend_class_entry *class_type) /* {{{ */
1996 {
1997 	php_timezone_obj *intern = zend_object_alloc(sizeof(php_timezone_obj), class_type);
1998 
1999 	zend_object_std_init(&intern->std, class_type);
2000 	object_properties_init(&intern->std, class_type);
2001 
2002 	return &intern->std;
2003 } /* }}} */
2004 
date_object_clone_timezone(zend_object * this_ptr)2005 static zend_object *date_object_clone_timezone(zend_object *this_ptr) /* {{{ */
2006 {
2007 	php_timezone_obj *old_obj = php_timezone_obj_from_obj(this_ptr);
2008 	php_timezone_obj *new_obj = php_timezone_obj_from_obj(date_object_new_timezone(old_obj->std.ce));
2009 
2010 	zend_objects_clone_members(&new_obj->std, &old_obj->std);
2011 	if (!old_obj->initialized) {
2012 		return &new_obj->std;
2013 	}
2014 
2015 	new_obj->type = old_obj->type;
2016 	new_obj->initialized = 1;
2017 	switch (new_obj->type) {
2018 		case TIMELIB_ZONETYPE_ID:
2019 			new_obj->tzi.tz = old_obj->tzi.tz;
2020 			break;
2021 		case TIMELIB_ZONETYPE_OFFSET:
2022 			new_obj->tzi.utc_offset = old_obj->tzi.utc_offset;
2023 			break;
2024 		case TIMELIB_ZONETYPE_ABBR:
2025 			new_obj->tzi.z.utc_offset = old_obj->tzi.z.utc_offset;
2026 			new_obj->tzi.z.dst        = old_obj->tzi.z.dst;
2027 			new_obj->tzi.z.abbr       = timelib_strdup(old_obj->tzi.z.abbr);
2028 			break;
2029 	}
2030 
2031 	return &new_obj->std;
2032 } /* }}} */
2033 
date_object_compare_timezone(zval * tz1,zval * tz2)2034 static int date_object_compare_timezone(zval *tz1, zval *tz2) /* {{{ */
2035 {
2036 	php_timezone_obj *o1, *o2;
2037 
2038 	ZEND_COMPARE_OBJECTS_FALLBACK(tz1, tz2);
2039 
2040 	o1 = Z_PHPTIMEZONE_P(tz1);
2041 	o2 = Z_PHPTIMEZONE_P(tz2);
2042 
2043 	if (!o1->initialized || !o2->initialized) {
2044 		zend_throw_error(date_ce_date_object_error, "Trying to compare uninitialized DateTimeZone objects");
2045 		return ZEND_UNCOMPARABLE;
2046 	}
2047 
2048 	if (o1->type != o2->type) {
2049 		zend_throw_error(date_ce_date_exception, "Cannot compare two different kinds of DateTimeZone objects");
2050 		return ZEND_UNCOMPARABLE;
2051 	}
2052 
2053 	switch (o1->type) {
2054 		case TIMELIB_ZONETYPE_OFFSET:
2055 			return o1->tzi.utc_offset == o2->tzi.utc_offset ? 0 : 1;
2056 		case TIMELIB_ZONETYPE_ABBR:
2057 			return strcmp(o1->tzi.z.abbr, o2->tzi.z.abbr) ? 1 : 0;
2058 		case TIMELIB_ZONETYPE_ID:
2059 			return strcmp(o1->tzi.tz->name, o2->tzi.tz->name) ? 1 : 0;
2060 		EMPTY_SWITCH_DEFAULT_CASE();
2061 	}
2062 } /* }}} */
2063 
php_timezone_to_string(php_timezone_obj * tzobj,zval * zv)2064 static void php_timezone_to_string(php_timezone_obj *tzobj, zval *zv)
2065 {
2066 	switch (tzobj->type) {
2067 		case TIMELIB_ZONETYPE_ID:
2068 			ZVAL_STRING(zv, tzobj->tzi.tz->name);
2069 			break;
2070 		case TIMELIB_ZONETYPE_OFFSET: {
2071 			timelib_sll utc_offset = tzobj->tzi.utc_offset;
2072 			int seconds = utc_offset % 60;
2073 			size_t size;
2074 			const char *format;
2075 			if (seconds == 0) {
2076 				size = sizeof("+05:00");
2077 				format = "%c%02d:%02d";
2078 			} else {
2079 				size = sizeof("+05:00:01");
2080 				format = "%c%02d:%02d:%02d";
2081 			}
2082 			zend_string *tmpstr = zend_string_alloc(size - 1, 0);
2083 
2084 			/* Note: if seconds == 0, the seconds argument will be excessive and therefore ignored. */
2085 			ZSTR_LEN(tmpstr) = snprintf(ZSTR_VAL(tmpstr), size, format,
2086 				utc_offset < 0 ? '-' : '+',
2087 				abs((int)(utc_offset / 3600)),
2088 				abs((int)(utc_offset % 3600) / 60),
2089 				abs(seconds));
2090 
2091 			ZVAL_NEW_STR(zv, tmpstr);
2092 			}
2093 			break;
2094 		case TIMELIB_ZONETYPE_ABBR:
2095 			ZVAL_STRING(zv, tzobj->tzi.z.abbr);
2096 			break;
2097 	}
2098 }
2099 
date_timezone_object_to_hash(php_timezone_obj * tzobj,HashTable * props)2100 static void date_timezone_object_to_hash(php_timezone_obj *tzobj, HashTable *props)
2101 {
2102 	zval zv;
2103 
2104 	ZVAL_LONG(&zv, tzobj->type);
2105 	zend_hash_str_update(props, "timezone_type", strlen("timezone_type"), &zv);
2106 
2107 	php_timezone_to_string(tzobj, &zv);
2108 	zend_hash_str_update(props, "timezone", strlen("timezone"), &zv);
2109 }
2110 
date_object_get_properties_for_timezone(zend_object * object,zend_prop_purpose purpose)2111 static HashTable *date_object_get_properties_for_timezone(zend_object *object, zend_prop_purpose purpose) /* {{{ */
2112 {
2113 	HashTable *props;
2114 	php_timezone_obj *tzobj;
2115 
2116 	switch (purpose) {
2117 		case ZEND_PROP_PURPOSE_DEBUG:
2118 		case ZEND_PROP_PURPOSE_SERIALIZE:
2119 		case ZEND_PROP_PURPOSE_VAR_EXPORT:
2120 		case ZEND_PROP_PURPOSE_JSON:
2121 		case ZEND_PROP_PURPOSE_ARRAY_CAST:
2122 			break;
2123 		default:
2124 			return zend_std_get_properties_for(object, purpose);
2125 	}
2126 
2127 	tzobj = php_timezone_obj_from_obj(object);
2128 	props = zend_array_dup(zend_std_get_properties(object));
2129 	if (!tzobj->initialized) {
2130 		return props;
2131 	}
2132 
2133 	date_timezone_object_to_hash(tzobj, props);
2134 
2135 	return props;
2136 } /* }}} */
2137 
date_object_get_debug_info_timezone(zend_object * object,int * is_temp)2138 static HashTable *date_object_get_debug_info_timezone(zend_object *object, int *is_temp) /* {{{ */
2139 {
2140 	HashTable *ht, *props;
2141 	zval zv;
2142 	php_timezone_obj *tzobj;
2143 
2144 	tzobj = php_timezone_obj_from_obj(object);
2145 	props = zend_std_get_properties(object);
2146 
2147 	*is_temp = 1;
2148 	ht = zend_array_dup(props);
2149 
2150 	ZVAL_LONG(&zv, tzobj->type);
2151 	zend_hash_str_update(ht, "timezone_type", sizeof("timezone_type")-1, &zv);
2152 
2153 	php_timezone_to_string(tzobj, &zv);
2154 	zend_hash_str_update(ht, "timezone", sizeof("timezone")-1, &zv);
2155 
2156 	return ht;
2157 } /* }}} */
2158 
date_object_new_interval(zend_class_entry * class_type)2159 static zend_object *date_object_new_interval(zend_class_entry *class_type) /* {{{ */
2160 {
2161 	php_interval_obj *intern = zend_object_alloc(sizeof(php_interval_obj), class_type);
2162 
2163 	zend_object_std_init(&intern->std, class_type);
2164 	object_properties_init(&intern->std, class_type);
2165 
2166 	return &intern->std;
2167 } /* }}} */
2168 
date_object_clone_interval(zend_object * this_ptr)2169 static zend_object *date_object_clone_interval(zend_object *this_ptr) /* {{{ */
2170 {
2171 	php_interval_obj *old_obj = php_interval_obj_from_obj(this_ptr);
2172 	php_interval_obj *new_obj = php_interval_obj_from_obj(date_object_new_interval(old_obj->std.ce));
2173 
2174 	zend_objects_clone_members(&new_obj->std, &old_obj->std);
2175 	new_obj->civil_or_wall = old_obj->civil_or_wall;
2176 	new_obj->from_string = old_obj->from_string;
2177 	if (old_obj->date_string) {
2178 		new_obj->date_string = zend_string_copy(old_obj->date_string);
2179 	}
2180 	new_obj->initialized = old_obj->initialized;
2181 	if (old_obj->diff) {
2182 		new_obj->diff = timelib_rel_time_clone(old_obj->diff);
2183 	}
2184 
2185 	return &new_obj->std;
2186 } /* }}} */
2187 
date_object_get_gc_interval(zend_object * object,zval ** table,int * n)2188 static HashTable *date_object_get_gc_interval(zend_object *object, zval **table, int *n) /* {{{ */
2189 {
2190 
2191 	*table = NULL;
2192 	*n = 0;
2193 	return zend_std_get_properties(object);
2194 } /* }}} */
2195 
date_interval_object_to_hash(php_interval_obj * intervalobj,HashTable * props)2196 static void date_interval_object_to_hash(php_interval_obj *intervalobj, HashTable *props)
2197 {
2198 	zval zv;
2199 
2200 	/* Records whether this is a special relative interval that needs to be recreated from a string */
2201 	if (intervalobj->from_string) {
2202 		ZVAL_BOOL(&zv, (bool)intervalobj->from_string);
2203 		zend_hash_str_update(props, "from_string", strlen("from_string"), &zv);
2204 		ZVAL_STR_COPY(&zv, intervalobj->date_string);
2205 		zend_hash_str_update(props, "date_string", strlen("date_string"), &zv);
2206 		return;
2207 	}
2208 
2209 #define PHP_DATE_INTERVAL_ADD_PROPERTY(n,f) \
2210 	ZVAL_LONG(&zv, (zend_long)intervalobj->diff->f); \
2211 	zend_hash_str_update(props, n, sizeof(n)-1, &zv);
2212 
2213 	PHP_DATE_INTERVAL_ADD_PROPERTY("y", y);
2214 	PHP_DATE_INTERVAL_ADD_PROPERTY("m", m);
2215 	PHP_DATE_INTERVAL_ADD_PROPERTY("d", d);
2216 	PHP_DATE_INTERVAL_ADD_PROPERTY("h", h);
2217 	PHP_DATE_INTERVAL_ADD_PROPERTY("i", i);
2218 	PHP_DATE_INTERVAL_ADD_PROPERTY("s", s);
2219 	ZVAL_DOUBLE(&zv, (double)intervalobj->diff->us / 1000000.0);
2220 	zend_hash_str_update(props, "f", sizeof("f") - 1, &zv);
2221 	PHP_DATE_INTERVAL_ADD_PROPERTY("invert", invert);
2222 	if (intervalobj->diff->days != TIMELIB_UNSET) {
2223 		PHP_DATE_INTERVAL_ADD_PROPERTY("days", days);
2224 	} else {
2225 		ZVAL_FALSE(&zv);
2226 		zend_hash_str_update(props, "days", sizeof("days")-1, &zv);
2227 	}
2228 	ZVAL_BOOL(&zv, (bool)intervalobj->from_string);
2229 	zend_hash_str_update(props, "from_string", strlen("from_string"), &zv);
2230 
2231 #undef PHP_DATE_INTERVAL_ADD_PROPERTY
2232 }
2233 
date_object_get_properties_interval(zend_object * object)2234 static HashTable *date_object_get_properties_interval(zend_object *object) /* {{{ */
2235 {
2236 	HashTable *props;
2237 	php_interval_obj *intervalobj;
2238 
2239 	intervalobj = php_interval_obj_from_obj(object);
2240 	props = zend_std_get_properties(object);
2241 	if (!intervalobj->initialized) {
2242 		return props;
2243 	}
2244 
2245 	date_interval_object_to_hash(intervalobj, props);
2246 
2247 	return props;
2248 } /* }}} */
2249 
date_object_new_period(zend_class_entry * class_type)2250 static zend_object *date_object_new_period(zend_class_entry *class_type) /* {{{ */
2251 {
2252 	php_period_obj *intern = zend_object_alloc(sizeof(php_period_obj), class_type);
2253 
2254 	zend_object_std_init(&intern->std, class_type);
2255 	object_properties_init(&intern->std, class_type);
2256 
2257 	return &intern->std;
2258 } /* }}} */
2259 
date_object_clone_period(zend_object * this_ptr)2260 static zend_object *date_object_clone_period(zend_object *this_ptr) /* {{{ */
2261 {
2262 	php_period_obj *old_obj = php_period_obj_from_obj(this_ptr);
2263 	php_period_obj *new_obj = php_period_obj_from_obj(date_object_new_period(old_obj->std.ce));
2264 
2265 	zend_objects_clone_members(&new_obj->std, &old_obj->std);
2266 	new_obj->initialized = old_obj->initialized;
2267 	new_obj->recurrences = old_obj->recurrences;
2268 	new_obj->include_start_date = old_obj->include_start_date;
2269 	new_obj->include_end_date = old_obj->include_end_date;
2270 	new_obj->start_ce = old_obj->start_ce;
2271 
2272 	if (old_obj->start) {
2273 		new_obj->start = timelib_time_clone(old_obj->start);
2274 	}
2275 	if (old_obj->current) {
2276 		new_obj->current = timelib_time_clone(old_obj->current);
2277 	}
2278 	if (old_obj->end) {
2279 		new_obj->end = timelib_time_clone(old_obj->end);
2280 	}
2281 	if (old_obj->interval) {
2282 		new_obj->interval = timelib_rel_time_clone(old_obj->interval);
2283 	}
2284 	return &new_obj->std;
2285 } /* }}} */
2286 
date_object_free_storage_date(zend_object * object)2287 static void date_object_free_storage_date(zend_object *object) /* {{{ */
2288 {
2289 	php_date_obj *intern = php_date_obj_from_obj(object);
2290 
2291 	if (intern->time) {
2292 		timelib_time_dtor(intern->time);
2293 	}
2294 
2295 	zend_object_std_dtor(&intern->std);
2296 } /* }}} */
2297 
date_object_free_storage_timezone(zend_object * object)2298 static void date_object_free_storage_timezone(zend_object *object) /* {{{ */
2299 {
2300 	php_timezone_obj *intern = php_timezone_obj_from_obj(object);
2301 
2302 	if (intern->type == TIMELIB_ZONETYPE_ABBR) {
2303 		timelib_free(intern->tzi.z.abbr);
2304 	}
2305 	zend_object_std_dtor(&intern->std);
2306 } /* }}} */
2307 
date_object_free_storage_interval(zend_object * object)2308 static void date_object_free_storage_interval(zend_object *object) /* {{{ */
2309 {
2310 	php_interval_obj *intern = php_interval_obj_from_obj(object);
2311 
2312 	if (intern->date_string) {
2313 		zend_string_release(intern->date_string);
2314 		intern->date_string = NULL;
2315 	}
2316 	timelib_rel_time_dtor(intern->diff);
2317 	zend_object_std_dtor(&intern->std);
2318 } /* }}} */
2319 
date_object_free_storage_period(zend_object * object)2320 static void date_object_free_storage_period(zend_object *object) /* {{{ */
2321 {
2322 	php_period_obj *intern = php_period_obj_from_obj(object);
2323 
2324 	if (intern->start) {
2325 		timelib_time_dtor(intern->start);
2326 	}
2327 
2328 	if (intern->current) {
2329 		timelib_time_dtor(intern->current);
2330 	}
2331 
2332 	if (intern->end) {
2333 		timelib_time_dtor(intern->end);
2334 	}
2335 
2336 	timelib_rel_time_dtor(intern->interval);
2337 	zend_object_std_dtor(&intern->std);
2338 } /* }}} */
2339 
add_common_properties(HashTable * myht,zend_object * zobj)2340 static void add_common_properties(HashTable *myht, zend_object *zobj)
2341 {
2342 	HashTable *common;
2343 	zend_string *name;
2344 	zval *prop;
2345 
2346 	common = zend_std_get_properties(zobj);
2347 
2348 	ZEND_HASH_FOREACH_STR_KEY_VAL_IND(common, name, prop) {
2349 		if (zend_hash_add(myht, name, prop) != NULL) {
2350 			Z_TRY_ADDREF_P(prop);
2351 		}
2352 	} ZEND_HASH_FOREACH_END();
2353 }
2354 
2355 /* Advanced Interface */
php_date_instantiate(zend_class_entry * pce,zval * object)2356 PHPAPI zval *php_date_instantiate(zend_class_entry *pce, zval *object) /* {{{ */
2357 {
2358 	object_init_ex(object, pce);
2359 	return object;
2360 } /* }}} */
2361 
2362 /* Helper function used to store the latest found warnings and errors while
2363  * parsing, from either strtotime or parse_from_format. */
update_errors_warnings(timelib_error_container ** last_errors)2364 static void update_errors_warnings(timelib_error_container **last_errors) /* {{{ */
2365 {
2366 	if (DATEG(last_errors)) {
2367 		timelib_error_container_dtor(DATEG(last_errors));
2368 		DATEG(last_errors) = NULL;
2369 	}
2370 
2371 	if (last_errors == NULL || (*last_errors) == NULL) {
2372 		return;
2373 	}
2374 
2375 	if ((*last_errors)->warning_count || (*last_errors)->error_count) {
2376 		DATEG(last_errors) = *last_errors;
2377 		return;
2378 	}
2379 
2380 	timelib_error_container_dtor(*last_errors);
2381 	*last_errors = NULL;
2382 } /* }}} */
2383 
php_date_set_time_fraction(timelib_time * time,int microsecond)2384 static void php_date_set_time_fraction(timelib_time *time, int microsecond)
2385 {
2386 	time->us = microsecond;
2387 }
2388 
php_date_get_current_time_with_fraction(time_t * sec,suseconds_t * usec)2389 static void php_date_get_current_time_with_fraction(time_t *sec, suseconds_t *usec)
2390 {
2391 #ifdef HAVE_GETTIMEOFDAY
2392 	struct timeval tp = {0}; /* For setting microsecond */
2393 
2394 	gettimeofday(&tp, NULL);
2395 	*sec = tp.tv_sec;
2396 	*usec = tp.tv_usec;
2397 #else
2398 	*sec = time(NULL);
2399 	*usec = 0;
2400 #endif
2401 }
2402 
php_date_initialize(php_date_obj * dateobj,const char * time_str,size_t time_str_len,const char * format,zval * timezone_object,int flags)2403 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) /* {{{ */
2404 {
2405 	timelib_time   *now;
2406 	timelib_tzinfo *tzi = NULL;
2407 	timelib_error_container *err = NULL;
2408 	int type = TIMELIB_ZONETYPE_ID, new_dst = 0;
2409 	char *new_abbr = NULL;
2410 	timelib_sll new_offset = 0;
2411 	time_t sec;
2412 	suseconds_t usec;
2413 	int options = 0;
2414 
2415 	if (dateobj->time) {
2416 		timelib_time_dtor(dateobj->time);
2417 	}
2418 	if (format) {
2419 		if (time_str_len == 0) {
2420 			time_str = "";
2421 		}
2422 		dateobj->time = timelib_parse_from_format(format, time_str, time_str_len, &err, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
2423 	} else {
2424 		if (time_str_len == 0) {
2425 			time_str = "now";
2426 			time_str_len = sizeof("now") - 1;
2427 		}
2428 		dateobj->time = timelib_strtotime(time_str, time_str_len, &err, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
2429 	}
2430 
2431 	/* update last errors and warnings */
2432 	update_errors_warnings(&err);
2433 
2434 	/* If called from a constructor throw an exception */
2435 	if ((flags & PHP_DATE_INIT_CTOR) && err && err->error_count) {
2436 		/* spit out the first library error message, at least */
2437 		zend_throw_exception_ex(date_ce_date_malformed_string_exception, 0, "Failed to parse time string (%s) at position %d (%c): %s", time_str,
2438 			err->error_messages[0].position, err->error_messages[0].character ? err->error_messages[0].character : ' ', err->error_messages[0].message);
2439 	}
2440 	if (err && err->error_count) {
2441 		timelib_time_dtor(dateobj->time);
2442 		dateobj->time = 0;
2443 		return 0;
2444 	}
2445 
2446 	if (timezone_object) {
2447 		php_timezone_obj *tzobj;
2448 
2449 		tzobj = Z_PHPTIMEZONE_P(timezone_object);
2450 		switch (tzobj->type) {
2451 			case TIMELIB_ZONETYPE_ID:
2452 				tzi = tzobj->tzi.tz;
2453 				break;
2454 			case TIMELIB_ZONETYPE_OFFSET:
2455 				new_offset = tzobj->tzi.utc_offset;
2456 				break;
2457 			case TIMELIB_ZONETYPE_ABBR:
2458 				new_offset = tzobj->tzi.z.utc_offset;
2459 				new_dst    = tzobj->tzi.z.dst;
2460 				new_abbr   = timelib_strdup(tzobj->tzi.z.abbr);
2461 				break;
2462 			default:
2463 				zend_throw_error(NULL, "The DateTimeZone object has not been correctly initialized by its constructor");
2464 				return 0;
2465 		}
2466 		type = tzobj->type;
2467 	} else if (dateobj->time->tz_info) {
2468 		tzi = dateobj->time->tz_info;
2469 	} else {
2470 		tzi = get_timezone_info();
2471 		if (!tzi) {
2472 			return 0;
2473 		}
2474 	}
2475 
2476 	now = timelib_time_ctor();
2477 	now->zone_type = type;
2478 	switch (type) {
2479 		case TIMELIB_ZONETYPE_ID:
2480 			now->tz_info = tzi;
2481 			break;
2482 		case TIMELIB_ZONETYPE_OFFSET:
2483 			now->z = new_offset;
2484 			break;
2485 		case TIMELIB_ZONETYPE_ABBR:
2486 			now->z = new_offset;
2487 			now->dst = new_dst;
2488 			now->tz_abbr = new_abbr;
2489 			break;
2490 	}
2491 	php_date_get_current_time_with_fraction(&sec, &usec);
2492 	timelib_unixtime2local(now, (timelib_sll) sec);
2493 	php_date_set_time_fraction(now, usec);
2494 
2495 	if (!format
2496 	 && time_str_len == sizeof("now") - 1
2497 	 && memcmp(time_str, "now", sizeof("now") - 1) == 0) {
2498 		timelib_time_dtor(dateobj->time);
2499 		dateobj->time = now;
2500 		return 1;
2501 	}
2502 
2503 	options = TIMELIB_NO_CLONE;
2504 	if (flags & PHP_DATE_INIT_FORMAT) {
2505 		options |= TIMELIB_OVERRIDE_TIME;
2506 	}
2507 	timelib_fill_holes(dateobj->time, now, options);
2508 
2509 	timelib_update_ts(dateobj->time, tzi);
2510 	timelib_update_from_sse(dateobj->time);
2511 
2512 	dateobj->time->have_relative = 0;
2513 
2514 	timelib_time_dtor(now);
2515 
2516 	return 1;
2517 } /* }}} */
2518 
php_date_initialize_from_ts_long(php_date_obj * dateobj,zend_long sec,int usec)2519 PHPAPI void php_date_initialize_from_ts_long(php_date_obj *dateobj, zend_long sec, int usec) /* {{{ */
2520 {
2521 	dateobj->time = timelib_time_ctor();
2522 	dateobj->time->zone_type = TIMELIB_ZONETYPE_OFFSET;
2523 
2524 	timelib_unixtime2gmt(dateobj->time, (timelib_sll)sec);
2525 	timelib_update_ts(dateobj->time, NULL);
2526 	php_date_set_time_fraction(dateobj->time, usec);
2527 } /* }}} */
2528 
php_date_initialize_from_ts_double(php_date_obj * dateobj,double ts)2529 PHPAPI bool php_date_initialize_from_ts_double(php_date_obj *dateobj, double ts) /* {{{ */
2530 {
2531 	double sec_dval = trunc(ts);
2532 	zend_long sec;
2533 	int usec;
2534 
2535 	if (UNEXPECTED(isnan(sec_dval) || !PHP_DATE_DOUBLE_FITS_LONG(sec_dval))) {
2536 		zend_argument_error(
2537 			date_ce_date_range_error,
2538 			1,
2539 			"must be a finite number between " TIMELIB_LONG_FMT " and " TIMELIB_LONG_FMT ".999999, %g given",
2540 			TIMELIB_LONG_MIN,
2541 			TIMELIB_LONG_MAX,
2542 			ts
2543 		);
2544 		return false;
2545 	}
2546 
2547 	sec = (zend_long)sec_dval;
2548 	usec = (int) round(fmod(ts, 1) * 1000000);
2549 
2550 	if (UNEXPECTED(abs(usec) == 1000000)) {
2551 		sec += usec > 0 ? 1 : -1;
2552 		usec = 0;
2553 	}
2554 
2555 	if (UNEXPECTED(usec < 0)) {
2556 		if (UNEXPECTED(sec == TIMELIB_LONG_MIN)) {
2557 			zend_argument_error(
2558 				date_ce_date_range_error,
2559 				1,
2560 				"must be a finite number between " TIMELIB_LONG_FMT " and " TIMELIB_LONG_FMT ".999999, %g given",
2561 				TIMELIB_LONG_MIN,
2562 				TIMELIB_LONG_MAX,
2563 				ts
2564 			);
2565 			return false;
2566 		}
2567 
2568 		sec = sec - 1;
2569 		usec = 1000000 + usec;
2570 	}
2571 
2572 	php_date_initialize_from_ts_long(dateobj, sec, usec);
2573 
2574 	return true;
2575 } /* }}} */
2576 
2577 /* {{{ Returns new DateTime object */
PHP_FUNCTION(date_create)2578 PHP_FUNCTION(date_create)
2579 {
2580 	zval           *timezone_object = NULL;
2581 	char           *time_str = NULL;
2582 	size_t          time_str_len = 0;
2583 
2584 	ZEND_PARSE_PARAMETERS_START(0, 2)
2585 		Z_PARAM_OPTIONAL
2586 		Z_PARAM_STRING(time_str, time_str_len)
2587 		Z_PARAM_OBJECT_OF_CLASS_OR_NULL(timezone_object, date_ce_timezone)
2588 	ZEND_PARSE_PARAMETERS_END();
2589 
2590 	php_date_instantiate(date_ce_date, return_value);
2591 	if (!php_date_initialize(Z_PHPDATE_P(return_value), time_str, time_str_len, NULL, timezone_object, 0)) {
2592 		zval_ptr_dtor(return_value);
2593 		RETURN_FALSE;
2594 	}
2595 }
2596 /* }}} */
2597 
2598 /* {{{ Returns new DateTimeImmutable object */
PHP_FUNCTION(date_create_immutable)2599 PHP_FUNCTION(date_create_immutable)
2600 {
2601 	zval           *timezone_object = NULL;
2602 	char           *time_str = NULL;
2603 	size_t          time_str_len = 0;
2604 
2605 	ZEND_PARSE_PARAMETERS_START(0, 2)
2606 		Z_PARAM_OPTIONAL
2607 		Z_PARAM_STRING(time_str, time_str_len)
2608 		Z_PARAM_OBJECT_OF_CLASS_OR_NULL(timezone_object, date_ce_timezone)
2609 	ZEND_PARSE_PARAMETERS_END();
2610 
2611 	php_date_instantiate(date_ce_immutable, return_value);
2612 	if (!php_date_initialize(Z_PHPDATE_P(return_value), time_str, time_str_len, NULL, timezone_object, 0)) {
2613 		zval_ptr_dtor(return_value);
2614 		RETURN_FALSE;
2615 	}
2616 }
2617 /* }}} */
2618 
2619 /* {{{ Returns new DateTime object formatted according to the specified format */
PHP_FUNCTION(date_create_from_format)2620 PHP_FUNCTION(date_create_from_format)
2621 {
2622 	zval           *timezone_object = NULL;
2623 	char           *time_str = NULL, *format_str = NULL;
2624 	size_t          time_str_len = 0, format_str_len = 0;
2625 
2626 	ZEND_PARSE_PARAMETERS_START(2, 3)
2627 		Z_PARAM_STRING(format_str, format_str_len)
2628 		Z_PARAM_PATH(time_str, time_str_len)
2629 		Z_PARAM_OPTIONAL
2630 		Z_PARAM_OBJECT_OF_CLASS_OR_NULL(timezone_object, date_ce_timezone)
2631 	ZEND_PARSE_PARAMETERS_END();
2632 
2633 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_date, return_value);
2634 	if (!php_date_initialize(Z_PHPDATE_P(return_value), time_str, time_str_len, format_str, timezone_object, PHP_DATE_INIT_FORMAT)) {
2635 		zval_ptr_dtor(return_value);
2636 		RETURN_FALSE;
2637 	}
2638 }
2639 /* }}} */
2640 
2641 /* {{{ Returns new DateTimeImmutable object formatted according to the specified format */
PHP_FUNCTION(date_create_immutable_from_format)2642 PHP_FUNCTION(date_create_immutable_from_format)
2643 {
2644 	zval           *timezone_object = NULL;
2645 	char           *time_str = NULL, *format_str = NULL;
2646 	size_t          time_str_len = 0, format_str_len = 0;
2647 
2648 	ZEND_PARSE_PARAMETERS_START(2, 3)
2649 		Z_PARAM_STRING(format_str, format_str_len)
2650 		Z_PARAM_PATH(time_str, time_str_len)
2651 		Z_PARAM_OPTIONAL
2652 		Z_PARAM_OBJECT_OF_CLASS_OR_NULL(timezone_object, date_ce_timezone)
2653 	ZEND_PARSE_PARAMETERS_END();
2654 
2655 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_immutable, return_value);
2656 	if (!php_date_initialize(Z_PHPDATE_P(return_value), time_str, time_str_len, format_str, timezone_object, PHP_DATE_INIT_FORMAT)) {
2657 		zval_ptr_dtor(return_value);
2658 		RETURN_FALSE;
2659 	}
2660 }
2661 /* }}} */
2662 
2663 /* {{{ Creates new DateTime object */
PHP_METHOD(DateTime,__construct)2664 PHP_METHOD(DateTime, __construct)
2665 {
2666 	zval *timezone_object = NULL;
2667 	char *time_str = NULL;
2668 	size_t time_str_len = 0;
2669 
2670 	ZEND_PARSE_PARAMETERS_START(0, 2)
2671 		Z_PARAM_OPTIONAL
2672 		Z_PARAM_STRING(time_str, time_str_len)
2673 		Z_PARAM_OBJECT_OF_CLASS_OR_NULL(timezone_object, date_ce_timezone)
2674 	ZEND_PARSE_PARAMETERS_END();
2675 
2676 	php_date_initialize(Z_PHPDATE_P(ZEND_THIS), time_str, time_str_len, NULL, timezone_object, PHP_DATE_INIT_CTOR);
2677 }
2678 /* }}} */
2679 
2680 /* {{{ Creates new DateTimeImmutable object */
PHP_METHOD(DateTimeImmutable,__construct)2681 PHP_METHOD(DateTimeImmutable, __construct)
2682 {
2683 	zval *timezone_object = NULL;
2684 	char *time_str = NULL;
2685 	size_t time_str_len = 0;
2686 
2687 	ZEND_PARSE_PARAMETERS_START(0, 2)
2688 		Z_PARAM_OPTIONAL
2689 		Z_PARAM_STRING(time_str, time_str_len)
2690 		Z_PARAM_OBJECT_OF_CLASS_OR_NULL(timezone_object, date_ce_timezone)
2691 	ZEND_PARSE_PARAMETERS_END();
2692 
2693 	php_date_initialize(Z_PHPDATE_P(ZEND_THIS), time_str, time_str_len, NULL, timezone_object, PHP_DATE_INIT_CTOR);
2694 }
2695 /* }}} */
2696 
2697 /* {{{ Creates new DateTime object from an existing immutable DateTimeImmutable object. */
PHP_METHOD(DateTime,createFromImmutable)2698 PHP_METHOD(DateTime, createFromImmutable)
2699 {
2700 	zval *datetimeimmutable_object = NULL;
2701 	php_date_obj *new_obj = NULL;
2702 	php_date_obj *old_obj = NULL;
2703 
2704 	ZEND_PARSE_PARAMETERS_START(1, 1)
2705 		Z_PARAM_OBJECT_OF_CLASS(datetimeimmutable_object, date_ce_immutable)
2706 	ZEND_PARSE_PARAMETERS_END();
2707 
2708 	old_obj = Z_PHPDATE_P(datetimeimmutable_object);
2709 	DATE_CHECK_INITIALIZED(old_obj->time, Z_OBJCE_P(datetimeimmutable_object));
2710 
2711 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_date, return_value);
2712 	new_obj = Z_PHPDATE_P(return_value);
2713 
2714 	new_obj->time = timelib_time_clone(old_obj->time);
2715 }
2716 /* }}} */
2717 
2718 /* {{{ Creates new DateTime object from an existing DateTimeInterface object. */
PHP_METHOD(DateTime,createFromInterface)2719 PHP_METHOD(DateTime, createFromInterface)
2720 {
2721 	zval *datetimeinterface_object = NULL;
2722 	php_date_obj *new_obj = NULL;
2723 	php_date_obj *old_obj = NULL;
2724 
2725 	ZEND_PARSE_PARAMETERS_START(1, 1)
2726 		Z_PARAM_OBJECT_OF_CLASS(datetimeinterface_object, date_ce_interface)
2727 	ZEND_PARSE_PARAMETERS_END();
2728 
2729 	old_obj = Z_PHPDATE_P(datetimeinterface_object);
2730 	DATE_CHECK_INITIALIZED(old_obj->time, Z_OBJCE_P(datetimeinterface_object));
2731 
2732 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_date, return_value);
2733 	new_obj = Z_PHPDATE_P(return_value);
2734 
2735 	new_obj->time = timelib_time_clone(old_obj->time);
2736 }
2737 /* }}} */
2738 
2739 /* {{{ Creates new DateTime object from given unix timetamp */
PHP_METHOD(DateTime,createFromTimestamp)2740 PHP_METHOD(DateTime, createFromTimestamp)
2741 {
2742 	zval         *value;
2743 	zval         new_object;
2744 	php_date_obj *new_dateobj;
2745 
2746 	ZEND_PARSE_PARAMETERS_START(1, 1)
2747 		Z_PARAM_NUMBER(value)
2748 	ZEND_PARSE_PARAMETERS_END();
2749 
2750 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_date, &new_object);
2751 	new_dateobj = Z_PHPDATE_P(&new_object);
2752 
2753 	switch (Z_TYPE_P(value)) {
2754 		case IS_LONG:
2755 			php_date_initialize_from_ts_long(new_dateobj, Z_LVAL_P(value), 0);
2756 			break;
2757 
2758 		case IS_DOUBLE:
2759 			if (!php_date_initialize_from_ts_double(new_dateobj, Z_DVAL_P(value))) {
2760 				zval_ptr_dtor(&new_object);
2761 				RETURN_THROWS();
2762 			}
2763 			break;
2764 
2765 		EMPTY_SWITCH_DEFAULT_CASE();
2766 	}
2767 
2768 	RETURN_OBJ(Z_OBJ(new_object));
2769 }
2770 /* }}} */
2771 
2772 /* {{{ Creates new DateTimeImmutable object from an existing mutable DateTime object. */
PHP_METHOD(DateTimeImmutable,createFromMutable)2773 PHP_METHOD(DateTimeImmutable, createFromMutable)
2774 {
2775 	zval *datetime_object = NULL;
2776 	php_date_obj *new_obj = NULL;
2777 	php_date_obj *old_obj = NULL;
2778 
2779 	ZEND_PARSE_PARAMETERS_START(1, 1)
2780 		Z_PARAM_OBJECT_OF_CLASS(datetime_object, date_ce_date)
2781 	ZEND_PARSE_PARAMETERS_END();
2782 
2783 	old_obj = Z_PHPDATE_P(datetime_object);
2784 	DATE_CHECK_INITIALIZED(old_obj->time, Z_OBJCE_P(datetime_object));
2785 
2786 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_immutable, return_value);
2787 	new_obj = Z_PHPDATE_P(return_value);
2788 
2789 	new_obj->time = timelib_time_clone(old_obj->time);
2790 }
2791 /* }}} */
2792 
2793 /* {{{ Creates new DateTimeImmutable object from an existing DateTimeInterface object. */
PHP_METHOD(DateTimeImmutable,createFromInterface)2794 PHP_METHOD(DateTimeImmutable, createFromInterface)
2795 {
2796 	zval *datetimeinterface_object = NULL;
2797 	php_date_obj *new_obj = NULL;
2798 	php_date_obj *old_obj = NULL;
2799 
2800 	ZEND_PARSE_PARAMETERS_START(1, 1)
2801 		Z_PARAM_OBJECT_OF_CLASS(datetimeinterface_object, date_ce_interface)
2802 	ZEND_PARSE_PARAMETERS_END();
2803 
2804 	old_obj = Z_PHPDATE_P(datetimeinterface_object);
2805 	DATE_CHECK_INITIALIZED(old_obj->time, Z_OBJCE_P(datetimeinterface_object));
2806 
2807 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_immutable, return_value);
2808 	new_obj = Z_PHPDATE_P(return_value);
2809 
2810 	new_obj->time = timelib_time_clone(old_obj->time);
2811 }
2812 /* }}} */
2813 
2814 /* {{{ Creates new DateTimeImmutable object from given unix timestamp */
PHP_METHOD(DateTimeImmutable,createFromTimestamp)2815 PHP_METHOD(DateTimeImmutable, createFromTimestamp)
2816 {
2817 	zval         *value;
2818 	zval         new_object;
2819 	php_date_obj *new_dateobj;
2820 
2821 	ZEND_PARSE_PARAMETERS_START(1, 1)
2822 		Z_PARAM_NUMBER(value)
2823 	ZEND_PARSE_PARAMETERS_END();
2824 
2825 	php_date_instantiate(execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_immutable, &new_object);
2826 	new_dateobj = Z_PHPDATE_P(&new_object);
2827 
2828 	switch (Z_TYPE_P(value)) {
2829 		case IS_LONG:
2830 			php_date_initialize_from_ts_long(new_dateobj, Z_LVAL_P(value), 0);
2831 			break;
2832 
2833 		case IS_DOUBLE:
2834 			if (!php_date_initialize_from_ts_double(new_dateobj, Z_DVAL_P(value))) {
2835 				zval_ptr_dtor(&new_object);
2836 				RETURN_THROWS();
2837 			}
2838 			break;
2839 
2840 		EMPTY_SWITCH_DEFAULT_CASE();
2841 	}
2842 
2843 	RETURN_OBJ(Z_OBJ(new_object));
2844 }
2845 /* }}} */
2846 
php_date_initialize_from_hash(php_date_obj ** dateobj,HashTable * myht)2847 static bool php_date_initialize_from_hash(php_date_obj **dateobj, HashTable *myht)
2848 {
2849 	zval             *z_date;
2850 	zval             *z_timezone_type;
2851 	zval             *z_timezone;
2852 	zval              tmp_obj;
2853 	timelib_tzinfo   *tzi;
2854 
2855 	z_date = zend_hash_str_find(myht, "date", sizeof("date")-1);
2856 	if (!z_date || Z_TYPE_P(z_date) != IS_STRING) {
2857 		return false;
2858 	}
2859 
2860 	z_timezone_type = zend_hash_str_find(myht, "timezone_type", sizeof("timezone_type")-1);
2861 	if (!z_timezone_type || Z_TYPE_P(z_timezone_type) != IS_LONG) {
2862 		return false;
2863 	}
2864 
2865 	z_timezone = zend_hash_str_find(myht, "timezone", sizeof("timezone")-1);
2866 	if (!z_timezone || Z_TYPE_P(z_timezone) != IS_STRING) {
2867 		return false;
2868 	}
2869 
2870 	switch (Z_LVAL_P(z_timezone_type)) {
2871 		case TIMELIB_ZONETYPE_OFFSET:
2872 		case TIMELIB_ZONETYPE_ABBR: {
2873 			zend_string *tmp = zend_string_concat3(
2874 				Z_STRVAL_P(z_date), Z_STRLEN_P(z_date), " ", 1,
2875 				Z_STRVAL_P(z_timezone), Z_STRLEN_P(z_timezone));
2876 			bool ret = php_date_initialize(*dateobj, ZSTR_VAL(tmp), ZSTR_LEN(tmp), NULL, NULL, 0);
2877 			zend_string_release(tmp);
2878 			return ret;
2879 		}
2880 
2881 		case TIMELIB_ZONETYPE_ID: {
2882 			bool ret;
2883 			php_timezone_obj *tzobj;
2884 
2885 			tzi = php_date_parse_tzfile(Z_STRVAL_P(z_timezone), DATE_TIMEZONEDB);
2886 
2887 			if (tzi == NULL) {
2888 				return false;
2889 			}
2890 
2891 			tzobj = Z_PHPTIMEZONE_P(php_date_instantiate(date_ce_timezone, &tmp_obj));
2892 			tzobj->type = TIMELIB_ZONETYPE_ID;
2893 			tzobj->tzi.tz = tzi;
2894 			tzobj->initialized = 1;
2895 
2896 			ret = php_date_initialize(*dateobj, Z_STRVAL_P(z_date), Z_STRLEN_P(z_date), NULL, &tmp_obj, 0);
2897 			zval_ptr_dtor(&tmp_obj);
2898 			return ret;
2899 		}
2900 	}
2901 	return false;
2902 } /* }}} */
2903 
2904 /* {{{ */
PHP_METHOD(DateTime,__set_state)2905 PHP_METHOD(DateTime, __set_state)
2906 {
2907 	php_date_obj     *dateobj;
2908 	zval             *array;
2909 	HashTable        *myht;
2910 
2911 	ZEND_PARSE_PARAMETERS_START(1, 1)
2912 		Z_PARAM_ARRAY(array)
2913 	ZEND_PARSE_PARAMETERS_END();
2914 
2915 	myht = Z_ARRVAL_P(array);
2916 
2917 	php_date_instantiate(date_ce_date, return_value);
2918 	dateobj = Z_PHPDATE_P(return_value);
2919 	if (!php_date_initialize_from_hash(&dateobj, myht)) {
2920 		zend_throw_error(NULL, "Invalid serialization data for DateTime object");
2921 		RETURN_THROWS();
2922 	}
2923 }
2924 /* }}} */
2925 
2926 /* {{{ */
PHP_METHOD(DateTimeImmutable,__set_state)2927 PHP_METHOD(DateTimeImmutable, __set_state)
2928 {
2929 	php_date_obj     *dateobj;
2930 	zval             *array;
2931 	HashTable        *myht;
2932 
2933 	ZEND_PARSE_PARAMETERS_START(1, 1)
2934 		Z_PARAM_ARRAY(array)
2935 	ZEND_PARSE_PARAMETERS_END();
2936 
2937 	myht = Z_ARRVAL_P(array);
2938 
2939 	php_date_instantiate(date_ce_immutable, return_value);
2940 	dateobj = Z_PHPDATE_P(return_value);
2941 	if (!php_date_initialize_from_hash(&dateobj, myht)) {
2942 		zend_throw_error(NULL, "Invalid serialization data for DateTimeImmutable object");
2943 		RETURN_THROWS();
2944 	}
2945 }
2946 /* }}} */
2947 
2948 /* {{{ */
PHP_METHOD(DateTime,__serialize)2949 PHP_METHOD(DateTime, __serialize)
2950 {
2951 	zval             *object = ZEND_THIS;
2952 	php_date_obj     *dateobj;
2953 	HashTable        *myht;
2954 
2955 	ZEND_PARSE_PARAMETERS_NONE();
2956 
2957 	dateobj = Z_PHPDATE_P(object);
2958 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
2959 
2960 	array_init(return_value);
2961 	myht = Z_ARRVAL_P(return_value);
2962 	date_object_to_hash(dateobj, myht);
2963 
2964 	add_common_properties(myht, &dateobj->std);
2965 }
2966 /* }}} */
2967 
2968 /* {{{ */
PHP_METHOD(DateTimeImmutable,__serialize)2969 PHP_METHOD(DateTimeImmutable, __serialize)
2970 {
2971 	zval             *object = ZEND_THIS;
2972 	php_date_obj     *dateobj;
2973 	HashTable        *myht;
2974 
2975 	ZEND_PARSE_PARAMETERS_NONE();
2976 
2977 	dateobj = Z_PHPDATE_P(object);
2978 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
2979 
2980 	array_init(return_value);
2981 	myht = Z_ARRVAL_P(return_value);
2982 	date_object_to_hash(dateobj, myht);
2983 
2984 	add_common_properties(myht, &dateobj->std);
2985 }
2986 /* }}} */
2987 
date_time_is_internal_property(zend_string * name)2988 static bool date_time_is_internal_property(zend_string *name)
2989 {
2990 	if (
2991 		zend_string_equals_literal(name, "date") ||
2992 		zend_string_equals_literal(name, "timezone_type") ||
2993 		zend_string_equals_literal(name, "timezone")
2994 	) {
2995 		return 1;
2996 	}
2997 	return 0;
2998 }
2999 
restore_custom_datetime_properties(zval * object,HashTable * myht)3000 static void restore_custom_datetime_properties(zval *object, HashTable *myht)
3001 {
3002 	zend_string      *prop_name;
3003 	zval             *prop_val;
3004 
3005 	ZEND_HASH_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) {
3006 		if (!prop_name || (Z_TYPE_P(prop_val) == IS_REFERENCE) || date_time_is_internal_property(prop_name)) {
3007 			continue;
3008 		}
3009 		update_property(Z_OBJ_P(object), prop_name, prop_val);
3010 	} ZEND_HASH_FOREACH_END();
3011 }
3012 
3013 /* {{{ */
PHP_METHOD(DateTime,__unserialize)3014 PHP_METHOD(DateTime, __unserialize)
3015 {
3016 	zval             *object = ZEND_THIS;
3017 	php_date_obj     *dateobj;
3018 	zval             *array;
3019 	HashTable        *myht;
3020 
3021 	ZEND_PARSE_PARAMETERS_START(1, 1)
3022 		Z_PARAM_ARRAY(array)
3023 	ZEND_PARSE_PARAMETERS_END();
3024 
3025 	dateobj = Z_PHPDATE_P(object);
3026 	myht = Z_ARRVAL_P(array);
3027 
3028 	if (!php_date_initialize_from_hash(&dateobj, myht)) {
3029 		zend_throw_error(NULL, "Invalid serialization data for DateTime object");
3030 		RETURN_THROWS();
3031 	}
3032 
3033 	restore_custom_datetime_properties(object, myht);
3034 }
3035 /* }}} */
3036 
3037 /* {{{ */
PHP_METHOD(DateTimeImmutable,__unserialize)3038 PHP_METHOD(DateTimeImmutable, __unserialize)
3039 {
3040 	zval             *object = ZEND_THIS;
3041 	php_date_obj     *dateobj;
3042 	zval             *array;
3043 	HashTable        *myht;
3044 
3045 	ZEND_PARSE_PARAMETERS_START(1, 1)
3046 		Z_PARAM_ARRAY(array)
3047 	ZEND_PARSE_PARAMETERS_END();
3048 
3049 	dateobj = Z_PHPDATE_P(object);
3050 	myht = Z_ARRVAL_P(array);
3051 
3052 	if (!php_date_initialize_from_hash(&dateobj, myht)) {
3053 		zend_throw_error(NULL, "Invalid serialization data for DateTimeImmutable object");
3054 		RETURN_THROWS();
3055 	}
3056 
3057 	restore_custom_datetime_properties(object, myht);
3058 }
3059 /* }}} */
3060 
3061 /* {{{ */
PHP_METHOD(DateTime,__wakeup)3062 PHP_METHOD(DateTime, __wakeup)
3063 {
3064 	zval             *object = ZEND_THIS;
3065 	php_date_obj     *dateobj;
3066 	HashTable        *myht;
3067 
3068 	ZEND_PARSE_PARAMETERS_NONE();
3069 
3070 	dateobj = Z_PHPDATE_P(object);
3071 
3072 	myht = Z_OBJPROP_P(object);
3073 
3074 	if (!php_date_initialize_from_hash(&dateobj, myht)) {
3075 		zend_throw_error(NULL, "Invalid serialization data for DateTime object");
3076 		RETURN_THROWS();
3077 	}
3078 }
3079 /* }}} */
3080 
3081 /* {{{ */
PHP_METHOD(DateTimeImmutable,__wakeup)3082 PHP_METHOD(DateTimeImmutable, __wakeup)
3083 {
3084 	zval             *object = ZEND_THIS;
3085 	php_date_obj     *dateobj;
3086 	HashTable        *myht;
3087 
3088 	ZEND_PARSE_PARAMETERS_NONE();
3089 
3090 	dateobj = Z_PHPDATE_P(object);
3091 
3092 	myht = Z_OBJPROP_P(object);
3093 
3094 	if (!php_date_initialize_from_hash(&dateobj, myht)) {
3095 		zend_throw_error(NULL, "Invalid serialization data for DateTimeImmutable object");
3096 		RETURN_THROWS();
3097 	}
3098 }
3099 /* }}} */
3100 
3101 /* Helper function used to add an associative array of warnings and errors to a zval */
zval_from_error_container(zval * z,timelib_error_container * error)3102 static void zval_from_error_container(zval *z, timelib_error_container *error) /* {{{ */
3103 {
3104 	int   i;
3105 	zval element;
3106 
3107 	add_assoc_long(z, "warning_count", error->warning_count);
3108 	array_init(&element);
3109 	for (i = 0; i < error->warning_count; i++) {
3110 		add_index_string(&element, error->warning_messages[i].position, error->warning_messages[i].message);
3111 	}
3112 	add_assoc_zval(z, "warnings", &element);
3113 
3114 	add_assoc_long(z, "error_count", error->error_count);
3115 	array_init(&element);
3116 	for (i = 0; i < error->error_count; i++) {
3117 		add_index_string(&element, error->error_messages[i].position, error->error_messages[i].message);
3118 	}
3119 	add_assoc_zval(z, "errors", &element);
3120 } /* }}} */
3121 
3122 /* {{{ Returns the warnings and errors found while parsing a date/time string. */
PHP_FUNCTION(date_get_last_errors)3123 PHP_FUNCTION(date_get_last_errors)
3124 {
3125 	ZEND_PARSE_PARAMETERS_NONE();
3126 
3127 	if (DATEG(last_errors)) {
3128 		array_init(return_value);
3129 		zval_from_error_container(return_value, DATEG(last_errors));
3130 	} else {
3131 		RETURN_FALSE;
3132 	}
3133 }
3134 /* }}} */
3135 
php_date_do_return_parsed_time(INTERNAL_FUNCTION_PARAMETERS,timelib_time * parsed_time,timelib_error_container * error)3136 static void php_date_do_return_parsed_time(INTERNAL_FUNCTION_PARAMETERS, timelib_time *parsed_time, timelib_error_container *error) /* {{{ */
3137 {
3138 	zval element;
3139 
3140 	array_init(return_value);
3141 #define PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(name, elem) \
3142 	if (parsed_time->elem == TIMELIB_UNSET) {               \
3143 		add_assoc_bool(return_value, #name, 0); \
3144 	} else {                                       \
3145 		add_assoc_long(return_value, #name, parsed_time->elem); \
3146 	}
3147 	PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(year,      y);
3148 	PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(month,     m);
3149 	PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(day,       d);
3150 	PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(hour,      h);
3151 	PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(minute,    i);
3152 	PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(second,    s);
3153 
3154 	if (parsed_time->us == TIMELIB_UNSET) {
3155 		add_assoc_bool(return_value, "fraction", 0);
3156 	} else {
3157 		add_assoc_double(return_value, "fraction", (double)parsed_time->us / 1000000.0);
3158 	}
3159 
3160 	zval_from_error_container(return_value, error);
3161 
3162 	timelib_error_container_dtor(error);
3163 
3164 	add_assoc_bool(return_value, "is_localtime", parsed_time->is_localtime);
3165 
3166 	if (parsed_time->is_localtime) {
3167 		PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(zone_type, zone_type);
3168 		switch (parsed_time->zone_type) {
3169 			case TIMELIB_ZONETYPE_OFFSET:
3170 				PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(zone, z);
3171 				add_assoc_bool(return_value, "is_dst", parsed_time->dst);
3172 				break;
3173 			case TIMELIB_ZONETYPE_ID:
3174 				if (parsed_time->tz_abbr) {
3175 					add_assoc_string(return_value, "tz_abbr", parsed_time->tz_abbr);
3176 				}
3177 				if (parsed_time->tz_info) {
3178 					add_assoc_string(return_value, "tz_id", parsed_time->tz_info->name);
3179 				}
3180 				break;
3181 			case TIMELIB_ZONETYPE_ABBR:
3182 				PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(zone, z);
3183 				add_assoc_bool(return_value, "is_dst", parsed_time->dst);
3184 				add_assoc_string(return_value, "tz_abbr", parsed_time->tz_abbr);
3185 				break;
3186 		}
3187 	}
3188 	if (parsed_time->have_relative) {
3189 		array_init(&element);
3190 		add_assoc_long(&element, "year",   parsed_time->relative.y);
3191 		add_assoc_long(&element, "month",  parsed_time->relative.m);
3192 		add_assoc_long(&element, "day",    parsed_time->relative.d);
3193 		add_assoc_long(&element, "hour",   parsed_time->relative.h);
3194 		add_assoc_long(&element, "minute", parsed_time->relative.i);
3195 		add_assoc_long(&element, "second", parsed_time->relative.s);
3196 		if (parsed_time->relative.have_weekday_relative) {
3197 			add_assoc_long(&element, "weekday", parsed_time->relative.weekday);
3198 		}
3199 		if (parsed_time->relative.have_special_relative && (parsed_time->relative.special.type == TIMELIB_SPECIAL_WEEKDAY)) {
3200 			add_assoc_long(&element, "weekdays", parsed_time->relative.special.amount);
3201 		}
3202 		if (parsed_time->relative.first_last_day_of) {
3203 			add_assoc_bool(&element, parsed_time->relative.first_last_day_of == TIMELIB_SPECIAL_FIRST_DAY_OF_MONTH ? "first_day_of_month" : "last_day_of_month", 1);
3204 		}
3205 		add_assoc_zval(return_value, "relative", &element);
3206 	}
3207 	timelib_time_dtor(parsed_time);
3208 } /* }}} */
3209 
3210 /* {{{ Returns associative array with detailed info about given date */
PHP_FUNCTION(date_parse)3211 PHP_FUNCTION(date_parse)
3212 {
3213 	zend_string                    *date;
3214 	timelib_error_container *error;
3215 	timelib_time                   *parsed_time;
3216 
3217 	ZEND_PARSE_PARAMETERS_START(1, 1)
3218 		Z_PARAM_STR(date)
3219 	ZEND_PARSE_PARAMETERS_END();
3220 
3221 	parsed_time = timelib_strtotime(ZSTR_VAL(date), ZSTR_LEN(date), &error, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
3222 	php_date_do_return_parsed_time(INTERNAL_FUNCTION_PARAM_PASSTHRU, parsed_time, error);
3223 }
3224 /* }}} */
3225 
3226 /* {{{ Returns associative array with detailed info about given date */
PHP_FUNCTION(date_parse_from_format)3227 PHP_FUNCTION(date_parse_from_format)
3228 {
3229 	zend_string                    *date, *format;
3230 	timelib_error_container *error;
3231 	timelib_time                   *parsed_time;
3232 
3233 	ZEND_PARSE_PARAMETERS_START(2, 2)
3234 		Z_PARAM_STR(format)
3235 		Z_PARAM_PATH_STR(date)
3236 	ZEND_PARSE_PARAMETERS_END();
3237 
3238 	parsed_time = timelib_parse_from_format(ZSTR_VAL(format), ZSTR_VAL(date), ZSTR_LEN(date), &error, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
3239 	php_date_do_return_parsed_time(INTERNAL_FUNCTION_PARAM_PASSTHRU, parsed_time, error);
3240 }
3241 /* }}} */
3242 
3243 /* {{{ Returns date formatted according to given format */
PHP_FUNCTION(date_format)3244 PHP_FUNCTION(date_format)
3245 {
3246 	zval         *object;
3247 	php_date_obj *dateobj;
3248 	char         *format;
3249 	size_t       format_len;
3250 
3251 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Os", &object, date_ce_interface, &format, &format_len) == FAILURE) {
3252 		RETURN_THROWS();
3253 	}
3254 	dateobj = Z_PHPDATE_P(object);
3255 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
3256 	RETURN_STR(date_format(format, format_len, dateobj->time, dateobj->time->is_localtime));
3257 }
3258 /* }}} */
3259 
php_date_modify(zval * object,char * modify,size_t modify_len)3260 static bool php_date_modify(zval *object, char *modify, size_t modify_len) /* {{{ */
3261 {
3262 	php_date_obj *dateobj;
3263 	timelib_time *tmp_time;
3264 	timelib_error_container *err = NULL;
3265 
3266 	dateobj = Z_PHPDATE_P(object);
3267 
3268 	if (!(dateobj->time)) {
3269 		date_throw_uninitialized_error(Z_OBJCE_P(object));
3270 		return 0;
3271 	}
3272 
3273 	tmp_time = timelib_strtotime(modify, modify_len, &err, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
3274 
3275 	/* update last errors and warnings */
3276 	update_errors_warnings(&err);
3277 
3278 	if (err && err->error_count) {
3279 		/* spit out the first library error message, at least */
3280 		php_error_docref(NULL, E_WARNING, "Failed to parse time string (%s) at position %d (%c): %s", modify,
3281 			err->error_messages[0].position,
3282 			err->error_messages[0].character ? err->error_messages[0].character : ' ',
3283 			err->error_messages[0].message);
3284 		timelib_time_dtor(tmp_time);
3285 		return 0;
3286 	}
3287 
3288 	memcpy(&dateobj->time->relative, &tmp_time->relative, sizeof(timelib_rel_time));
3289 	dateobj->time->have_relative = tmp_time->have_relative;
3290 	dateobj->time->sse_uptodate = 0;
3291 
3292 	if (tmp_time->y != TIMELIB_UNSET) {
3293 		dateobj->time->y = tmp_time->y;
3294 	}
3295 	if (tmp_time->m != TIMELIB_UNSET) {
3296 		dateobj->time->m = tmp_time->m;
3297 	}
3298 	if (tmp_time->d != TIMELIB_UNSET) {
3299 		dateobj->time->d = tmp_time->d;
3300 	}
3301 
3302 	if (tmp_time->h != TIMELIB_UNSET) {
3303 		dateobj->time->h = tmp_time->h;
3304 		if (tmp_time->i != TIMELIB_UNSET) {
3305 			dateobj->time->i = tmp_time->i;
3306 			if (tmp_time->s != TIMELIB_UNSET) {
3307 				dateobj->time->s = tmp_time->s;
3308 			} else {
3309 				dateobj->time->s = 0;
3310 			}
3311 		} else {
3312 			dateobj->time->i = 0;
3313 			dateobj->time->s = 0;
3314 		}
3315 	}
3316 
3317 	if (tmp_time->us != TIMELIB_UNSET) {
3318 		dateobj->time->us = tmp_time->us;
3319 	}
3320 
3321 	/* Reset timezone to UTC if we detect a "@<ts>" modification */
3322 	if (
3323 		tmp_time->y == 1970 && tmp_time->m == 1 && tmp_time->d == 1 &&
3324 		tmp_time->h == 0 && tmp_time->i == 0 && tmp_time->s == 0 && tmp_time->us == 0 &&
3325 		tmp_time->have_zone && tmp_time->zone_type == TIMELIB_ZONETYPE_OFFSET &&
3326 		tmp_time->z == 0 && tmp_time->dst == 0
3327 	) {
3328 		timelib_set_timezone_from_offset(dateobj->time, 0);
3329 	}
3330 
3331 	timelib_time_dtor(tmp_time);
3332 
3333 	timelib_update_ts(dateobj->time, NULL);
3334 	timelib_update_from_sse(dateobj->time);
3335 	dateobj->time->have_relative = 0;
3336 	memset(&dateobj->time->relative, 0, sizeof(dateobj->time->relative));
3337 
3338 	return 1;
3339 } /* }}} */
3340 
3341 /* {{{ Alters the timestamp. */
PHP_FUNCTION(date_modify)3342 PHP_FUNCTION(date_modify)
3343 {
3344 	zval         *object;
3345 	char         *modify;
3346 	size_t        modify_len;
3347 
3348 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Os", &object, date_ce_date, &modify, &modify_len) == FAILURE) {
3349 		RETURN_THROWS();
3350 	}
3351 
3352 	if (!php_date_modify(object, modify, modify_len)) {
3353 		RETURN_FALSE;
3354 	}
3355 
3356 	RETURN_OBJ_COPY(Z_OBJ_P(object));
3357 }
3358 /* }}} */
3359 
3360 /* {{{ */
PHP_METHOD(DateTime,modify)3361 PHP_METHOD(DateTime, modify)
3362 {
3363 	zval                *object;
3364 	char                *modify;
3365 	size_t               modify_len;
3366 	zend_error_handling  zeh;
3367 
3368 	object = ZEND_THIS;
3369 	ZEND_PARSE_PARAMETERS_START(1, 1)
3370 		Z_PARAM_STRING(modify, modify_len)
3371 	ZEND_PARSE_PARAMETERS_END();
3372 
3373 	zend_replace_error_handling(EH_THROW, date_ce_date_malformed_string_exception, &zeh);
3374 	if (!php_date_modify(object, modify, modify_len)) {
3375 		zend_restore_error_handling(&zeh);
3376 		RETURN_THROWS();
3377 	}
3378 
3379 	zend_restore_error_handling(&zeh);
3380 
3381 	RETURN_OBJ_COPY(Z_OBJ_P(object));
3382 }
3383 /* }}} */
3384 
3385 /* {{{ */
PHP_METHOD(DateTimeImmutable,modify)3386 PHP_METHOD(DateTimeImmutable, modify)
3387 {
3388 	zval *object, new_object;
3389 	char *modify;
3390 	size_t   modify_len;
3391 	zend_error_handling zeh;
3392 
3393 	object = ZEND_THIS;
3394 	ZEND_PARSE_PARAMETERS_START(1, 1)
3395 		Z_PARAM_STRING(modify, modify_len)
3396 	ZEND_PARSE_PARAMETERS_END();
3397 
3398 	date_clone_immutable(object, &new_object);
3399 
3400 	zend_replace_error_handling(EH_THROW, date_ce_date_malformed_string_exception, &zeh);
3401 	if (!php_date_modify(&new_object, modify, modify_len)) {
3402 		zval_ptr_dtor(&new_object);
3403 		zend_restore_error_handling(&zeh);
3404 		RETURN_THROWS();
3405 	}
3406 
3407 	zend_restore_error_handling(&zeh);
3408 
3409 	RETURN_OBJ(Z_OBJ(new_object));
3410 }
3411 /* }}} */
3412 
php_date_add(zval * object,zval * interval,zval * return_value)3413 static void php_date_add(zval *object, zval *interval, zval *return_value) /* {{{ */
3414 {
3415 	php_date_obj     *dateobj;
3416 	php_interval_obj *intobj;
3417 	timelib_time     *new_time;
3418 
3419 	dateobj = Z_PHPDATE_P(object);
3420 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
3421 	intobj = Z_PHPINTERVAL_P(interval);
3422 	DATE_CHECK_INITIALIZED(intobj->initialized, Z_OBJCE_P(interval));
3423 
3424 	if (intobj->civil_or_wall == PHP_DATE_WALL) {
3425 		new_time = timelib_add_wall(dateobj->time, intobj->diff);
3426 	} else {
3427 		new_time = timelib_add(dateobj->time, intobj->diff);
3428 	}
3429 	timelib_time_dtor(dateobj->time);
3430 	dateobj->time = new_time;
3431 } /* }}} */
3432 
3433 /* {{{ Adds an interval to the current date in object. */
PHP_FUNCTION(date_add)3434 PHP_FUNCTION(date_add)
3435 {
3436 	zval *object, *interval;
3437 
3438 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "OO", &object, date_ce_date, &interval, date_ce_interval) == FAILURE) {
3439 		RETURN_THROWS();
3440 	}
3441 
3442 	php_date_add(object, interval, return_value);
3443 
3444 	RETURN_OBJ_COPY(Z_OBJ_P(object));
3445 }
3446 /* }}} */
3447 
3448 /* {{{ */
PHP_METHOD(DateTimeImmutable,add)3449 PHP_METHOD(DateTimeImmutable, add)
3450 {
3451 	zval *object, *interval, new_object;
3452 
3453 	object = ZEND_THIS;
3454 	ZEND_PARSE_PARAMETERS_START(1, 1)
3455 		Z_PARAM_OBJECT_OF_CLASS(interval, date_ce_interval)
3456 	ZEND_PARSE_PARAMETERS_END();
3457 
3458 	date_clone_immutable(object, &new_object);
3459 	php_date_add(&new_object, interval, return_value);
3460 
3461 	RETURN_OBJ(Z_OBJ(new_object));
3462 }
3463 /* }}} */
3464 
php_date_sub(zval * object,zval * interval,zval * return_value)3465 static void php_date_sub(zval *object, zval *interval, zval *return_value) /* {{{ */
3466 {
3467 	php_date_obj     *dateobj;
3468 	php_interval_obj *intobj;
3469 	timelib_time     *new_time;
3470 
3471 	dateobj = Z_PHPDATE_P(object);
3472 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
3473 	intobj = Z_PHPINTERVAL_P(interval);
3474 	DATE_CHECK_INITIALIZED(intobj->initialized, Z_OBJCE_P(interval));
3475 
3476 	if (intobj->diff->have_weekday_relative || intobj->diff->have_special_relative) {
3477 		php_error_docref(NULL, E_WARNING, "Only non-special relative time specifications are supported for subtraction");
3478 		return;
3479 	}
3480 
3481 	if (intobj->civil_or_wall == PHP_DATE_WALL) {
3482 		new_time = timelib_sub_wall(dateobj->time, intobj->diff);
3483 	} else {
3484 		new_time = timelib_sub(dateobj->time, intobj->diff);
3485 	}
3486 	timelib_time_dtor(dateobj->time);
3487 	dateobj->time = new_time;
3488 } /* }}} */
3489 
3490 /* {{{ Subtracts an interval to the current date in object. */
PHP_FUNCTION(date_sub)3491 PHP_FUNCTION(date_sub)
3492 {
3493 	zval *object, *interval;
3494 
3495 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "OO", &object, date_ce_date, &interval, date_ce_interval) == FAILURE) {
3496 		RETURN_THROWS();
3497 	}
3498 
3499 	php_date_sub(object, interval, return_value);
3500 	RETURN_OBJ_COPY(Z_OBJ_P(object));
3501 }
3502 /* }}} */
3503 
3504 /* {{{ Subtracts an interval to the current date in object. */
PHP_METHOD(DateTime,sub)3505 PHP_METHOD(DateTime, sub)
3506 {
3507 	zval *object, *interval;
3508 	zend_error_handling zeh;
3509 
3510 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "OO", &object, date_ce_date, &interval, date_ce_interval) == FAILURE) {
3511 		RETURN_THROWS();
3512 	}
3513 
3514 	zend_replace_error_handling(EH_THROW, date_ce_date_invalid_operation_exception, &zeh);
3515 	php_date_sub(object, interval, return_value);
3516 	zend_restore_error_handling(&zeh);
3517 
3518 	RETURN_OBJ_COPY(Z_OBJ_P(object));
3519 }
3520 /* }}} */
3521 
3522 /* {{{ */
PHP_METHOD(DateTimeImmutable,sub)3523 PHP_METHOD(DateTimeImmutable, sub)
3524 {
3525 	zval *object, *interval, new_object;
3526 	zend_error_handling zeh;
3527 
3528 	object = ZEND_THIS;
3529 	ZEND_PARSE_PARAMETERS_START(1, 1)
3530 		Z_PARAM_OBJECT_OF_CLASS(interval, date_ce_interval)
3531 	ZEND_PARSE_PARAMETERS_END();
3532 
3533 	date_clone_immutable(object, &new_object);
3534 
3535 	zend_replace_error_handling(EH_THROW, date_ce_date_invalid_operation_exception, &zeh);
3536 	php_date_sub(&new_object, interval, return_value);
3537 	zend_restore_error_handling(&zeh);
3538 
3539 	RETURN_OBJ(Z_OBJ(new_object));
3540 }
3541 /* }}} */
3542 
set_timezone_from_timelib_time(php_timezone_obj * tzobj,timelib_time * t)3543 static void set_timezone_from_timelib_time(php_timezone_obj *tzobj, timelib_time *t)
3544 {
3545 	/* Free abbreviation if already set */
3546 	if (tzobj->initialized && tzobj->type == TIMELIB_ZONETYPE_ABBR) {
3547 		timelib_free(tzobj->tzi.z.abbr);
3548 	}
3549 
3550 	/* Set new values */
3551 	tzobj->initialized = 1;
3552 	tzobj->type = t->zone_type;
3553 
3554 	switch (t->zone_type) {
3555 		case TIMELIB_ZONETYPE_ID:
3556 			tzobj->tzi.tz = t->tz_info;
3557 			break;
3558 		case TIMELIB_ZONETYPE_OFFSET:
3559 			tzobj->tzi.utc_offset = t->z;
3560 			break;
3561 		case TIMELIB_ZONETYPE_ABBR:
3562 			tzobj->tzi.z.utc_offset = t->z;
3563 			tzobj->tzi.z.dst = t->dst;
3564 			tzobj->tzi.z.abbr = timelib_strdup(t->tz_abbr);
3565 			break;
3566 	}
3567 }
3568 
3569 
3570 /* {{{ Return new DateTimeZone object relative to give DateTime */
PHP_FUNCTION(date_timezone_get)3571 PHP_FUNCTION(date_timezone_get)
3572 {
3573 	zval             *object;
3574 	php_date_obj     *dateobj;
3575 
3576 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O", &object, date_ce_interface) == FAILURE) {
3577 		RETURN_THROWS();
3578 	}
3579 	dateobj = Z_PHPDATE_P(object);
3580 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
3581 	if (dateobj->time->is_localtime) {
3582 		php_timezone_obj *tzobj;
3583 		php_date_instantiate(date_ce_timezone, return_value);
3584 		tzobj = Z_PHPTIMEZONE_P(return_value);
3585 		set_timezone_from_timelib_time(tzobj, dateobj->time);
3586 	} else {
3587 		RETURN_FALSE;
3588 	}
3589 }
3590 /* }}} */
3591 
php_date_timezone_set(zval * object,zval * timezone_object,zval * return_value)3592 static void php_date_timezone_set(zval *object, zval *timezone_object, zval *return_value) /* {{{ */
3593 {
3594 	php_date_obj     *dateobj;
3595 	php_timezone_obj *tzobj;
3596 
3597 	dateobj = Z_PHPDATE_P(object);
3598 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
3599 	tzobj = Z_PHPTIMEZONE_P(timezone_object);
3600 
3601 	switch (tzobj->type) {
3602 		case TIMELIB_ZONETYPE_OFFSET:
3603 			timelib_set_timezone_from_offset(dateobj->time, tzobj->tzi.utc_offset);
3604 			break;
3605 		case TIMELIB_ZONETYPE_ABBR:
3606 			timelib_set_timezone_from_abbr(dateobj->time, tzobj->tzi.z);
3607 			break;
3608 		case TIMELIB_ZONETYPE_ID:
3609 			timelib_set_timezone(dateobj->time, tzobj->tzi.tz);
3610 			break;
3611 	}
3612 	timelib_unixtime2local(dateobj->time, dateobj->time->sse);
3613 } /* }}} */
3614 
3615 /* {{{ Sets the timezone for the DateTime object. */
PHP_FUNCTION(date_timezone_set)3616 PHP_FUNCTION(date_timezone_set)
3617 {
3618 	zval *object;
3619 	zval *timezone_object;
3620 
3621 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "OO", &object, date_ce_date, &timezone_object, date_ce_timezone) == FAILURE) {
3622 		RETURN_THROWS();
3623 	}
3624 
3625 	php_date_timezone_set(object, timezone_object, return_value);
3626 
3627 	RETURN_OBJ_COPY(Z_OBJ_P(object));
3628 }
3629 /* }}} */
3630 
3631 /* {{{ */
PHP_METHOD(DateTimeImmutable,setTimezone)3632 PHP_METHOD(DateTimeImmutable, setTimezone)
3633 {
3634 	zval *object, new_object;
3635 	zval *timezone_object;
3636 
3637 	object = ZEND_THIS;
3638 	ZEND_PARSE_PARAMETERS_START(1, 1)
3639 		Z_PARAM_OBJECT_OF_CLASS(timezone_object, date_ce_timezone)
3640 	ZEND_PARSE_PARAMETERS_END();
3641 
3642 	date_clone_immutable(object, &new_object);
3643 	php_date_timezone_set(&new_object, timezone_object, return_value);
3644 
3645 	RETURN_OBJ(Z_OBJ(new_object));
3646 }
3647 /* }}} */
3648 
3649 /* {{{ Returns the DST offset. */
PHP_FUNCTION(date_offset_get)3650 PHP_FUNCTION(date_offset_get)
3651 {
3652 	zval                *object;
3653 	php_date_obj        *dateobj;
3654 	timelib_time_offset *offset;
3655 
3656 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O", &object, date_ce_interface) == FAILURE) {
3657 		RETURN_THROWS();
3658 	}
3659 	dateobj = Z_PHPDATE_P(object);
3660 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
3661 	if (dateobj->time->is_localtime) {
3662 		switch (dateobj->time->zone_type) {
3663 			case TIMELIB_ZONETYPE_ID:
3664 				offset = timelib_get_time_zone_info(dateobj->time->sse, dateobj->time->tz_info);
3665 				RETVAL_LONG(offset->offset);
3666 				timelib_time_offset_dtor(offset);
3667 				break;
3668 			case TIMELIB_ZONETYPE_OFFSET:
3669 				RETVAL_LONG(dateobj->time->z);
3670 				break;
3671 			case TIMELIB_ZONETYPE_ABBR:
3672 				RETVAL_LONG((dateobj->time->z + (3600 * dateobj->time->dst)));
3673 				break;
3674 		}
3675 		return;
3676 	} else {
3677 		RETURN_LONG(0);
3678 	}
3679 }
3680 /* }}} */
3681 
php_date_time_set(zval * object,zend_long h,zend_long i,zend_long s,zend_long ms,zval * return_value)3682 static void php_date_time_set(zval *object, zend_long h, zend_long i, zend_long s, zend_long ms, zval *return_value) /* {{{ */
3683 {
3684 	php_date_obj *dateobj;
3685 
3686 	dateobj = Z_PHPDATE_P(object);
3687 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
3688 	dateobj->time->h = h;
3689 	dateobj->time->i = i;
3690 	dateobj->time->s = s;
3691 	dateobj->time->us = ms;
3692 	timelib_update_ts(dateobj->time, NULL);
3693 	timelib_update_from_sse(dateobj->time);
3694 } /* }}} */
3695 
3696 /* {{{ Sets the time. */
PHP_FUNCTION(date_time_set)3697 PHP_FUNCTION(date_time_set)
3698 {
3699 	zval *object;
3700 	zend_long  h, i, s = 0, ms = 0;
3701 
3702 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Oll|ll", &object, date_ce_date, &h, &i, &s, &ms) == FAILURE) {
3703 		RETURN_THROWS();
3704 	}
3705 
3706 	php_date_time_set(object, h, i, s, ms, return_value);
3707 
3708 	RETURN_OBJ_COPY(Z_OBJ_P(object));
3709 }
3710 /* }}} */
3711 
3712 /* {{{ */
PHP_METHOD(DateTimeImmutable,setTime)3713 PHP_METHOD(DateTimeImmutable, setTime)
3714 {
3715 	zval *object, new_object;
3716 	zend_long  h, i, s = 0, ms = 0;
3717 
3718 	object = ZEND_THIS;
3719 	ZEND_PARSE_PARAMETERS_START(2, 4)
3720 		Z_PARAM_LONG(h)
3721 		Z_PARAM_LONG(i)
3722 		Z_PARAM_OPTIONAL
3723 		Z_PARAM_LONG(s)
3724 		Z_PARAM_LONG(ms)
3725 	ZEND_PARSE_PARAMETERS_END();
3726 
3727 	date_clone_immutable(object, &new_object);
3728 	php_date_time_set(&new_object, h, i, s, ms, return_value);
3729 
3730 	RETURN_OBJ(Z_OBJ(new_object));
3731 }
3732 /* }}} */
3733 
php_date_date_set(zval * object,zend_long y,zend_long m,zend_long d,zval * return_value)3734 static void php_date_date_set(zval *object, zend_long y, zend_long m, zend_long d, zval *return_value) /* {{{ */
3735 {
3736 	php_date_obj *dateobj;
3737 
3738 	dateobj = Z_PHPDATE_P(object);
3739 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
3740 	dateobj->time->y = y;
3741 	dateobj->time->m = m;
3742 	dateobj->time->d = d;
3743 	timelib_update_ts(dateobj->time, NULL);
3744 } /* }}} */
3745 
3746 /* {{{ Sets the date. */
PHP_FUNCTION(date_date_set)3747 PHP_FUNCTION(date_date_set)
3748 {
3749 	zval *object;
3750 	zend_long  y, m, d;
3751 
3752 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Olll", &object, date_ce_date, &y, &m, &d) == FAILURE) {
3753 		RETURN_THROWS();
3754 	}
3755 
3756 	php_date_date_set(object, y, m, d, return_value);
3757 
3758 	RETURN_OBJ_COPY(Z_OBJ_P(object));
3759 }
3760 /* }}} */
3761 
3762 /* {{{ */
PHP_METHOD(DateTimeImmutable,setDate)3763 PHP_METHOD(DateTimeImmutable, setDate)
3764 {
3765 	zval *object, new_object;
3766 	zend_long  y, m, d;
3767 
3768 	object = ZEND_THIS;
3769 	ZEND_PARSE_PARAMETERS_START(3, 3)
3770 		Z_PARAM_LONG(y)
3771 		Z_PARAM_LONG(m)
3772 		Z_PARAM_LONG(d)
3773 	ZEND_PARSE_PARAMETERS_END();
3774 
3775 	date_clone_immutable(object, &new_object);
3776 	php_date_date_set(&new_object, y, m, d, return_value);
3777 
3778 	RETURN_OBJ(Z_OBJ(new_object));
3779 }
3780 /* }}} */
3781 
php_date_isodate_set(zval * object,zend_long y,zend_long w,zend_long d,zval * return_value)3782 static void php_date_isodate_set(zval *object, zend_long y, zend_long w, zend_long d, zval *return_value) /* {{{ */
3783 {
3784 	php_date_obj *dateobj;
3785 
3786 	dateobj = Z_PHPDATE_P(object);
3787 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
3788 	dateobj->time->y = y;
3789 	dateobj->time->m = 1;
3790 	dateobj->time->d = 1;
3791 	memset(&dateobj->time->relative, 0, sizeof(dateobj->time->relative));
3792 	dateobj->time->relative.d = timelib_daynr_from_weeknr(y, w, d);
3793 	dateobj->time->have_relative = 1;
3794 
3795 	timelib_update_ts(dateobj->time, NULL);
3796 } /* }}} */
3797 
3798 /* {{{ Sets the ISO date. */
PHP_FUNCTION(date_isodate_set)3799 PHP_FUNCTION(date_isodate_set)
3800 {
3801 	zval *object;
3802 	zend_long  y, w, d = 1;
3803 
3804 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Oll|l", &object, date_ce_date, &y, &w, &d) == FAILURE) {
3805 		RETURN_THROWS();
3806 	}
3807 
3808 	php_date_isodate_set(object, y, w, d, return_value);
3809 
3810 	RETURN_OBJ_COPY(Z_OBJ_P(object));
3811 }
3812 /* }}} */
3813 
3814 /* {{{ */
PHP_METHOD(DateTimeImmutable,setISODate)3815 PHP_METHOD(DateTimeImmutable, setISODate)
3816 {
3817 	zval *object, new_object;
3818 	zend_long  y, w, d = 1;
3819 
3820 	object = ZEND_THIS;
3821 	ZEND_PARSE_PARAMETERS_START(2, 3)
3822 		Z_PARAM_LONG(y)
3823 		Z_PARAM_LONG(w)
3824 		Z_PARAM_OPTIONAL
3825 		Z_PARAM_LONG(d)
3826 	ZEND_PARSE_PARAMETERS_END();
3827 
3828 	date_clone_immutable(object, &new_object);
3829 	php_date_isodate_set(&new_object, y, w, d, return_value);
3830 
3831 	RETURN_OBJ(Z_OBJ(new_object));
3832 }
3833 /* }}} */
3834 
php_date_timestamp_set(zval * object,zend_long timestamp,zval * return_value)3835 static void php_date_timestamp_set(zval *object, zend_long timestamp, zval *return_value) /* {{{ */
3836 {
3837 	php_date_obj *dateobj;
3838 
3839 	dateobj = Z_PHPDATE_P(object);
3840 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
3841 	timelib_unixtime2local(dateobj->time, (timelib_sll)timestamp);
3842 	timelib_update_ts(dateobj->time, NULL);
3843 	php_date_set_time_fraction(dateobj->time, 0);
3844 } /* }}} */
3845 
3846 /* {{{ Sets the date and time based on an Unix timestamp. */
PHP_FUNCTION(date_timestamp_set)3847 PHP_FUNCTION(date_timestamp_set)
3848 {
3849 	zval *object;
3850 	zend_long  timestamp;
3851 
3852 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Ol", &object, date_ce_date, &timestamp) == FAILURE) {
3853 		RETURN_THROWS();
3854 	}
3855 
3856 	php_date_timestamp_set(object, timestamp, return_value);
3857 
3858 	RETURN_OBJ_COPY(Z_OBJ_P(object));
3859 }
3860 /* }}} */
3861 
3862 /* {{{ */
PHP_METHOD(DateTimeImmutable,setTimestamp)3863 PHP_METHOD(DateTimeImmutable, setTimestamp)
3864 {
3865 	zval *object, new_object;
3866 	zend_long  timestamp;
3867 
3868 	object = ZEND_THIS;
3869 	ZEND_PARSE_PARAMETERS_START(1, 1)
3870 		Z_PARAM_LONG(timestamp)
3871 	ZEND_PARSE_PARAMETERS_END();
3872 
3873 	date_clone_immutable(object, &new_object);
3874 	php_date_timestamp_set(&new_object, timestamp, return_value);
3875 
3876 	RETURN_OBJ(Z_OBJ(new_object));
3877 }
3878 /* }}} */
3879 
3880 /* {{{ */
PHP_METHOD(DateTimeImmutable,setMicrosecond)3881 PHP_METHOD(DateTimeImmutable, setMicrosecond)
3882 {
3883 	zval *object, new_object;
3884 	php_date_obj *dateobj, *new_dateobj;
3885 	zend_long us;
3886 
3887 	ZEND_PARSE_PARAMETERS_START(1, 1)
3888 		Z_PARAM_LONG(us)
3889 	ZEND_PARSE_PARAMETERS_END();
3890 
3891 	if (UNEXPECTED(us < 0 || us > 999999)) {
3892 		zend_argument_error(
3893 			date_ce_date_range_error,
3894 			1,
3895 			"must be between 0 and 999999, " ZEND_LONG_FMT " given",
3896 			us
3897 		);
3898 		RETURN_THROWS();
3899 	}
3900 
3901 	object = ZEND_THIS;
3902 	dateobj = Z_PHPDATE_P(object);
3903 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
3904 
3905 	date_clone_immutable(object, &new_object);
3906 	new_dateobj = Z_PHPDATE_P(&new_object);
3907 
3908 	php_date_set_time_fraction(new_dateobj->time, (int)us);
3909 
3910 	RETURN_OBJ(Z_OBJ(new_object));
3911 }
3912 /* }}} */
3913 
3914 /* {{{ */
PHP_METHOD(DateTime,setMicrosecond)3915 PHP_METHOD(DateTime, setMicrosecond)
3916 {
3917 	zval *object;
3918 	php_date_obj *dateobj;
3919 	zend_long us;
3920 
3921 	ZEND_PARSE_PARAMETERS_START(1, 1)
3922 		Z_PARAM_LONG(us)
3923 	ZEND_PARSE_PARAMETERS_END();
3924 
3925 	if (UNEXPECTED(us < 0 || us > 999999)) {
3926 		zend_argument_error(
3927 			date_ce_date_range_error,
3928 			1,
3929 			"must be between 0 and 999999, " ZEND_LONG_FMT " given",
3930 			us
3931 		);
3932 		RETURN_THROWS();
3933 	}
3934 
3935 	object = ZEND_THIS;
3936 	dateobj = Z_PHPDATE_P(object);
3937 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
3938 	php_date_set_time_fraction(dateobj->time, (int)us);
3939 
3940 	RETURN_OBJ_COPY(Z_OBJ_P(object));
3941 }
3942 /* }}} */
3943 
3944 /* {{{ Gets the Unix timestamp. */
PHP_FUNCTION(date_timestamp_get)3945 PHP_FUNCTION(date_timestamp_get)
3946 {
3947 	zval         *object;
3948 	php_date_obj *dateobj;
3949 	zend_long     timestamp;
3950 	int           epoch_does_not_fit_in_zend_long;
3951 
3952 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O", &object, date_ce_interface) == FAILURE) {
3953 		RETURN_THROWS();
3954 	}
3955 	dateobj = Z_PHPDATE_P(object);
3956 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
3957 
3958 	if (!dateobj->time->sse_uptodate) {
3959 		timelib_update_ts(dateobj->time, NULL);
3960 	}
3961 
3962 	timestamp = timelib_date_to_int(dateobj->time, &epoch_does_not_fit_in_zend_long);
3963 
3964 	if (epoch_does_not_fit_in_zend_long) {
3965 		zend_throw_error(date_ce_date_range_error, "Epoch doesn't fit in a PHP integer");
3966 		RETURN_THROWS();
3967 	}
3968 
3969 	RETURN_LONG(timestamp);
3970 }
3971 /* }}} */
3972 
PHP_METHOD(DateTime,getMicrosecond)3973 PHP_METHOD(DateTime, getMicrosecond) /* {{{ */
3974 {
3975 	zval *object;
3976 	php_date_obj *dateobj;
3977 
3978 	ZEND_PARSE_PARAMETERS_NONE();
3979 
3980 	object = ZEND_THIS;
3981 	dateobj = Z_PHPDATE_P(object);
3982 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object));
3983 
3984 	RETURN_LONG((zend_long)dateobj->time->us);
3985 }
3986 /* }}} */
3987 
3988 /* {{{ Returns the difference between two DateTime objects. */
PHP_FUNCTION(date_diff)3989 PHP_FUNCTION(date_diff)
3990 {
3991 	zval         *object1, *object2;
3992 	php_date_obj *dateobj1, *dateobj2;
3993 	php_interval_obj *interval;
3994 	bool      absolute = 0;
3995 
3996 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "OO|b", &object1, date_ce_interface, &object2, date_ce_interface, &absolute) == FAILURE) {
3997 		RETURN_THROWS();
3998 	}
3999 	dateobj1 = Z_PHPDATE_P(object1);
4000 	dateobj2 = Z_PHPDATE_P(object2);
4001 	DATE_CHECK_INITIALIZED(dateobj1->time, Z_OBJCE_P(object1));
4002 	DATE_CHECK_INITIALIZED(dateobj2->time, Z_OBJCE_P(object2));
4003 
4004 	php_date_instantiate(date_ce_interval, return_value);
4005 	interval = Z_PHPINTERVAL_P(return_value);
4006 	interval->diff = timelib_diff(dateobj1->time, dateobj2->time);
4007 	if (absolute) {
4008 		interval->diff->invert = 0;
4009 	}
4010 	interval->initialized = 1;
4011 	interval->civil_or_wall = PHP_DATE_CIVIL;
4012 }
4013 /* }}} */
4014 
timezone_initialize(php_timezone_obj * tzobj,const char * tz,size_t tz_len,char ** warning_message)4015 static bool timezone_initialize(php_timezone_obj *tzobj, const char *tz, size_t tz_len, char **warning_message) /* {{{ */
4016 {
4017 	timelib_time *dummy_t = ecalloc(1, sizeof(timelib_time));
4018 	int           dst, not_found;
4019 	const char   *orig_tz = tz;
4020 
4021 	if (strlen(tz) != tz_len) {
4022 		if (warning_message) {
4023 			spprintf(warning_message, 0, "Timezone must not contain null bytes");
4024 		}
4025 		efree(dummy_t);
4026 		return false;
4027 	}
4028 
4029 	dummy_t->z = timelib_parse_zone(&tz, &dst, dummy_t, &not_found, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
4030 	if ((dummy_t->z >= (100 * 60 * 60)) || (dummy_t->z <= (-100 * 60 * 60))) {
4031 		if (warning_message) {
4032 			spprintf(warning_message, 0, "Timezone offset is out of range (%s)", orig_tz);
4033 		}
4034 		timelib_free(dummy_t->tz_abbr);
4035 		efree(dummy_t);
4036 		return false;
4037 	}
4038 	dummy_t->dst = dst;
4039 	if (!not_found && (*tz != '\0')) {
4040 		if (warning_message) {
4041 			spprintf(warning_message, 0, "Unknown or bad timezone (%s)", orig_tz);
4042 		}
4043 		timelib_free(dummy_t->tz_abbr);
4044 		efree(dummy_t);
4045 		return false;
4046 	}
4047 	if (not_found) {
4048 		if (warning_message) {
4049 			spprintf(warning_message, 0, "Unknown or bad timezone (%s)", orig_tz);
4050 		}
4051 		efree(dummy_t);
4052 		return false;
4053 	} else {
4054 		set_timezone_from_timelib_time(tzobj, dummy_t);
4055 		timelib_free(dummy_t->tz_abbr);
4056 		efree(dummy_t);
4057 		return true;
4058 	}
4059 } /* }}} */
4060 
4061 /* {{{ Returns new DateTimeZone object */
PHP_FUNCTION(timezone_open)4062 PHP_FUNCTION(timezone_open)
4063 {
4064 	zend_string *tz;
4065 	php_timezone_obj *tzobj;
4066 	char *warning_message;
4067 
4068 	ZEND_PARSE_PARAMETERS_START(1, 1)
4069 		Z_PARAM_PATH_STR(tz) /* To prevent null bytes */
4070 	ZEND_PARSE_PARAMETERS_END();
4071 
4072 	tzobj = Z_PHPTIMEZONE_P(php_date_instantiate(date_ce_timezone, return_value));
4073 	if (!timezone_initialize(tzobj, ZSTR_VAL(tz), ZSTR_LEN(tz), &warning_message)) {
4074 		php_error_docref(NULL, E_WARNING, "%s", warning_message);
4075 		efree(warning_message);
4076 		zval_ptr_dtor(return_value);
4077 		RETURN_FALSE;
4078 	}
4079 }
4080 /* }}} */
4081 
4082 /* {{{ Creates new DateTimeZone object. */
PHP_METHOD(DateTimeZone,__construct)4083 PHP_METHOD(DateTimeZone, __construct)
4084 {
4085 	zend_string *tz;
4086 	php_timezone_obj *tzobj;
4087 	char *exception_message;
4088 
4089 	ZEND_PARSE_PARAMETERS_START(1, 1)
4090 		Z_PARAM_PATH_STR(tz) /* To prevent null bytes */
4091 	ZEND_PARSE_PARAMETERS_END();
4092 
4093 	tzobj = Z_PHPTIMEZONE_P(ZEND_THIS);
4094 	if (!timezone_initialize(tzobj, ZSTR_VAL(tz), ZSTR_LEN(tz), &exception_message)) {
4095 		zend_throw_exception_ex(date_ce_date_invalid_timezone_exception, 0, "DateTimeZone::__construct(): %s", exception_message);
4096 		efree(exception_message);
4097 		RETURN_THROWS();
4098 	}
4099 }
4100 /* }}} */
4101 
php_date_timezone_initialize_from_hash(zval ** return_value,php_timezone_obj ** tzobj,HashTable * myht)4102 static bool php_date_timezone_initialize_from_hash(zval **return_value, php_timezone_obj **tzobj, HashTable *myht) /* {{{ */
4103 {
4104 	zval            *z_timezone_type;
4105 
4106 	if ((z_timezone_type = zend_hash_str_find(myht, "timezone_type", sizeof("timezone_type") - 1)) == NULL) {
4107 		return false;
4108 	}
4109 
4110 	zval *z_timezone;
4111 
4112 	if ((z_timezone = zend_hash_str_find(myht, "timezone", sizeof("timezone") - 1)) == NULL) {
4113 		return false;
4114 	}
4115 
4116 	if (Z_TYPE_P(z_timezone_type) != IS_LONG) {
4117 		return false;
4118 	}
4119 	if (Z_LVAL_P(z_timezone_type) < TIMELIB_ZONETYPE_OFFSET || Z_LVAL_P(z_timezone_type) > TIMELIB_ZONETYPE_ID) {
4120 		return false;
4121 	}
4122 	if (Z_TYPE_P(z_timezone) != IS_STRING) {
4123 		return false;
4124 	}
4125 	return timezone_initialize(*tzobj, Z_STRVAL_P(z_timezone), Z_STRLEN_P(z_timezone), NULL);
4126 } /* }}} */
4127 
4128 /* {{{  */
PHP_METHOD(DateTimeZone,__set_state)4129 PHP_METHOD(DateTimeZone, __set_state)
4130 {
4131 	php_timezone_obj *tzobj;
4132 	zval             *array;
4133 	HashTable        *myht;
4134 
4135 	ZEND_PARSE_PARAMETERS_START(1, 1)
4136 		Z_PARAM_ARRAY(array)
4137 	ZEND_PARSE_PARAMETERS_END();
4138 
4139 	myht = Z_ARRVAL_P(array);
4140 
4141 	php_date_instantiate(date_ce_timezone, return_value);
4142 	tzobj = Z_PHPTIMEZONE_P(return_value);
4143 	if (!php_date_timezone_initialize_from_hash(&return_value, &tzobj, myht)) {
4144 		zend_throw_error(NULL, "Invalid serialization data for DateTimeZone object");
4145 		RETURN_THROWS();
4146 	}
4147 }
4148 /* }}} */
4149 
4150 /* {{{  */
PHP_METHOD(DateTimeZone,__wakeup)4151 PHP_METHOD(DateTimeZone, __wakeup)
4152 {
4153 	zval             *object = ZEND_THIS;
4154 	php_timezone_obj *tzobj;
4155 	HashTable        *myht;
4156 
4157 	ZEND_PARSE_PARAMETERS_NONE();
4158 
4159 	tzobj = Z_PHPTIMEZONE_P(object);
4160 
4161 	myht = Z_OBJPROP_P(object);
4162 
4163 	if (!php_date_timezone_initialize_from_hash(&return_value, &tzobj, myht)) {
4164 		zend_throw_error(NULL, "Invalid serialization data for DateTimeZone object");
4165 		RETURN_THROWS();
4166 	}
4167 }
4168 /* }}} */
4169 
4170 /* {{{ */
PHP_METHOD(DateTimeZone,__serialize)4171 PHP_METHOD(DateTimeZone, __serialize)
4172 {
4173 	zval             *object = ZEND_THIS;
4174 	php_timezone_obj *tzobj;
4175 	HashTable        *myht;
4176 
4177 	ZEND_PARSE_PARAMETERS_NONE();
4178 
4179 	tzobj = Z_PHPTIMEZONE_P(object);
4180 	DATE_CHECK_INITIALIZED(tzobj->initialized, Z_OBJCE_P(object));
4181 
4182 	array_init(return_value);
4183 	myht = Z_ARRVAL_P(return_value);
4184 	date_timezone_object_to_hash(tzobj, myht);
4185 
4186 	add_common_properties(myht, &tzobj->std);
4187 }
4188 /* }}} */
4189 
date_timezone_is_internal_property(zend_string * name)4190 static bool date_timezone_is_internal_property(zend_string *name)
4191 {
4192 	if (
4193 		zend_string_equals_literal(name, "timezone_type") ||
4194 		zend_string_equals_literal(name, "timezone")
4195 	) {
4196 		return 1;
4197 	}
4198 	return 0;
4199 }
4200 
restore_custom_datetimezone_properties(zval * object,HashTable * myht)4201 static void restore_custom_datetimezone_properties(zval *object, HashTable *myht)
4202 {
4203 	zend_string      *prop_name;
4204 	zval             *prop_val;
4205 
4206 	ZEND_HASH_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) {
4207 		if (!prop_name || (Z_TYPE_P(prop_val) == IS_REFERENCE) || date_timezone_is_internal_property(prop_name)) {
4208 			continue;
4209 		}
4210 		update_property(Z_OBJ_P(object), prop_name, prop_val);
4211 	} ZEND_HASH_FOREACH_END();
4212 }
4213 
4214 /* {{{ */
PHP_METHOD(DateTimeZone,__unserialize)4215 PHP_METHOD(DateTimeZone, __unserialize)
4216 {
4217 	zval             *object = ZEND_THIS;
4218 	php_timezone_obj *tzobj;
4219 	zval             *array;
4220 	HashTable        *myht;
4221 
4222 	ZEND_PARSE_PARAMETERS_START(1, 1)
4223 		Z_PARAM_ARRAY(array)
4224 	ZEND_PARSE_PARAMETERS_END();
4225 
4226 	tzobj = Z_PHPTIMEZONE_P(object);
4227 	myht = Z_ARRVAL_P(array);
4228 
4229 	if (!php_date_timezone_initialize_from_hash(&object, &tzobj, myht)) {
4230 		zend_throw_error(NULL, "Invalid serialization data for DateTimeZone object");
4231 		RETURN_THROWS();
4232 	}
4233 
4234 	restore_custom_datetimezone_properties(object, myht);
4235 }
4236 /* }}} */
4237 
4238 /* {{{ Returns the name of the timezone. */
PHP_FUNCTION(timezone_name_get)4239 PHP_FUNCTION(timezone_name_get)
4240 {
4241 	zval             *object;
4242 	php_timezone_obj *tzobj;
4243 
4244 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O", &object, date_ce_timezone) == FAILURE) {
4245 		RETURN_THROWS();
4246 	}
4247 	tzobj = Z_PHPTIMEZONE_P(object);
4248 	DATE_CHECK_INITIALIZED(tzobj->initialized, Z_OBJCE_P(object));
4249 	php_timezone_to_string(tzobj, return_value);
4250 }
4251 /* }}} */
4252 
4253 /* {{{ Returns the timezone name from abbreviation */
PHP_FUNCTION(timezone_name_from_abbr)4254 PHP_FUNCTION(timezone_name_from_abbr)
4255 {
4256 	zend_string  *abbr;
4257 	const char   *tzid;
4258 	zend_long     gmtoffset = -1;
4259 	zend_long     isdst = -1;
4260 
4261 	ZEND_PARSE_PARAMETERS_START(1, 3)
4262 		Z_PARAM_STR(abbr)
4263 		Z_PARAM_OPTIONAL
4264 		Z_PARAM_LONG(gmtoffset)
4265 		Z_PARAM_LONG(isdst)
4266 	ZEND_PARSE_PARAMETERS_END();
4267 
4268 	tzid = timelib_timezone_id_from_abbr(ZSTR_VAL(abbr), gmtoffset, isdst);
4269 
4270 	if (tzid) {
4271 		RETURN_STRING(tzid);
4272 	} else {
4273 		RETURN_FALSE;
4274 	}
4275 }
4276 /* }}} */
4277 
4278 /* {{{ Returns the timezone offset. */
PHP_FUNCTION(timezone_offset_get)4279 PHP_FUNCTION(timezone_offset_get)
4280 {
4281 	zval                *object, *dateobject;
4282 	php_timezone_obj    *tzobj;
4283 	php_date_obj        *dateobj;
4284 	timelib_time_offset *offset;
4285 
4286 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "OO", &object, date_ce_timezone, &dateobject, date_ce_interface) == FAILURE) {
4287 		RETURN_THROWS();
4288 	}
4289 	tzobj = Z_PHPTIMEZONE_P(object);
4290 	DATE_CHECK_INITIALIZED(tzobj->initialized, Z_OBJCE_P(object));
4291 	dateobj = Z_PHPDATE_P(dateobject);
4292 	DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(dateobject));
4293 
4294 	switch (tzobj->type) {
4295 		case TIMELIB_ZONETYPE_ID:
4296 			offset = timelib_get_time_zone_info(dateobj->time->sse, tzobj->tzi.tz);
4297 			RETVAL_LONG(offset->offset);
4298 			timelib_time_offset_dtor(offset);
4299 			break;
4300 		case TIMELIB_ZONETYPE_OFFSET:
4301 			RETURN_LONG(tzobj->tzi.utc_offset);
4302 			break;
4303 		case TIMELIB_ZONETYPE_ABBR:
4304 			RETURN_LONG(tzobj->tzi.z.utc_offset + (tzobj->tzi.z.dst * 3600));
4305 			break;
4306 	}
4307 }
4308 /* }}} */
4309 
4310 /* {{{ Returns numerically indexed array containing associative array for all transitions in the specified range for the timezone. */
PHP_FUNCTION(timezone_transitions_get)4311 PHP_FUNCTION(timezone_transitions_get)
4312 {
4313 	zval                *object, element;
4314 	php_timezone_obj    *tzobj;
4315 	uint64_t             begin = 0;
4316 	bool                 found;
4317 	zend_long            timestamp_begin = ZEND_LONG_MIN, timestamp_end = INT32_MAX;
4318 
4319 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O|ll", &object, date_ce_timezone, &timestamp_begin, &timestamp_end) == FAILURE) {
4320 		RETURN_THROWS();
4321 	}
4322 	tzobj = Z_PHPTIMEZONE_P(object);
4323 	DATE_CHECK_INITIALIZED(tzobj->initialized, Z_OBJCE_P(object));
4324 	if (tzobj->type != TIMELIB_ZONETYPE_ID) {
4325 		RETURN_FALSE;
4326 	}
4327 
4328 #define add_nominal() \
4329 		array_init(&element); \
4330 		add_assoc_long(&element, "ts",     timestamp_begin); \
4331 		add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601_LARGE_YEAR, 13, timestamp_begin, 0)); \
4332 		add_assoc_long(&element, "offset", tzobj->tzi.tz->type[0].offset); \
4333 		add_assoc_bool(&element, "isdst",  tzobj->tzi.tz->type[0].isdst); \
4334 		add_assoc_string(&element, "abbr", &tzobj->tzi.tz->timezone_abbr[tzobj->tzi.tz->type[0].abbr_idx]); \
4335 		add_next_index_zval(return_value, &element);
4336 
4337 #define add(i,ts) \
4338 		array_init(&element); \
4339 		add_assoc_long(&element, "ts",     ts); \
4340 		add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601_LARGE_YEAR, 13, ts, 0)); \
4341 		add_assoc_long(&element, "offset", tzobj->tzi.tz->type[tzobj->tzi.tz->trans_idx[i]].offset); \
4342 		add_assoc_bool(&element, "isdst",  tzobj->tzi.tz->type[tzobj->tzi.tz->trans_idx[i]].isdst); \
4343 		add_assoc_string(&element, "abbr", &tzobj->tzi.tz->timezone_abbr[tzobj->tzi.tz->type[tzobj->tzi.tz->trans_idx[i]].abbr_idx]); \
4344 		add_next_index_zval(return_value, &element);
4345 
4346 #define add_by_index(i,ts) \
4347 		array_init(&element); \
4348 		add_assoc_long(&element, "ts",     ts); \
4349 		add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601_LARGE_YEAR, 13, ts, 0)); \
4350 		add_assoc_long(&element, "offset", tzobj->tzi.tz->type[i].offset); \
4351 		add_assoc_bool(&element, "isdst",  tzobj->tzi.tz->type[i].isdst); \
4352 		add_assoc_string(&element, "abbr", &tzobj->tzi.tz->timezone_abbr[tzobj->tzi.tz->type[i].abbr_idx]); \
4353 		add_next_index_zval(return_value, &element);
4354 
4355 #define add_from_tto(to,ts) \
4356 		array_init(&element); \
4357 		add_assoc_long(&element, "ts",     ts); \
4358 		add_assoc_str(&element, "time", php_format_date(DATE_FORMAT_ISO8601_LARGE_YEAR, 13, ts, 0)); \
4359 		add_assoc_long(&element, "offset", (to)->offset); \
4360 		add_assoc_bool(&element, "isdst",  (to)->is_dst); \
4361 		add_assoc_string(&element, "abbr", (to)->abbr); \
4362 		add_next_index_zval(return_value, &element);
4363 
4364 #define add_last() add(tzobj->tzi.tz->bit64.timecnt - 1, timestamp_begin)
4365 
4366 	array_init(return_value);
4367 
4368 	if (timestamp_begin == ZEND_LONG_MIN) {
4369 		add_nominal();
4370 		begin = 0;
4371 		found = 1;
4372 	} else {
4373 		begin = 0;
4374 		found = 0;
4375 		if (tzobj->tzi.tz->bit64.timecnt > 0) {
4376 			do {
4377 				if (tzobj->tzi.tz->trans[begin] > timestamp_begin) {
4378 					if (begin > 0) {
4379 						add(begin - 1, timestamp_begin);
4380 					} else {
4381 						add_nominal();
4382 					}
4383 					found = 1;
4384 					break;
4385 				}
4386 				begin++;
4387 			} while (begin < tzobj->tzi.tz->bit64.timecnt);
4388 		}
4389 	}
4390 
4391 	if (!found) {
4392 		if (tzobj->tzi.tz->bit64.timecnt > 0) {
4393 			if (tzobj->tzi.tz->posix_info && tzobj->tzi.tz->posix_info->dst_end) {
4394 				timelib_time_offset *tto = timelib_get_time_zone_info(timestamp_begin, tzobj->tzi.tz);
4395 				add_from_tto(tto, timestamp_begin);
4396 				timelib_time_offset_dtor(tto);
4397 			} else {
4398 				add_last();
4399 			}
4400 		} else {
4401 			add_nominal();
4402 		}
4403 	} else {
4404 		for (uint64_t i = begin; i < tzobj->tzi.tz->bit64.timecnt; ++i) {
4405 			if (tzobj->tzi.tz->trans[i] < timestamp_end) {
4406 				add(i, tzobj->tzi.tz->trans[i]);
4407 			} else {
4408 				return;
4409 			}
4410 		}
4411 	}
4412 	if (tzobj->tzi.tz->posix_info && tzobj->tzi.tz->posix_info->dst_end) {
4413 		timelib_sll start_y, end_y, dummy_m, dummy_d;
4414 		timelib_sll last_transition_ts = tzobj->tzi.tz->trans[tzobj->tzi.tz->bit64.timecnt - 1];
4415 
4416 		/* Find out year for last transition */
4417 		timelib_unixtime2date(last_transition_ts, &start_y, &dummy_m, &dummy_d);
4418 
4419 		/* Find out year for final boundary timestamp */
4420 		timelib_unixtime2date(timestamp_end, &end_y, &dummy_m, &dummy_d);
4421 
4422 		for (timelib_sll i = start_y; i <= end_y; i++) {
4423 			timelib_posix_transitions transitions = { 0 };
4424 
4425 			timelib_get_transitions_for_year(tzobj->tzi.tz, i, &transitions);
4426 
4427 			for (size_t j = 0; j < transitions.count; j++) {
4428 				if (transitions.times[j] <= last_transition_ts) {
4429 					continue;
4430 				}
4431 				if (transitions.times[j] < timestamp_begin) {
4432 					continue;
4433 				}
4434 				if (transitions.times[j] > timestamp_end) {
4435 					return;
4436 				}
4437 				add_by_index(transitions.types[j], transitions.times[j]);
4438 			}
4439 		}
4440 	}
4441 }
4442 /* }}} */
4443 
4444 /* {{{ Returns location information for a timezone, including country code, latitude/longitude and comments */
PHP_FUNCTION(timezone_location_get)4445 PHP_FUNCTION(timezone_location_get)
4446 {
4447 	zval                *object;
4448 	php_timezone_obj    *tzobj;
4449 
4450 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O", &object, date_ce_timezone) == FAILURE) {
4451 		RETURN_THROWS();
4452 	}
4453 	tzobj = Z_PHPTIMEZONE_P(object);
4454 	DATE_CHECK_INITIALIZED(tzobj->initialized, Z_OBJCE_P(object));
4455 	if (tzobj->type != TIMELIB_ZONETYPE_ID) {
4456 		RETURN_FALSE;
4457 	}
4458 
4459 	array_init(return_value);
4460 	add_assoc_string(return_value, "country_code", tzobj->tzi.tz->location.country_code);
4461 	add_assoc_double(return_value, "latitude", tzobj->tzi.tz->location.latitude);
4462 	add_assoc_double(return_value, "longitude", tzobj->tzi.tz->location.longitude);
4463 	add_assoc_string(return_value, "comments", tzobj->tzi.tz->location.comments);
4464 }
4465 /* }}} */
4466 
date_interval_initialize(timelib_rel_time ** rt,char * format,size_t format_length)4467 static bool date_interval_initialize(timelib_rel_time **rt, /*const*/ char *format, size_t format_length) /* {{{ */
4468 {
4469 	timelib_time     *b = NULL, *e = NULL;
4470 	timelib_rel_time *p = NULL;
4471 	int               r = 0;
4472 	bool              retval = false;
4473 	timelib_error_container *errors;
4474 
4475 	timelib_strtointerval(format, format_length, &b, &e, &p, &r, &errors);
4476 
4477 	if (errors->error_count > 0) {
4478 		zend_throw_exception_ex(date_ce_date_malformed_interval_string_exception, 0, "Unknown or bad format (%s)", format);
4479 		retval = false;
4480 		if (p) {
4481 			timelib_rel_time_dtor(p);
4482 		}
4483 	} else {
4484 		if (p) {
4485 			*rt = p;
4486 			retval = true;
4487 		} else {
4488 			if (b && e) {
4489 				timelib_update_ts(b, NULL);
4490 				timelib_update_ts(e, NULL);
4491 				*rt = timelib_diff(b, e);
4492 				retval = true;
4493 			} else {
4494 				zend_throw_exception_ex(date_ce_date_malformed_interval_string_exception, 0, "Failed to parse interval (%s)", format);
4495 				retval = false;
4496 			}
4497 		}
4498 	}
4499 	timelib_error_container_dtor(errors);
4500 	timelib_free(b);
4501 	timelib_free(e);
4502 	return retval;
4503 } /* }}} */
4504 
date_interval_compare_objects(zval * o1,zval * o2)4505 static int date_interval_compare_objects(zval *o1, zval *o2)
4506 {
4507 	ZEND_COMPARE_OBJECTS_FALLBACK(o1, o2);
4508 	/* There is no well defined way to compare intervals like P1M and P30D, which may compare
4509 	 * smaller, equal or greater depending on the point in time at which the interval starts. As
4510 	 * such, we treat DateInterval objects are non-comparable and emit a warning. */
4511 	zend_error(E_WARNING, "Cannot compare DateInterval objects");
4512 	return ZEND_UNCOMPARABLE;
4513 }
4514 
4515 /* {{{ date_interval_read_property */
date_interval_read_property(zend_object * object,zend_string * name,int type,void ** cache_slot,zval * rv)4516 static zval *date_interval_read_property(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv)
4517 {
4518 	php_interval_obj *obj;
4519 	zval *retval;
4520 	timelib_sll value = -1;
4521 	double      fvalue = -1;
4522 
4523 	obj = php_interval_obj_from_obj(object);
4524 
4525 	if (!obj->initialized) {
4526 		retval = zend_std_read_property(object, name, type, cache_slot, rv);
4527 		return retval;
4528 	}
4529 
4530 #define GET_VALUE_FROM_STRUCT(n,m)            \
4531 	if (zend_string_equals_literal(name, m)) { \
4532 		value = obj->diff->n; \
4533 		break; \
4534 	}
4535 	do {
4536 		GET_VALUE_FROM_STRUCT(y, "y");
4537 		GET_VALUE_FROM_STRUCT(m, "m");
4538 		GET_VALUE_FROM_STRUCT(d, "d");
4539 		GET_VALUE_FROM_STRUCT(h, "h");
4540 		GET_VALUE_FROM_STRUCT(i, "i");
4541 		GET_VALUE_FROM_STRUCT(s, "s");
4542 		if (zend_string_equals_literal(name, "f")) {
4543 			fvalue = obj->diff->us / 1000000.0;
4544 			break;
4545 		}
4546 		GET_VALUE_FROM_STRUCT(invert, "invert");
4547 		GET_VALUE_FROM_STRUCT(days, "days");
4548 		/* didn't find any */
4549 		retval = zend_std_read_property(object, name, type, cache_slot, rv);
4550 
4551 		return retval;
4552 	} while(0);
4553 
4554 	retval = rv;
4555 
4556 	if (fvalue != -1) {
4557 		ZVAL_DOUBLE(retval, fvalue);
4558 	} else if (value != TIMELIB_UNSET) {
4559 		ZVAL_LONG(retval, value);
4560 	} else {
4561 		ZVAL_FALSE(retval);
4562 	}
4563 
4564 	return retval;
4565 }
4566 /* }}} */
4567 
4568 /* {{{ date_interval_write_property */
date_interval_write_property(zend_object * object,zend_string * name,zval * value,void ** cache_slot)4569 static zval *date_interval_write_property(zend_object *object, zend_string *name, zval *value, void **cache_slot)
4570 {
4571 	php_interval_obj *obj;
4572 
4573 	obj = php_interval_obj_from_obj(object);
4574 
4575 	if (!obj->initialized) {
4576 		return zend_std_write_property(object, name, value, cache_slot);
4577 	}
4578 
4579 #define SET_VALUE_FROM_STRUCT(n,m) \
4580 	if (zend_string_equals_literal(name, m)) { \
4581 		obj->diff->n = zval_get_long(value); \
4582 		break; \
4583 	}
4584 
4585 	do {
4586 		SET_VALUE_FROM_STRUCT(y, "y");
4587 		SET_VALUE_FROM_STRUCT(m, "m");
4588 		SET_VALUE_FROM_STRUCT(d, "d");
4589 		SET_VALUE_FROM_STRUCT(h, "h");
4590 		SET_VALUE_FROM_STRUCT(i, "i");
4591 		SET_VALUE_FROM_STRUCT(s, "s");
4592 		if (zend_string_equals_literal(name, "f")) {
4593 			obj->diff->us = zend_dval_to_lval(zval_get_double(value) * 1000000.0);
4594 			break;
4595 		}
4596 		SET_VALUE_FROM_STRUCT(invert, "invert");
4597 		/* didn't find any */
4598 		value = zend_std_write_property(object, name, value, cache_slot);
4599 	} while(0);
4600 
4601 	return value;
4602 }
4603 /* }}} */
4604 
4605 /* {{{ date_interval_get_property_ptr_ptr */
date_interval_get_property_ptr_ptr(zend_object * object,zend_string * name,int type,void ** cache_slot)4606 static zval *date_interval_get_property_ptr_ptr(zend_object *object, zend_string *name, int type, void **cache_slot)
4607 {
4608 	zval *ret;
4609 
4610 	if (
4611 		zend_string_equals_literal(name, "y") ||
4612 		zend_string_equals_literal(name, "m") ||
4613 		zend_string_equals_literal(name, "d") ||
4614 		zend_string_equals_literal(name, "h") ||
4615 		zend_string_equals_literal(name, "i") ||
4616 		zend_string_equals_literal(name, "s") ||
4617 		zend_string_equals_literal(name, "f") ||
4618 		zend_string_equals_literal(name, "days") ||
4619 		zend_string_equals_literal(name, "invert") ) {
4620 		/* Fallback to read_property. */
4621 		ret = NULL;
4622 	} else {
4623 		ret = zend_std_get_property_ptr_ptr(object, name, type, cache_slot);
4624 	}
4625 
4626 	return ret;
4627 }
4628 /* }}} */
4629 
4630 /* {{{ Creates new DateInterval object. */
PHP_METHOD(DateInterval,__construct)4631 PHP_METHOD(DateInterval, __construct)
4632 {
4633 	zend_string *interval_string = NULL;
4634 	timelib_rel_time *reltime;
4635 
4636 	ZEND_PARSE_PARAMETERS_START(1, 1)
4637 		Z_PARAM_STR(interval_string)
4638 	ZEND_PARSE_PARAMETERS_END();
4639 
4640 	if (!date_interval_initialize(&reltime, ZSTR_VAL(interval_string), ZSTR_LEN(interval_string))) {
4641 		RETURN_THROWS();
4642 	}
4643 
4644 	php_interval_obj *diobj = Z_PHPINTERVAL_P(ZEND_THIS);
4645 	diobj->diff = reltime;
4646 	diobj->initialized = 1;
4647 	diobj->civil_or_wall = PHP_DATE_WALL;
4648 }
4649 /* }}} */
4650 
php_date_interval_initialize_from_hash(zval ** return_value,php_interval_obj ** intobj,HashTable * myht)4651 static void php_date_interval_initialize_from_hash(zval **return_value, php_interval_obj **intobj, HashTable *myht) /* {{{ */
4652 {
4653 	/* If we have a date_string, use that instead */
4654 	zval *date_str = zend_hash_str_find(myht, "date_string", strlen("date_string"));
4655 	if (date_str && Z_TYPE_P(date_str) == IS_STRING) {
4656 		timelib_time   *time;
4657 		timelib_error_container *err = NULL;
4658 
4659 		time = timelib_strtotime(Z_STRVAL_P(date_str), Z_STRLEN_P(date_str), &err, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
4660 
4661 		if (err->error_count > 0)  {
4662 			zend_throw_error(NULL,
4663 				"Unknown or bad format (%s) at position %d (%c) while unserializing: %s",
4664 				Z_STRVAL_P(date_str),
4665 				err->error_messages[0].position,
4666 				err->error_messages[0].character ? err->error_messages[0].character : ' ', err->error_messages[0].message);
4667 				timelib_time_dtor(time);
4668 				timelib_error_container_dtor(err);
4669 				return;
4670 		}
4671 
4672 		/* If ->diff is already set, then we need to free it first */
4673 		if ((*intobj)->diff) {
4674 			timelib_rel_time_dtor((*intobj)->diff);
4675 		}
4676 
4677 		(*intobj)->diff = timelib_rel_time_clone(&time->relative);
4678 		(*intobj)->initialized = 1;
4679 		(*intobj)->civil_or_wall = PHP_DATE_CIVIL;
4680 		(*intobj)->from_string = true;
4681 		(*intobj)->date_string = zend_string_copy(Z_STR_P(date_str));
4682 
4683 		timelib_time_dtor(time);
4684 		timelib_error_container_dtor(err);
4685 
4686 		return;
4687 	}
4688 
4689 	/* If ->diff is already set, then we need to free it first */
4690 	if ((*intobj)->diff) {
4691 		timelib_rel_time_dtor((*intobj)->diff);
4692 	}
4693 
4694 	/* Set new value */
4695 	(*intobj)->diff = timelib_rel_time_ctor();
4696 
4697 #define PHP_DATE_INTERVAL_READ_PROPERTY(element, member, itype, def) \
4698 	do { \
4699 		zval *z_arg = zend_hash_str_find(myht, element, sizeof(element) - 1); \
4700 		if (z_arg && Z_TYPE_P(z_arg) <= IS_STRING) { \
4701 			(*intobj)->diff->member = (itype)zval_get_long(z_arg); \
4702 		} else { \
4703 			(*intobj)->diff->member = (itype)def; \
4704 		} \
4705 	} while (0);
4706 
4707 #define PHP_DATE_INTERVAL_READ_PROPERTY_I64(element, member) \
4708 	do { \
4709 		zval *z_arg = zend_hash_str_find(myht, element, sizeof(element) - 1); \
4710 		if (z_arg && Z_TYPE_P(z_arg) <= IS_STRING) { \
4711 			zend_string *tmp_str; \
4712 			zend_string *str = zval_get_tmp_string(z_arg, &tmp_str); \
4713 			DATE_A64I((*intobj)->diff->member, ZSTR_VAL(str)); \
4714 			zend_tmp_string_release(tmp_str); \
4715 		} else { \
4716 			(*intobj)->diff->member = -1LL; \
4717 		} \
4718 	} while (0);
4719 
4720 #define PHP_DATE_INTERVAL_READ_PROPERTY_DAYS(member) \
4721 	do { \
4722 		zval *z_arg = zend_hash_str_find(myht, "days", sizeof("days") - 1); \
4723 		if (z_arg && Z_TYPE_P(z_arg) == IS_FALSE) { \
4724 			(*intobj)->diff->member = TIMELIB_UNSET; \
4725 		} else if (z_arg && Z_TYPE_P(z_arg) <= IS_STRING) { \
4726 			zend_string *str = zval_get_string(z_arg); \
4727 			DATE_A64I((*intobj)->diff->member, ZSTR_VAL(str)); \
4728 			zend_string_release(str); \
4729 		} else { \
4730 			(*intobj)->diff->member = -1LL; \
4731 		} \
4732 	} while (0);
4733 
4734 #define PHP_DATE_INTERVAL_READ_PROPERTY_DOUBLE(element, member, def) \
4735 	do { \
4736 		zval *z_arg = zend_hash_str_find(myht, element, sizeof(element) - 1); \
4737 		if (z_arg) { \
4738 			(*intobj)->diff->member = (double)zval_get_double(z_arg); \
4739 		} else { \
4740 			(*intobj)->diff->member = (double)def; \
4741 		} \
4742 	} while (0);
4743 
4744 	PHP_DATE_INTERVAL_READ_PROPERTY("y", y, timelib_sll, -1)
4745 	PHP_DATE_INTERVAL_READ_PROPERTY("m", m, timelib_sll, -1)
4746 	PHP_DATE_INTERVAL_READ_PROPERTY("d", d, timelib_sll, -1)
4747 	PHP_DATE_INTERVAL_READ_PROPERTY("h", h, timelib_sll, -1)
4748 	PHP_DATE_INTERVAL_READ_PROPERTY("i", i, timelib_sll, -1)
4749 	PHP_DATE_INTERVAL_READ_PROPERTY("s", s, timelib_sll, -1)
4750 	{
4751 		zval *z_arg = zend_hash_str_find(myht, "f", sizeof("f") - 1);
4752 		if (z_arg) {
4753 			(*intobj)->diff->us = zend_dval_to_lval(zval_get_double(z_arg) * 1000000.0);
4754 		}
4755 	}
4756 	PHP_DATE_INTERVAL_READ_PROPERTY("weekday", weekday, int, -1)
4757 	PHP_DATE_INTERVAL_READ_PROPERTY("weekday_behavior", weekday_behavior, int, -1)
4758 	PHP_DATE_INTERVAL_READ_PROPERTY("first_last_day_of", first_last_day_of, int, -1)
4759 	PHP_DATE_INTERVAL_READ_PROPERTY("invert", invert, int, 0);
4760 	PHP_DATE_INTERVAL_READ_PROPERTY_DAYS(days);
4761 	PHP_DATE_INTERVAL_READ_PROPERTY("special_type", special.type, unsigned int, 0);
4762 	PHP_DATE_INTERVAL_READ_PROPERTY_I64("special_amount", special.amount);
4763 	PHP_DATE_INTERVAL_READ_PROPERTY("have_weekday_relative", have_weekday_relative, unsigned int, 0);
4764 	PHP_DATE_INTERVAL_READ_PROPERTY("have_special_relative", have_special_relative, unsigned int, 0);
4765 	{
4766 		zval *z_arg = zend_hash_str_find(myht, "civil_or_wall", sizeof("civil_or_wall") - 1);
4767 		(*intobj)->civil_or_wall = PHP_DATE_CIVIL;
4768 		if (z_arg) {
4769 			zend_long val = zval_get_long(z_arg);
4770 			(*intobj)->civil_or_wall = val;
4771 		}
4772 	}
4773 
4774 	(*intobj)->initialized = 1;
4775 } /* }}} */
4776 
4777 /* {{{ */
PHP_METHOD(DateInterval,__set_state)4778 PHP_METHOD(DateInterval, __set_state)
4779 {
4780 	php_interval_obj *intobj;
4781 	zval             *array;
4782 	HashTable        *myht;
4783 
4784 	ZEND_PARSE_PARAMETERS_START(1, 1)
4785 		Z_PARAM_ARRAY(array)
4786 	ZEND_PARSE_PARAMETERS_END();
4787 
4788 	myht = Z_ARRVAL_P(array);
4789 
4790 	php_date_instantiate(date_ce_interval, return_value);
4791 	intobj = Z_PHPINTERVAL_P(return_value);
4792 	php_date_interval_initialize_from_hash(&return_value, &intobj, myht);
4793 }
4794 /* }}} */
4795 
4796 /* {{{ */
PHP_METHOD(DateInterval,__serialize)4797 PHP_METHOD(DateInterval, __serialize)
4798 {
4799 	zval             *object = ZEND_THIS;
4800 	php_interval_obj *intervalobj;
4801 	HashTable        *myht;
4802 
4803 	ZEND_PARSE_PARAMETERS_NONE();
4804 
4805 	intervalobj = Z_PHPINTERVAL_P(object);
4806 	DATE_CHECK_INITIALIZED(intervalobj->initialized, Z_OBJCE_P(object));
4807 
4808 	array_init(return_value);
4809 	myht = Z_ARRVAL_P(return_value);
4810 	date_interval_object_to_hash(intervalobj, myht);
4811 
4812 	add_common_properties(myht, &intervalobj->std);
4813 }
4814 /* }}} */
4815 
date_interval_is_internal_property(zend_string * name)4816 static bool date_interval_is_internal_property(zend_string *name)
4817 {
4818 	if (
4819 		zend_string_equals_literal(name, "date_string") ||
4820 		zend_string_equals_literal(name, "from_string") ||
4821 		zend_string_equals_literal(name, "y") ||
4822 		zend_string_equals_literal(name, "m") ||
4823 		zend_string_equals_literal(name, "d") ||
4824 		zend_string_equals_literal(name, "h") ||
4825 		zend_string_equals_literal(name, "i") ||
4826 		zend_string_equals_literal(name, "s") ||
4827 		zend_string_equals_literal(name, "f") ||
4828 		zend_string_equals_literal(name, "invert") ||
4829 		zend_string_equals_literal(name, "days")
4830 	) {
4831 		return 1;
4832 	}
4833 	return 0;
4834 }
4835 
restore_custom_dateinterval_properties(zval * object,HashTable * myht)4836 static void restore_custom_dateinterval_properties(zval *object, HashTable *myht)
4837 {
4838 	zend_string      *prop_name;
4839 	zval             *prop_val;
4840 
4841 	ZEND_HASH_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) {
4842 		if (!prop_name || (Z_TYPE_P(prop_val) == IS_REFERENCE) || date_interval_is_internal_property(prop_name)) {
4843 			continue;
4844 		}
4845 		update_property(Z_OBJ_P(object), prop_name, prop_val);
4846 	} ZEND_HASH_FOREACH_END();
4847 }
4848 
4849 
4850 /* {{{ */
PHP_METHOD(DateInterval,__unserialize)4851 PHP_METHOD(DateInterval, __unserialize)
4852 {
4853 	zval             *object = ZEND_THIS;
4854 	php_interval_obj *intervalobj;
4855 	zval             *array;
4856 	HashTable        *myht;
4857 
4858 	ZEND_PARSE_PARAMETERS_START(1, 1)
4859 		Z_PARAM_ARRAY(array)
4860 	ZEND_PARSE_PARAMETERS_END();
4861 
4862 	intervalobj = Z_PHPINTERVAL_P(object);
4863 	myht = Z_ARRVAL_P(array);
4864 
4865 	php_date_interval_initialize_from_hash(&object, &intervalobj, myht);
4866 	restore_custom_dateinterval_properties(object, myht);
4867 }
4868 /* }}} */
4869 
4870 /* {{{ */
PHP_METHOD(DateInterval,__wakeup)4871 PHP_METHOD(DateInterval, __wakeup)
4872 {
4873 	zval             *object = ZEND_THIS;
4874 	php_interval_obj *intobj;
4875 	HashTable        *myht;
4876 
4877 	ZEND_PARSE_PARAMETERS_NONE();
4878 
4879 	intobj = Z_PHPINTERVAL_P(object);
4880 
4881 	myht = Z_OBJPROP_P(object);
4882 
4883 	php_date_interval_initialize_from_hash(&return_value, &intobj, myht);
4884 }
4885 /* }}} */
4886 
date_interval_instantiate_from_time(zval * return_value,timelib_time * time,zend_string * time_str)4887 static void date_interval_instantiate_from_time(zval *return_value, timelib_time *time, zend_string *time_str)
4888 {
4889 	php_interval_obj *diobj;
4890 
4891 	php_date_instantiate(date_ce_interval, return_value);
4892 	diobj = Z_PHPINTERVAL_P(return_value);
4893 	diobj->diff = timelib_rel_time_clone(&time->relative);
4894 	diobj->initialized = 1;
4895 	diobj->civil_or_wall = PHP_DATE_CIVIL;
4896 	diobj->from_string = true;
4897 	diobj->date_string = zend_string_copy(time_str);
4898 }
4899 
4900 /* {{{ Uses the normal date parsers and sets up a DateInterval from the relative parts of the parsed string */
PHP_FUNCTION(date_interval_create_from_date_string)4901 PHP_FUNCTION(date_interval_create_from_date_string)
4902 {
4903 	zend_string    *time_str = NULL;
4904 	timelib_time   *time;
4905 	timelib_error_container *err = NULL;
4906 
4907 	ZEND_PARSE_PARAMETERS_START(1, 1)
4908 		Z_PARAM_STR(time_str)
4909 	ZEND_PARSE_PARAMETERS_END();
4910 
4911 	time = timelib_strtotime(ZSTR_VAL(time_str), ZSTR_LEN(time_str), &err, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
4912 
4913 	if (err->error_count > 0)  {
4914 		php_error_docref(NULL, E_WARNING, "Unknown or bad format (%s) at position %d (%c): %s", ZSTR_VAL(time_str),
4915 			err->error_messages[0].position, err->error_messages[0].character ? err->error_messages[0].character : ' ', err->error_messages[0].message);
4916 		RETVAL_FALSE;
4917 		goto cleanup;
4918 	}
4919 
4920 	if (time->have_date || time->have_time || time->have_zone) {
4921 		php_error_docref(NULL, E_WARNING, "String '%s' contains non-relative elements", ZSTR_VAL(time_str));
4922 		RETVAL_FALSE;
4923 		goto cleanup;
4924 	}
4925 
4926 	date_interval_instantiate_from_time(return_value, time, time_str);
4927 
4928 cleanup:
4929 	timelib_time_dtor(time);
4930 	timelib_error_container_dtor(err);
4931 }
4932 /* }}} */
4933 
4934 /* {{{ Uses the normal date parsers and sets up a DateInterval from the relative parts of the parsed string */
PHP_METHOD(DateInterval,createFromDateString)4935 PHP_METHOD(DateInterval, createFromDateString)
4936 {
4937 	zend_string    *time_str = NULL;
4938 	timelib_time   *time;
4939 	timelib_error_container *err = NULL;
4940 
4941 	ZEND_PARSE_PARAMETERS_START(1, 1)
4942 		Z_PARAM_STR(time_str)
4943 	ZEND_PARSE_PARAMETERS_END();
4944 
4945 	time = timelib_strtotime(ZSTR_VAL(time_str), ZSTR_LEN(time_str), &err, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
4946 
4947 	if (err->error_count > 0)  {
4948 		zend_throw_error(date_ce_date_malformed_interval_string_exception, "Unknown or bad format (%s) at position %d (%c): %s", ZSTR_VAL(time_str),
4949 			err->error_messages[0].position, err->error_messages[0].character ? err->error_messages[0].character : ' ', err->error_messages[0].message);
4950 		goto cleanup;
4951 	}
4952 
4953 	if (time->have_date || time->have_time || time->have_zone) {
4954 		zend_throw_error(date_ce_date_malformed_interval_string_exception, "String '%s' contains non-relative elements", ZSTR_VAL(time_str));
4955 		goto cleanup;
4956 	}
4957 
4958 	date_interval_instantiate_from_time(return_value, time, time_str);
4959 
4960 cleanup:
4961 	timelib_time_dtor(time);
4962 	timelib_error_container_dtor(err);
4963 }
4964 /* }}} */
4965 
4966 /* {{{ date_interval_format -  */
date_interval_format(char * format,size_t format_len,timelib_rel_time * t)4967 static zend_string *date_interval_format(char *format, size_t format_len, timelib_rel_time *t)
4968 {
4969 	smart_str            string = {0};
4970 	size_t               i;
4971 	int                  length, have_format_spec = 0;
4972 	char                 buffer[33];
4973 
4974 	if (!format_len) {
4975 		return ZSTR_EMPTY_ALLOC();
4976 	}
4977 
4978 	for (i = 0; i < format_len; i++) {
4979 		if (have_format_spec) {
4980 			switch (format[i]) {
4981 				case 'Y': length = slprintf(buffer, sizeof(buffer), "%02d", (int) t->y); break;
4982 				case 'y': length = slprintf(buffer, sizeof(buffer), "%d", (int) t->y); break;
4983 
4984 				case 'M': length = slprintf(buffer, sizeof(buffer), "%02d", (int) t->m); break;
4985 				case 'm': length = slprintf(buffer, sizeof(buffer), "%d", (int) t->m); break;
4986 
4987 				case 'D': length = slprintf(buffer, sizeof(buffer), "%02d", (int) t->d); break;
4988 				case 'd': length = slprintf(buffer, sizeof(buffer), "%d", (int) t->d); break;
4989 
4990 				case 'H': length = slprintf(buffer, sizeof(buffer), "%02d", (int) t->h); break;
4991 				case 'h': length = slprintf(buffer, sizeof(buffer), "%d", (int) t->h); break;
4992 
4993 				case 'I': length = slprintf(buffer, sizeof(buffer), "%02d", (int) t->i); break;
4994 				case 'i': length = slprintf(buffer, sizeof(buffer), "%d", (int) t->i); break;
4995 
4996 				case 'S': length = slprintf(buffer, sizeof(buffer), "%02" ZEND_LONG_FMT_SPEC, (zend_long) t->s); break;
4997 				case 's': length = slprintf(buffer, sizeof(buffer), ZEND_LONG_FMT, (zend_long) t->s); break;
4998 
4999 				case 'F': length = slprintf(buffer, sizeof(buffer), "%06" ZEND_LONG_FMT_SPEC, (zend_long) t->us); break;
5000 				case 'f': length = slprintf(buffer, sizeof(buffer), ZEND_LONG_FMT, (zend_long) t->us); break;
5001 
5002 				case 'a': {
5003 					if ((int) t->days != TIMELIB_UNSET) {
5004 						length = slprintf(buffer, sizeof(buffer), "%d", (int) t->days);
5005 					} else {
5006 						length = slprintf(buffer, sizeof(buffer), "(unknown)");
5007 					}
5008 				} break;
5009 				case 'r': length = slprintf(buffer, sizeof(buffer), "%s", t->invert ? "-" : ""); break;
5010 				case 'R': length = slprintf(buffer, sizeof(buffer), "%c", t->invert ? '-' : '+'); break;
5011 
5012 				case '%': length = slprintf(buffer, sizeof(buffer), "%%"); break;
5013 				default: buffer[0] = '%'; buffer[1] = format[i]; buffer[2] = '\0'; length = 2; break;
5014 			}
5015 			smart_str_appendl(&string, buffer, length);
5016 			have_format_spec = 0;
5017 		} else {
5018 			if (format[i] == '%') {
5019 				have_format_spec = 1;
5020 			} else {
5021 				smart_str_appendc(&string, format[i]);
5022 			}
5023 		}
5024 	}
5025 
5026 	smart_str_0(&string);
5027 
5028 	if (string.s == NULL) {
5029 		return ZSTR_EMPTY_ALLOC();
5030 	}
5031 
5032 	return string.s;
5033 }
5034 /* }}} */
5035 
5036 /* {{{ Formats the interval. */
PHP_FUNCTION(date_interval_format)5037 PHP_FUNCTION(date_interval_format)
5038 {
5039 	zval             *object;
5040 	php_interval_obj *diobj;
5041 	char             *format;
5042 	size_t            format_len;
5043 
5044 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Os", &object, date_ce_interval, &format, &format_len) == FAILURE) {
5045 		RETURN_THROWS();
5046 	}
5047 	diobj = Z_PHPINTERVAL_P(object);
5048 	DATE_CHECK_INITIALIZED(diobj->initialized, Z_OBJCE_P(object));
5049 
5050 	RETURN_STR(date_interval_format(format, format_len, diobj->diff));
5051 }
5052 /* }}} */
5053 
date_period_initialize(timelib_time ** st,timelib_time ** et,timelib_rel_time ** d,zend_long * recurrences,char * format,size_t format_length)5054 static bool date_period_initialize(timelib_time **st, timelib_time **et, timelib_rel_time **d, zend_long *recurrences, /*const*/ char *format, size_t format_length) /* {{{ */
5055 {
5056 	timelib_time     *b = NULL, *e = NULL;
5057 	timelib_rel_time *p = NULL;
5058 	int               r = 0;
5059 	timelib_error_container *errors;
5060 	bool              retval = false;
5061 
5062 	timelib_strtointerval(format, format_length, &b, &e, &p, &r, &errors);
5063 
5064 	if (errors->error_count > 0) {
5065 		retval = false;
5066 		zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "Unknown or bad format (%s)", format);
5067 		if (b) {
5068 			timelib_time_dtor(b);
5069 		}
5070 		if (e) {
5071 			timelib_time_dtor(e);
5072 		}
5073 		if (p) {
5074 			timelib_rel_time_dtor(p);
5075 		}
5076 	} else {
5077 		*st = b;
5078 		*et = e;
5079 		*d  = p;
5080 		*recurrences = r;
5081 		retval = true;
5082 	}
5083 	timelib_error_container_dtor(errors);
5084 	return retval;
5085 } /* }}} */
5086 
date_period_init_iso8601_string(php_period_obj * dpobj,zend_class_entry * base_ce,char * isostr,size_t isostr_len,zend_long options,zend_long * recurrences)5087 static bool date_period_init_iso8601_string(php_period_obj *dpobj, zend_class_entry* base_ce, char *isostr, size_t isostr_len, zend_long options, zend_long *recurrences)
5088 {
5089 	if (!date_period_initialize(&(dpobj->start), &(dpobj->end), &(dpobj->interval), recurrences, isostr, isostr_len)) {
5090 		return false;
5091 	}
5092 
5093 	if (dpobj->start == NULL) {
5094 		zend_string *func = get_active_function_or_method_name();
5095 		zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): ISO interval must contain a start date, \"%s\" given", ZSTR_VAL(func), isostr);
5096 		zend_string_release(func);
5097 		return false;
5098 	}
5099 	if (dpobj->interval == NULL) {
5100 		zend_string *func = get_active_function_or_method_name();
5101 		zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): ISO interval must contain an interval, \"%s\" given", ZSTR_VAL(func), isostr);
5102 		zend_string_release(func);
5103 		return false;
5104 	}
5105 	if (dpobj->end == NULL && recurrences == 0) {
5106 		zend_string *func = get_active_function_or_method_name();
5107 		zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): ISO interval must contain an end date or a recurrence count, \"%s\" given", ZSTR_VAL(func), isostr);
5108 		zend_string_release(func);
5109 		return false;
5110 	}
5111 
5112 	if (dpobj->start) {
5113 		timelib_update_ts(dpobj->start, NULL);
5114 	}
5115 	if (dpobj->end) {
5116 		timelib_update_ts(dpobj->end, NULL);
5117 	}
5118 	dpobj->start_ce = base_ce;
5119 
5120 	return true;
5121 }
5122 
date_period_init_finish(php_period_obj * dpobj,zend_long options,zend_long recurrences)5123 static bool date_period_init_finish(php_period_obj *dpobj, zend_long options, zend_long recurrences)
5124 {
5125 	if (dpobj->end == NULL && recurrences < 1) {
5126 		zend_string *func = get_active_function_or_method_name();
5127 		zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): Recurrence count must be greater than 0", ZSTR_VAL(func));
5128 		zend_string_release(func);
5129 		return false;
5130 	}
5131 
5132 	/* options */
5133 	dpobj->include_start_date = !(options & PHP_DATE_PERIOD_EXCLUDE_START_DATE);
5134 	dpobj->include_end_date = options & PHP_DATE_PERIOD_INCLUDE_END_DATE;
5135 
5136 	/* recurrrences */
5137 	dpobj->recurrences = recurrences + dpobj->include_start_date + dpobj->include_end_date;
5138 
5139 	dpobj->initialized = 1;
5140 
5141 	initialize_date_period_properties(dpobj);
5142 
5143 	return true;
5144 }
5145 
PHP_METHOD(DatePeriod,createFromISO8601String)5146 PHP_METHOD(DatePeriod, createFromISO8601String)
5147 {
5148 	php_period_obj *dpobj;
5149 	zend_long recurrences = 0, options = 0;
5150 	char *isostr = NULL;
5151 	size_t isostr_len = 0;
5152 
5153 	ZEND_PARSE_PARAMETERS_START(1, 2)
5154 		Z_PARAM_STRING(isostr, isostr_len)
5155 		Z_PARAM_OPTIONAL
5156 		Z_PARAM_LONG(options)
5157 	ZEND_PARSE_PARAMETERS_END();
5158 
5159 	object_init_ex(return_value, execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_period);
5160 	dpobj = Z_PHPPERIOD_P(return_value);
5161 
5162 	dpobj->current = NULL;
5163 
5164 	if (!date_period_init_iso8601_string(dpobj, date_ce_immutable, isostr, isostr_len, options, &recurrences)) {
5165 		RETURN_THROWS();
5166 	}
5167 
5168 	if (!date_period_init_finish(dpobj, options, recurrences)) {
5169 		RETURN_THROWS();
5170 	}
5171 }
5172 
5173 /* {{{ Creates new DatePeriod object. */
PHP_METHOD(DatePeriod,__construct)5174 PHP_METHOD(DatePeriod, __construct)
5175 {
5176 	php_period_obj   *dpobj;
5177 	php_date_obj     *dateobj;
5178 	zval *start, *end = NULL, *interval;
5179 	zend_long  recurrences = 0, options = 0;
5180 	char *isostr = NULL;
5181 	size_t   isostr_len = 0;
5182 	timelib_time *clone;
5183 
5184 	if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "OOl|l", &start, date_ce_interface, &interval, date_ce_interval, &recurrences, &options) == FAILURE) {
5185 		if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "OOO|l", &start, date_ce_interface, &interval, date_ce_interval, &end, date_ce_interface, &options) == FAILURE) {
5186 			if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "s|l", &isostr, &isostr_len, &options) == FAILURE) {
5187 				zend_type_error("DatePeriod::__construct() accepts (DateTimeInterface, DateInterval, int [, int]), or (DateTimeInterface, DateInterval, DateTime [, int]), or (string [, int]) as arguments");
5188 				RETURN_THROWS();
5189 			}
5190 		}
5191 	}
5192 
5193 	dpobj = Z_PHPPERIOD_P(ZEND_THIS);
5194 	dpobj->current = NULL;
5195 
5196 	if (isostr) {
5197 		zend_error(E_DEPRECATED, "Calling DatePeriod::__construct(string $isostr, int $options = 0) is deprecated, "
5198 			"use DatePeriod::createFromISO8601String() instead");
5199 		if (UNEXPECTED(EG(exception))) {
5200 			RETURN_THROWS();
5201 		}
5202 
5203 		if (!date_period_init_iso8601_string(dpobj, date_ce_date, isostr, isostr_len, options, &recurrences)) {
5204 			RETURN_THROWS();
5205 		}
5206 	} else {
5207 		/* check initialisation */
5208 		DATE_CHECK_INITIALIZED(Z_PHPDATE_P(start)->time, date_ce_interface);
5209 		if (end) {
5210 			DATE_CHECK_INITIALIZED(Z_PHPDATE_P(end)->time, date_ce_interface);
5211 		}
5212 
5213 		/* init */
5214 		php_interval_obj *intobj = Z_PHPINTERVAL_P(interval);
5215 
5216 		/* start date */
5217 		dateobj = Z_PHPDATE_P(start);
5218 		clone = timelib_time_ctor();
5219 		memcpy(clone, dateobj->time, sizeof(timelib_time));
5220 		if (dateobj->time->tz_abbr) {
5221 			clone->tz_abbr = timelib_strdup(dateobj->time->tz_abbr);
5222 		}
5223 		if (dateobj->time->tz_info) {
5224 			clone->tz_info = dateobj->time->tz_info;
5225 		}
5226 		dpobj->start = clone;
5227 		dpobj->start_ce = Z_OBJCE_P(start);
5228 
5229 		/* interval */
5230 		dpobj->interval = timelib_rel_time_clone(intobj->diff);
5231 
5232 		/* end date */
5233 		if (end) {
5234 			dateobj = Z_PHPDATE_P(end);
5235 			clone = timelib_time_clone(dateobj->time);
5236 			dpobj->end = clone;
5237 		}
5238 	}
5239 
5240 	if (!date_period_init_finish(dpobj, options, recurrences)) {
5241 		RETURN_THROWS();
5242 	}
5243 }
5244 /* }}} */
5245 
5246 /* {{{ Get start date. */
PHP_METHOD(DatePeriod,getStartDate)5247 PHP_METHOD(DatePeriod, getStartDate)
5248 {
5249 	php_period_obj   *dpobj;
5250 	php_date_obj     *dateobj;
5251 
5252 	ZEND_PARSE_PARAMETERS_NONE();
5253 
5254 	dpobj = Z_PHPPERIOD_P(ZEND_THIS);
5255 	DATE_CHECK_INITIALIZED(dpobj->start, Z_OBJCE_P(ZEND_THIS));
5256 
5257 	php_date_instantiate(dpobj->start_ce, return_value);
5258 	dateobj = Z_PHPDATE_P(return_value);
5259 	dateobj->time = timelib_time_ctor();
5260 	*dateobj->time = *dpobj->start;
5261 	if (dpobj->start->tz_abbr) {
5262 		dateobj->time->tz_abbr = timelib_strdup(dpobj->start->tz_abbr);
5263 	}
5264 	if (dpobj->start->tz_info) {
5265 		dateobj->time->tz_info = dpobj->start->tz_info;
5266 	}
5267 }
5268 /* }}} */
5269 
5270 /* {{{ Get end date. */
PHP_METHOD(DatePeriod,getEndDate)5271 PHP_METHOD(DatePeriod, getEndDate)
5272 {
5273 	php_period_obj   *dpobj;
5274 	php_date_obj     *dateobj;
5275 
5276 	ZEND_PARSE_PARAMETERS_NONE();
5277 
5278 	dpobj = Z_PHPPERIOD_P(ZEND_THIS);
5279 
5280 	if (!dpobj->end) {
5281 		return;
5282 	}
5283 
5284 	php_date_instantiate(dpobj->start_ce, return_value);
5285 	dateobj = Z_PHPDATE_P(return_value);
5286 	dateobj->time = timelib_time_ctor();
5287 	*dateobj->time = *dpobj->end;
5288 	if (dpobj->end->tz_abbr) {
5289 			dateobj->time->tz_abbr = timelib_strdup(dpobj->end->tz_abbr);
5290 	}
5291 	if (dpobj->end->tz_info) {
5292 			dateobj->time->tz_info = dpobj->end->tz_info;
5293 	}
5294 }
5295 /* }}} */
5296 
5297 /* {{{ Get date interval. */
PHP_METHOD(DatePeriod,getDateInterval)5298 PHP_METHOD(DatePeriod, getDateInterval)
5299 {
5300 	php_period_obj   *dpobj;
5301 	php_interval_obj *diobj;
5302 
5303 	ZEND_PARSE_PARAMETERS_NONE();
5304 
5305 	dpobj = Z_PHPPERIOD_P(ZEND_THIS);
5306 	DATE_CHECK_INITIALIZED(dpobj->interval, Z_OBJCE_P(ZEND_THIS));
5307 
5308 	php_date_instantiate(date_ce_interval, return_value);
5309 	diobj = Z_PHPINTERVAL_P(return_value);
5310 	diobj->diff = timelib_rel_time_clone(dpobj->interval);
5311 	diobj->initialized = 1;
5312 }
5313 /* }}} */
5314 
5315 /* {{{ Get recurrences. */
PHP_METHOD(DatePeriod,getRecurrences)5316 PHP_METHOD(DatePeriod, getRecurrences)
5317 {
5318 	php_period_obj   *dpobj;
5319 
5320 	ZEND_PARSE_PARAMETERS_NONE();
5321 
5322 	dpobj = Z_PHPPERIOD_P(ZEND_THIS);
5323 
5324 	if (0 == dpobj->recurrences - dpobj->include_start_date - dpobj->include_end_date) {
5325 		return;
5326 	}
5327 
5328 	RETURN_LONG(dpobj->recurrences - dpobj->include_start_date - dpobj->include_end_date);
5329 }
5330 /* }}} */
5331 
PHP_METHOD(DatePeriod,getIterator)5332 PHP_METHOD(DatePeriod, getIterator)
5333 {
5334 	ZEND_PARSE_PARAMETERS_NONE();
5335 
5336 	zend_create_internal_iterator_zval(return_value, ZEND_THIS);
5337 }
5338 
check_id_allowed(char * id,zend_long what)5339 static int check_id_allowed(char *id, zend_long what) /* {{{ */
5340 {
5341 	if ((what & PHP_DATE_TIMEZONE_GROUP_AFRICA)     && strncasecmp(id, "Africa/",      7) == 0) return 1;
5342 	if ((what & PHP_DATE_TIMEZONE_GROUP_AMERICA)    && strncasecmp(id, "America/",     8) == 0) return 1;
5343 	if ((what & PHP_DATE_TIMEZONE_GROUP_ANTARCTICA) && strncasecmp(id, "Antarctica/", 11) == 0) return 1;
5344 	if ((what & PHP_DATE_TIMEZONE_GROUP_ARCTIC)     && strncasecmp(id, "Arctic/",      7) == 0) return 1;
5345 	if ((what & PHP_DATE_TIMEZONE_GROUP_ASIA)       && strncasecmp(id, "Asia/",        5) == 0) return 1;
5346 	if ((what & PHP_DATE_TIMEZONE_GROUP_ATLANTIC)   && strncasecmp(id, "Atlantic/",    9) == 0) return 1;
5347 	if ((what & PHP_DATE_TIMEZONE_GROUP_AUSTRALIA)  && strncasecmp(id, "Australia/",  10) == 0) return 1;
5348 	if ((what & PHP_DATE_TIMEZONE_GROUP_EUROPE)     && strncasecmp(id, "Europe/",      7) == 0) return 1;
5349 	if ((what & PHP_DATE_TIMEZONE_GROUP_INDIAN)     && strncasecmp(id, "Indian/",      7) == 0) return 1;
5350 	if ((what & PHP_DATE_TIMEZONE_GROUP_PACIFIC)    && strncasecmp(id, "Pacific/",     8) == 0) return 1;
5351 	if ((what & PHP_DATE_TIMEZONE_GROUP_UTC)        && strncasecmp(id, "UTC",          3) == 0) return 1;
5352 	return 0;
5353 } /* }}} */
5354 
5355 /* {{{ Returns numerically index array with all timezone identifiers. */
PHP_FUNCTION(timezone_identifiers_list)5356 PHP_FUNCTION(timezone_identifiers_list)
5357 {
5358 	const timelib_tzdb             *tzdb;
5359 	const timelib_tzdb_index_entry *table;
5360 	int                             i, item_count;
5361 	zend_long                       what = PHP_DATE_TIMEZONE_GROUP_ALL;
5362 	char                           *option = NULL;
5363 	size_t                          option_len = 0;
5364 
5365 	ZEND_PARSE_PARAMETERS_START(0, 2)
5366 		Z_PARAM_OPTIONAL
5367 		Z_PARAM_LONG(what)
5368 		Z_PARAM_STRING_OR_NULL(option, option_len)
5369 	ZEND_PARSE_PARAMETERS_END();
5370 
5371 	/* Extra validation */
5372 	if (what == PHP_DATE_TIMEZONE_PER_COUNTRY && option_len != 2) {
5373 		zend_argument_value_error(2, "must be a two-letter ISO 3166-1 compatible country code "
5374 			"when argument #1 ($timezoneGroup) is DateTimeZone::PER_COUNTRY");
5375 		RETURN_THROWS();
5376 	}
5377 
5378 	tzdb = DATE_TIMEZONEDB;
5379 	table = timelib_timezone_identifiers_list((timelib_tzdb*) tzdb, &item_count);
5380 
5381 	array_init(return_value);
5382 
5383 	for (i = 0; i < item_count; ++i) {
5384 		if (what == PHP_DATE_TIMEZONE_PER_COUNTRY) {
5385 			if (tzdb->data[table[i].pos + 5] == option[0] && tzdb->data[table[i].pos + 6] == option[1]) {
5386 				add_next_index_string(return_value, table[i].id);
5387 			}
5388 		} else if (what == PHP_DATE_TIMEZONE_GROUP_ALL_W_BC || (check_id_allowed(table[i].id, what) && (tzdb->data[table[i].pos + 4] == '\1'))) {
5389 			add_next_index_string(return_value, table[i].id);
5390 		}
5391 	};
5392 }
5393 /* }}} */
5394 
5395 /* {{{ Returns the Olson database version number. */
PHP_FUNCTION(timezone_version_get)5396 PHP_FUNCTION(timezone_version_get)
5397 {
5398 	const timelib_tzdb *tzdb;
5399 
5400 	ZEND_PARSE_PARAMETERS_NONE();
5401 
5402 	tzdb = DATE_TIMEZONEDB;
5403 	RETURN_STRING(tzdb->version);
5404 }
5405 /* }}} */
5406 
5407 /* {{{ Returns associative array containing dst, offset and the timezone name */
PHP_FUNCTION(timezone_abbreviations_list)5408 PHP_FUNCTION(timezone_abbreviations_list)
5409 {
5410 	const timelib_tz_lookup_table *table, *entry;
5411 	zval                          element, *abbr_array_p, abbr_array;
5412 
5413 	ZEND_PARSE_PARAMETERS_NONE();
5414 
5415 	table = timelib_timezone_abbreviations_list();
5416 	array_init(return_value);
5417 	entry = table;
5418 
5419 	do {
5420 		array_init(&element);
5421 		add_assoc_bool_ex(&element, "dst", sizeof("dst") -1, entry->type);
5422 		add_assoc_long_ex(&element, "offset", sizeof("offset") - 1, entry->gmtoffset);
5423 		if (entry->full_tz_name) {
5424 			add_assoc_string_ex(&element, "timezone_id", sizeof("timezone_id") - 1, entry->full_tz_name);
5425 		} else {
5426 			add_assoc_null_ex(&element, "timezone_id", sizeof("timezone_id") - 1);
5427 		}
5428 
5429 		abbr_array_p = zend_hash_str_find(Z_ARRVAL_P(return_value), entry->name, strlen(entry->name));
5430 		if (!abbr_array_p) {
5431 			array_init(&abbr_array);
5432 			add_assoc_zval(return_value, entry->name, &abbr_array);
5433 		} else {
5434 			ZVAL_COPY_VALUE(&abbr_array, abbr_array_p);
5435 		}
5436 		add_next_index_zval(&abbr_array, &element);
5437 		entry++;
5438 	} while (entry->name);
5439 }
5440 /* }}} */
5441 
5442 /* {{{ Sets the default timezone used by all date/time functions in a script */
PHP_FUNCTION(date_default_timezone_set)5443 PHP_FUNCTION(date_default_timezone_set)
5444 {
5445 	char *zone;
5446 	size_t   zone_len;
5447 
5448 	ZEND_PARSE_PARAMETERS_START(1, 1)
5449 		Z_PARAM_STRING(zone, zone_len)
5450 	ZEND_PARSE_PARAMETERS_END();
5451 
5452 	if (!timelib_timezone_id_is_valid(zone, DATE_TIMEZONEDB)) {
5453 		php_error_docref(NULL, E_NOTICE, "Timezone ID '%s' is invalid", zone);
5454 		RETURN_FALSE;
5455 	}
5456 	if (DATEG(timezone)) {
5457 		efree(DATEG(timezone));
5458 		DATEG(timezone) = NULL;
5459 	}
5460 	DATEG(timezone) = estrndup(zone, zone_len);
5461 	RETURN_TRUE;
5462 }
5463 /* }}} */
5464 
5465 /* {{{ Gets the default timezone used by all date/time functions in a script */
PHP_FUNCTION(date_default_timezone_get)5466 PHP_FUNCTION(date_default_timezone_get)
5467 {
5468 	timelib_tzinfo *default_tz;
5469 	ZEND_PARSE_PARAMETERS_NONE();
5470 
5471 	default_tz = get_timezone_info();
5472 	if (!default_tz) {
5473 		RETURN_THROWS();
5474 	}
5475 	RETVAL_STRING(default_tz->name);
5476 }
5477 /* }}} */
5478 
5479 /* {{{ php_do_date_sunrise_sunset
5480  *  Common for date_sunrise() and date_sunset() functions
5481  */
php_do_date_sunrise_sunset(INTERNAL_FUNCTION_PARAMETERS,bool calc_sunset)5482 static void php_do_date_sunrise_sunset(INTERNAL_FUNCTION_PARAMETERS, bool calc_sunset)
5483 {
5484 	double latitude, longitude, zenith, gmt_offset, altitude;
5485 	bool latitude_is_null = 1, longitude_is_null = 1, zenith_is_null = 1, gmt_offset_is_null = 1;
5486 	double h_rise, h_set, N;
5487 	timelib_sll rise, set, transit;
5488 	zend_long time, retformat = SUNFUNCS_RET_STRING;
5489 	int             rs;
5490 	timelib_time   *t;
5491 	timelib_tzinfo *tzi;
5492 	zend_string    *retstr;
5493 
5494 	ZEND_PARSE_PARAMETERS_START(1, 6)
5495 		Z_PARAM_LONG(time)
5496 		Z_PARAM_OPTIONAL
5497 		Z_PARAM_LONG(retformat)
5498 		Z_PARAM_DOUBLE_OR_NULL(latitude, latitude_is_null)
5499 		Z_PARAM_DOUBLE_OR_NULL(longitude, longitude_is_null)
5500 		Z_PARAM_DOUBLE_OR_NULL(zenith, zenith_is_null)
5501 		Z_PARAM_DOUBLE_OR_NULL(gmt_offset, gmt_offset_is_null)
5502 	ZEND_PARSE_PARAMETERS_END();
5503 
5504 	if (latitude_is_null) {
5505 		latitude = INI_FLT("date.default_latitude");
5506 	}
5507 
5508 	if (longitude_is_null) {
5509 		longitude = INI_FLT("date.default_longitude");
5510 	}
5511 
5512 	if (zenith_is_null) {
5513 		if (calc_sunset) {
5514 			zenith = INI_FLT("date.sunset_zenith");
5515 		} else {
5516 			zenith = INI_FLT("date.sunrise_zenith");
5517 		}
5518 	}
5519 
5520 	if (retformat != SUNFUNCS_RET_TIMESTAMP &&
5521 		retformat != SUNFUNCS_RET_STRING &&
5522 		retformat != SUNFUNCS_RET_DOUBLE)
5523 	{
5524 		zend_argument_value_error(2, "must be one of SUNFUNCS_RET_TIMESTAMP, SUNFUNCS_RET_STRING, or SUNFUNCS_RET_DOUBLE");
5525 		RETURN_THROWS();
5526 	}
5527 	altitude = 90 - zenith;
5528 
5529 	if (!zend_finite(latitude) || !zend_finite(longitude)) {
5530 		RETURN_FALSE;
5531 	}
5532 
5533 	/* Initialize time struct */
5534 	tzi = get_timezone_info();
5535 	if (!tzi) {
5536 		RETURN_THROWS();
5537 	}
5538 	t = timelib_time_ctor();
5539 	t->tz_info = tzi;
5540 	t->zone_type = TIMELIB_ZONETYPE_ID;
5541 
5542 	if (gmt_offset_is_null) {
5543 		gmt_offset = timelib_get_current_offset(t) / 3600;
5544 	}
5545 
5546 	timelib_unixtime2local(t, time);
5547 	rs = timelib_astro_rise_set_altitude(t, longitude, latitude, altitude, 1, &h_rise, &h_set, &rise, &set, &transit);
5548 	timelib_time_dtor(t);
5549 
5550 	if (rs != 0) {
5551 		RETURN_FALSE;
5552 	}
5553 
5554 	if (retformat == SUNFUNCS_RET_TIMESTAMP) {
5555 		RETURN_LONG(calc_sunset ? set : rise);
5556 	}
5557 	N = (calc_sunset ? h_set : h_rise) + gmt_offset;
5558 
5559 	if (N > 24 || N < 0) {
5560 		N -= floor(N / 24) * 24;
5561 	}
5562 	if (N > 24 || N < 0) {
5563 		RETURN_FALSE;
5564 	}
5565 
5566 	switch (retformat) {
5567 		case SUNFUNCS_RET_STRING:
5568 			retstr = strpprintf(0, "%02d:%02d", (int) N, (int) (60 * (N - (int) N)));
5569 			RETURN_NEW_STR(retstr);
5570 			break;
5571 		case SUNFUNCS_RET_DOUBLE:
5572 			RETURN_DOUBLE(N);
5573 			break;
5574 	}
5575 }
5576 /* }}} */
5577 
5578 /* {{{ Returns time of sunrise for a given day and location */
PHP_FUNCTION(date_sunrise)5579 PHP_FUNCTION(date_sunrise)
5580 {
5581 	php_do_date_sunrise_sunset(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
5582 }
5583 /* }}} */
5584 
5585 /* {{{ Returns time of sunset for a given day and location */
PHP_FUNCTION(date_sunset)5586 PHP_FUNCTION(date_sunset)
5587 {
5588 	php_do_date_sunrise_sunset(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
5589 }
5590 /* }}} */
5591 
5592 /* {{{ Returns an array with information about sun set/rise and twilight begin/end */
PHP_FUNCTION(date_sun_info)5593 PHP_FUNCTION(date_sun_info)
5594 {
5595 	zend_long       time;
5596 	double          latitude, longitude;
5597 	timelib_time   *t, *t2;
5598 	timelib_tzinfo *tzi;
5599 	int             rs;
5600 	timelib_sll     rise, set, transit;
5601 	int             dummy;
5602 	double          ddummy;
5603 
5604 	ZEND_PARSE_PARAMETERS_START(3, 3)
5605 		Z_PARAM_LONG(time)
5606 		Z_PARAM_DOUBLE(latitude)
5607 		Z_PARAM_DOUBLE(longitude)
5608 	ZEND_PARSE_PARAMETERS_END();
5609 
5610 	if (!zend_finite(latitude)) {
5611 		zend_argument_value_error(2, "must be finite");
5612 		RETURN_THROWS();
5613 	}
5614 	if (!zend_finite(longitude)) {
5615 		zend_argument_value_error(3, "must be finite");
5616 		RETURN_THROWS();
5617 	}
5618 
5619 	/* Initialize time struct */
5620 	tzi = get_timezone_info();
5621 	if (!tzi) {
5622 		RETURN_THROWS();
5623 	}
5624 	t = timelib_time_ctor();
5625 	t->tz_info = tzi;
5626 	t->zone_type = TIMELIB_ZONETYPE_ID;
5627 	timelib_unixtime2local(t, time);
5628 
5629 	/* Setup */
5630 	t2 = timelib_time_ctor();
5631 	array_init(return_value);
5632 
5633 	/* Get sun up/down and transit */
5634 	rs = timelib_astro_rise_set_altitude(t, longitude, latitude, -50.0/60, 1, &ddummy, &ddummy, &rise, &set, &transit);
5635 	switch (rs) {
5636 		case -1: /* always below */
5637 			add_assoc_bool(return_value, "sunrise", 0);
5638 			add_assoc_bool(return_value, "sunset", 0);
5639 			break;
5640 		case 1: /* always above */
5641 			add_assoc_bool(return_value, "sunrise", 1);
5642 			add_assoc_bool(return_value, "sunset", 1);
5643 			break;
5644 		default:
5645 			t2->sse = rise;
5646 			add_assoc_long(return_value, "sunrise", timelib_date_to_int(t2, &dummy));
5647 			t2->sse = set;
5648 			add_assoc_long(return_value, "sunset", timelib_date_to_int(t2, &dummy));
5649 	}
5650 	t2->sse = transit;
5651 	add_assoc_long(return_value, "transit", timelib_date_to_int(t2, &dummy));
5652 
5653 	/* Get civil twilight */
5654 	rs = timelib_astro_rise_set_altitude(t, longitude, latitude, -6.0, 0, &ddummy, &ddummy, &rise, &set, &transit);
5655 	switch (rs) {
5656 		case -1: /* always below */
5657 			add_assoc_bool(return_value, "civil_twilight_begin", 0);
5658 			add_assoc_bool(return_value, "civil_twilight_end", 0);
5659 			break;
5660 		case 1: /* always above */
5661 			add_assoc_bool(return_value, "civil_twilight_begin", 1);
5662 			add_assoc_bool(return_value, "civil_twilight_end", 1);
5663 			break;
5664 		default:
5665 			t2->sse = rise;
5666 			add_assoc_long(return_value, "civil_twilight_begin", timelib_date_to_int(t2, &dummy));
5667 			t2->sse = set;
5668 			add_assoc_long(return_value, "civil_twilight_end", timelib_date_to_int(t2, &dummy));
5669 	}
5670 
5671 	/* Get nautical twilight */
5672 	rs = timelib_astro_rise_set_altitude(t, longitude, latitude, -12.0, 0, &ddummy, &ddummy, &rise, &set, &transit);
5673 	switch (rs) {
5674 		case -1: /* always below */
5675 			add_assoc_bool(return_value, "nautical_twilight_begin", 0);
5676 			add_assoc_bool(return_value, "nautical_twilight_end", 0);
5677 			break;
5678 		case 1: /* always above */
5679 			add_assoc_bool(return_value, "nautical_twilight_begin", 1);
5680 			add_assoc_bool(return_value, "nautical_twilight_end", 1);
5681 			break;
5682 		default:
5683 			t2->sse = rise;
5684 			add_assoc_long(return_value, "nautical_twilight_begin", timelib_date_to_int(t2, &dummy));
5685 			t2->sse = set;
5686 			add_assoc_long(return_value, "nautical_twilight_end", timelib_date_to_int(t2, &dummy));
5687 	}
5688 
5689 	/* Get astronomical twilight */
5690 	rs = timelib_astro_rise_set_altitude(t, longitude, latitude, -18.0, 0, &ddummy, &ddummy, &rise, &set, &transit);
5691 	switch (rs) {
5692 		case -1: /* always below */
5693 			add_assoc_bool(return_value, "astronomical_twilight_begin", 0);
5694 			add_assoc_bool(return_value, "astronomical_twilight_end", 0);
5695 			break;
5696 		case 1: /* always above */
5697 			add_assoc_bool(return_value, "astronomical_twilight_begin", 1);
5698 			add_assoc_bool(return_value, "astronomical_twilight_end", 1);
5699 			break;
5700 		default:
5701 			t2->sse = rise;
5702 			add_assoc_long(return_value, "astronomical_twilight_begin", timelib_date_to_int(t2, &dummy));
5703 			t2->sse = set;
5704 			add_assoc_long(return_value, "astronomical_twilight_end", timelib_date_to_int(t2, &dummy));
5705 	}
5706 	timelib_time_dtor(t);
5707 	timelib_time_dtor(t2);
5708 }
5709 /* }}} */
5710 
date_object_get_gc_period(zend_object * object,zval ** table,int * n)5711 static HashTable *date_object_get_gc_period(zend_object *object, zval **table, int *n) /* {{{ */
5712 {
5713 	*table = NULL;
5714 	*n = 0;
5715 	return zend_std_get_properties(object);
5716 } /* }}} */
5717 
date_period_object_to_hash(php_period_obj * period_obj,HashTable * props)5718 static void date_period_object_to_hash(php_period_obj *period_obj, HashTable *props)
5719 {
5720 	zval zv;
5721 
5722 	create_date_period_datetime(period_obj->start, period_obj->start_ce, &zv);
5723 	zend_hash_str_update(props, "start", sizeof("start")-1, &zv);
5724 
5725 	create_date_period_datetime(period_obj->current, period_obj->start_ce, &zv);
5726 	zend_hash_str_update(props, "current", sizeof("current")-1, &zv);
5727 
5728 	create_date_period_datetime(period_obj->end, period_obj->start_ce, &zv);
5729 	zend_hash_str_update(props, "end", sizeof("end")-1, &zv);
5730 
5731 	create_date_period_interval(period_obj->interval, &zv);
5732 	zend_hash_str_update(props, "interval", sizeof("interval")-1, &zv);
5733 
5734 	/* converted to larger type (int->long); must check when unserializing */
5735 	ZVAL_LONG(&zv, (zend_long) period_obj->recurrences);
5736 	zend_hash_str_update(props, "recurrences", sizeof("recurrences")-1, &zv);
5737 
5738 	ZVAL_BOOL(&zv, period_obj->include_start_date);
5739 	zend_hash_str_update(props, "include_start_date", sizeof("include_start_date")-1, &zv);
5740 
5741 	ZVAL_BOOL(&zv, period_obj->include_end_date);
5742 	zend_hash_str_update(props, "include_end_date", sizeof("include_end_date")-1, &zv);
5743 }
5744 
php_date_period_initialize_from_hash(php_period_obj * period_obj,HashTable * myht)5745 static bool php_date_period_initialize_from_hash(php_period_obj *period_obj, HashTable *myht) /* {{{ */
5746 {
5747 	zval *ht_entry;
5748 
5749 	/* this function does no rollback on error */
5750 
5751 	ht_entry = zend_hash_str_find(myht, "start", sizeof("start")-1);
5752 	if (ht_entry) {
5753 		if (Z_TYPE_P(ht_entry) == IS_OBJECT && instanceof_function(Z_OBJCE_P(ht_entry), date_ce_interface)) {
5754 			php_date_obj *date_obj;
5755 			date_obj = Z_PHPDATE_P(ht_entry);
5756 
5757 			if (!date_obj->time) {
5758 				return 0;
5759 			}
5760 
5761 			if (period_obj->start != NULL) {
5762 				timelib_time_dtor(period_obj->start);
5763 			}
5764 			period_obj->start = timelib_time_clone(date_obj->time);
5765 			period_obj->start_ce = Z_OBJCE_P(ht_entry);
5766 		} else if (Z_TYPE_P(ht_entry) != IS_NULL) {
5767 			return 0;
5768 		}
5769 	} else {
5770 		return 0;
5771 	}
5772 
5773 	ht_entry = zend_hash_str_find(myht, "end", sizeof("end")-1);
5774 	if (ht_entry) {
5775 		if (Z_TYPE_P(ht_entry) == IS_OBJECT && instanceof_function(Z_OBJCE_P(ht_entry), date_ce_interface)) {
5776 			php_date_obj *date_obj;
5777 			date_obj = Z_PHPDATE_P(ht_entry);
5778 
5779 			if (!date_obj->time) {
5780 				return 0;
5781 			}
5782 
5783 			if (period_obj->end != NULL) {
5784 				timelib_time_dtor(period_obj->end);
5785 			}
5786 			period_obj->end = timelib_time_clone(date_obj->time);
5787 		} else if (Z_TYPE_P(ht_entry) != IS_NULL) {
5788 			return 0;
5789 		}
5790 	} else {
5791 		return 0;
5792 	}
5793 
5794 	ht_entry = zend_hash_str_find(myht, "current", sizeof("current")-1);
5795 	if (ht_entry) {
5796 		if (Z_TYPE_P(ht_entry) == IS_OBJECT && instanceof_function(Z_OBJCE_P(ht_entry), date_ce_interface)) {
5797 			php_date_obj *date_obj;
5798 			date_obj = Z_PHPDATE_P(ht_entry);
5799 
5800 			if (!date_obj->time) {
5801 				return 0;
5802 			}
5803 
5804 			if (period_obj->current != NULL) {
5805 				timelib_time_dtor(period_obj->current);
5806 			}
5807 			period_obj->current = timelib_time_clone(date_obj->time);
5808 		} else if (Z_TYPE_P(ht_entry) != IS_NULL)  {
5809 			return 0;
5810 		}
5811 	} else {
5812 		return 0;
5813 	}
5814 
5815 	ht_entry = zend_hash_str_find(myht, "interval", sizeof("interval")-1);
5816 	if (ht_entry) {
5817 		if (Z_TYPE_P(ht_entry) == IS_OBJECT && Z_OBJCE_P(ht_entry) == date_ce_interval) {
5818 			php_interval_obj *interval_obj;
5819 			interval_obj = Z_PHPINTERVAL_P(ht_entry);
5820 
5821 			if (!interval_obj->initialized) {
5822 				return 0;
5823 			}
5824 
5825 			if (period_obj->interval != NULL) {
5826 				timelib_rel_time_dtor(period_obj->interval);
5827 			}
5828 			period_obj->interval = timelib_rel_time_clone(interval_obj->diff);
5829 		} else { /* interval is required */
5830 			return 0;
5831 		}
5832 	} else {
5833 		return 0;
5834 	}
5835 
5836 	ht_entry = zend_hash_str_find(myht, "recurrences", sizeof("recurrences")-1);
5837 	if (ht_entry &&
5838 			Z_TYPE_P(ht_entry) == IS_LONG && Z_LVAL_P(ht_entry) >= 0 && Z_LVAL_P(ht_entry) <= INT_MAX) {
5839 		period_obj->recurrences = Z_LVAL_P(ht_entry);
5840 	} else {
5841 		return 0;
5842 	}
5843 
5844 	ht_entry = zend_hash_str_find(myht, "include_start_date", sizeof("include_start_date")-1);
5845 	if (ht_entry &&
5846 			(Z_TYPE_P(ht_entry) == IS_FALSE || Z_TYPE_P(ht_entry) == IS_TRUE)) {
5847 		period_obj->include_start_date = (Z_TYPE_P(ht_entry) == IS_TRUE);
5848 	} else {
5849 		return 0;
5850 	}
5851 
5852 	ht_entry = zend_hash_str_find(myht, "include_end_date", sizeof("include_end_date")-1);
5853 	if (ht_entry &&
5854 			(Z_TYPE_P(ht_entry) == IS_FALSE || Z_TYPE_P(ht_entry) == IS_TRUE)) {
5855 		period_obj->include_end_date = (Z_TYPE_P(ht_entry) == IS_TRUE);
5856 	} else {
5857 		return 0;
5858 	}
5859 
5860 	period_obj->initialized = 1;
5861 
5862 	initialize_date_period_properties(period_obj);
5863 
5864 	return 1;
5865 } /* }}} */
5866 
5867 /* {{{ */
PHP_METHOD(DatePeriod,__set_state)5868 PHP_METHOD(DatePeriod, __set_state)
5869 {
5870 	php_period_obj   *period_obj;
5871 	zval             *array;
5872 	HashTable        *myht;
5873 
5874 	ZEND_PARSE_PARAMETERS_START(1, 1)
5875 		Z_PARAM_ARRAY(array)
5876 	ZEND_PARSE_PARAMETERS_END();
5877 
5878 	myht = Z_ARRVAL_P(array);
5879 
5880 	object_init_ex(return_value, date_ce_period);
5881 	period_obj = Z_PHPPERIOD_P(return_value);
5882 	if (!php_date_period_initialize_from_hash(period_obj, myht)) {
5883 		zend_throw_error(NULL, "Invalid serialization data for DatePeriod object");
5884 		RETURN_THROWS();
5885 	}
5886 }
5887 /* }}} */
5888 
5889 /* {{{ */
PHP_METHOD(DatePeriod,__serialize)5890 PHP_METHOD(DatePeriod, __serialize)
5891 {
5892 	zval             *object = ZEND_THIS;
5893 	php_period_obj   *period_obj;
5894 	HashTable        *myht;
5895 
5896 	ZEND_PARSE_PARAMETERS_NONE();
5897 
5898 	period_obj = Z_PHPPERIOD_P(object);
5899 	DATE_CHECK_INITIALIZED(period_obj->start, Z_OBJCE_P(object));
5900 
5901 	array_init(return_value);
5902 	myht = Z_ARRVAL_P(return_value);
5903 	date_period_object_to_hash(period_obj, myht);
5904 
5905 	add_common_properties(myht, &period_obj->std);
5906 }
5907 /* }}} */
5908 
5909 /* {{{ date_period_is_internal_property
5910  *  Common for date_period_read_property(), date_period_write_property(), and
5911  *  restore_custom_dateperiod_properties functions
5912  */
date_period_is_internal_property(zend_string * name)5913 static bool date_period_is_internal_property(zend_string *name)
5914 {
5915 	if (
5916 		zend_string_equals_literal(name, "start") ||
5917 		zend_string_equals_literal(name, "current") ||
5918 		zend_string_equals_literal(name, "end") ||
5919 		zend_string_equals_literal(name, "interval") ||
5920 		zend_string_equals_literal(name, "recurrences") ||
5921 		zend_string_equals_literal(name, "include_start_date") ||
5922 		zend_string_equals_literal(name, "include_end_date")
5923 	) {
5924 		return 1;
5925 	}
5926 	return 0;
5927 }
5928 /* }}} */
5929 
restore_custom_dateperiod_properties(zval * object,HashTable * myht)5930 static void restore_custom_dateperiod_properties(zval *object, HashTable *myht)
5931 {
5932 	zend_string      *prop_name;
5933 	zval             *prop_val;
5934 
5935 	ZEND_HASH_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) {
5936 		if (!prop_name || (Z_TYPE_P(prop_val) == IS_REFERENCE) || date_period_is_internal_property(prop_name)) {
5937 			continue;
5938 		}
5939 		update_property(Z_OBJ_P(object), prop_name, prop_val);
5940 	} ZEND_HASH_FOREACH_END();
5941 }
5942 
5943 /* {{{ */
PHP_METHOD(DatePeriod,__unserialize)5944 PHP_METHOD(DatePeriod, __unserialize)
5945 {
5946 	zval             *object = ZEND_THIS;
5947 	php_period_obj   *period_obj;
5948 	zval             *array;
5949 	HashTable        *myht;
5950 
5951 	ZEND_PARSE_PARAMETERS_START(1, 1)
5952 		Z_PARAM_ARRAY(array)
5953 	ZEND_PARSE_PARAMETERS_END();
5954 
5955 	period_obj = Z_PHPPERIOD_P(object);
5956 	myht = Z_ARRVAL_P(array);
5957 
5958 	if (!php_date_period_initialize_from_hash(period_obj, myht)) {
5959 		zend_throw_error(NULL, "Invalid serialization data for DatePeriod object");
5960 		RETURN_THROWS();
5961 	}
5962 	restore_custom_dateperiod_properties(object, myht);
5963 }
5964 /* }}} */
5965 
5966 /* {{{ */
PHP_METHOD(DatePeriod,__wakeup)5967 PHP_METHOD(DatePeriod, __wakeup)
5968 {
5969 	zval             *object = ZEND_THIS;
5970 	php_period_obj   *period_obj;
5971 	HashTable        *myht;
5972 
5973 	ZEND_PARSE_PARAMETERS_NONE();
5974 
5975 	period_obj = Z_PHPPERIOD_P(object);
5976 
5977 	myht = Z_OBJPROP_P(object);
5978 
5979 	if (!php_date_period_initialize_from_hash(period_obj, myht)) {
5980 		zend_throw_error(NULL, "Invalid serialization data for DatePeriod object");
5981 		RETURN_THROWS();
5982 	}
5983 }
5984 /* }}} */
5985 
5986 /* {{{ date_period_read_property */
date_period_read_property(zend_object * object,zend_string * name,int type,void ** cache_slot,zval * rv)5987 static zval *date_period_read_property(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv)
5988 {
5989 	if (type != BP_VAR_IS && type != BP_VAR_R) {
5990 		if (date_period_is_internal_property(name)) {
5991 			zend_readonly_property_modification_error_ex("DatePeriod", ZSTR_VAL(name));
5992 			return &EG(uninitialized_zval);
5993 		}
5994 	}
5995 
5996 	return zend_std_read_property(object, name, type, cache_slot, rv);
5997 }
5998 /* }}} */
5999 
date_period_write_property(zend_object * object,zend_string * name,zval * value,void ** cache_slot)6000 static zval *date_period_write_property(zend_object *object, zend_string *name, zval *value, void **cache_slot)
6001 {
6002 	if (date_period_is_internal_property(name)) {
6003 		zend_readonly_property_modification_error_ex("DatePeriod", ZSTR_VAL(name));
6004 		return value;
6005 	}
6006 
6007 	return zend_std_write_property(object, name, value, cache_slot);
6008 }
6009 
date_period_get_property_ptr_ptr(zend_object * object,zend_string * name,int type,void ** cache_slot)6010 static zval *date_period_get_property_ptr_ptr(zend_object *object, zend_string *name, int type, void **cache_slot)
6011 {
6012 	if (date_period_is_internal_property(name)) {
6013 		zend_readonly_property_modification_error_ex("DatePeriod", ZSTR_VAL(name));
6014 		return &EG(error_zval);
6015 	}
6016 
6017 	return zend_std_get_property_ptr_ptr(object, name, type, cache_slot);
6018 }
6019