Add sub-controls for Hack array compat runtime checks
[hiphop-php.git] / hphp / runtime / base / datetime.cpp
blob944d587c5849be2212f8742cf41ccfd7091f2d48
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/datetime.h"
18 #include "hphp/runtime/base/dateinterval.h"
19 #include "hphp/runtime/base/string-buffer.h"
20 #include "hphp/runtime/base/runtime-error.h"
21 #include "hphp/runtime/base/builtin-functions.h"
22 #include "hphp/runtime/base/array-init.h"
24 namespace HPHP {
26 ///////////////////////////////////////////////////////////////////////////////
27 // statics
29 IMPLEMENT_REQUEST_LOCAL(DateTime::LastErrors, DateTime::s_lastErrors);
31 const char *DateTime::DateFormatRFC822 = "D, d M y H:i:s O";
32 const char *DateTime::DateFormatRFC850 = "l, d-M-y H:i:s T";
33 const char *DateTime::DateFormatRFC1036 = "D, d M y H:i:s O";
34 const char *DateTime::DateFormatRFC1123 = "D, d M Y H:i:s O";
35 const char *DateTime::DateFormatRFC2822 = "D, d M Y H:i:s O";
36 const char *DateTime::DateFormatRFC3339 = "Y-m-d\\TH:i:sP";
37 const char *DateTime::DateFormatISO8601 = "Y-m-d\\TH:i:sO";
38 const char *DateTime::DateFormatCookie = "D, d-M-Y H:i:s T";
39 const char *DateTime::DateFormatHttpHeader = "D, d M Y H:i:s T";
41 const char *DateTime::MonthNames[] = {
42 "January", "February", "March", "April", "May", "June",
43 "July", "August", "September", "October", "November", "December"
46 const char *DateTime::ShortMonthNames[] = {
47 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
48 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
51 const char *DateTime::WeekdayNames[] = {
52 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
55 const char *DateTime::ShortWeekdayNames[] = {
56 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
59 const char *DateTime::GetWeekdayName(int y, int m, int d) {
60 int day_of_week = timelib_day_of_week(y, m, d);
61 if (day_of_week < 0) {
62 return "Unknown";
64 return WeekdayNames[day_of_week];
67 const char *DateTime::GetShortWeekdayName(int y, int m, int d) {
68 int day_of_week = timelib_day_of_week(y, m, d);
69 if (day_of_week < 0) {
70 return "Unknown";
72 return ShortWeekdayNames[day_of_week];
75 const char *DateTime::OrdinalSuffix(int number) {
76 if (number >= 10 && number <= 19) {
77 return "th";
79 switch (number % 10) {
80 case 1: return "st";
81 case 2: return "nd";
82 case 3: return "rd";
84 return "th";
87 bool DateTime::IsLeap(int year) {
88 return timelib_is_leap(year);
91 int DateTime::DaysInMonth(int y, int m) {
92 return timelib_days_in_month(y, m);
95 bool DateTime::IsValid(int y, int m, int d) {
96 return y >= 1 && y <= 32767 && m >= 1 && m <= 12 && d >= 1 &&
97 d <= timelib_days_in_month(y, m);
100 req::ptr<DateTime> DateTime::Current(bool utc /* = false */) {
101 return req::make<DateTime>(time(0), utc);
104 const StaticString
105 s_year("year"),
106 s_month("month"),
107 s_day("day"),
108 s_hour("hour"),
109 s_minute("minute"),
110 s_second("second"),
111 s_zone("zone"),
112 s_zone_type("zone_type"),
113 s_fraction("fraction"),
114 s_warning_count("warning_count"),
115 s_warnings("warnings"),
116 s_error_count("error_count"),
117 s_errors("errors"),
118 s_is_localtime("is_localtime"),
119 s_is_dst("is_dst"),
120 s_tz_abbr("tz_abbr"),
121 s_tz_id("tz_id"),
122 s_weekday("weekday"),
123 s_relative("relative"),
124 s_tm_sec("tm_sec"),
125 s_tm_min("tm_min"),
126 s_tm_hour("tm_hour"),
127 s_tm_mday("tm_mday"),
128 s_tm_mon("tm_mon"),
129 s_tm_year("tm_year"),
130 s_tm_wday("tm_wday"),
131 s_tm_yday("tm_yday"),
132 s_tm_isdst("tm_isdst"),
133 s_unparsed("unparsed"),
134 s_seconds("seconds"),
135 s_minutes("minutes"),
136 s_hours("hours"),
137 s_mday("mday"),
138 s_wday("wday"),
139 s_mon("mon"),
140 s_yday("yday");
142 #define PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(name, elem) \
143 if ((int)parsed_time->elem == -99999) { \
144 ret.set(name, false); \
145 } else { \
146 ret.set(name, (int)parsed_time->elem); \
149 Array DateTime::Parse(const String& datetime) {
150 struct timelib_error_container* error;
151 timelib_time* parsed_time =
152 timelib_strtotime((char *)datetime.data(), datetime.size(), &error,
153 TimeZone::GetDatabase(), TimeZone::GetTimeZoneInfoRaw);
154 return DateTime::ParseTime(parsed_time, error);
157 Array DateTime::Parse(const String& format, const String& date) {
158 struct timelib_error_container* error;
159 timelib_time* parsed_time =
160 timelib_parse_from_format((char *)format.data(), (char *)date.data(),
161 date.size(), &error, TimeZone::GetDatabase(),
162 TimeZone::GetTimeZoneInfoRaw);
163 return DateTime::ParseTime(parsed_time, error);
166 Array DateTime::ParseAsStrptime(const String& format, const String& date) {
167 struct tm parsed_time;
168 memset(&parsed_time, 0, sizeof(parsed_time));
169 char* unparsed_part = strptime(date.data(), format.data(), &parsed_time);
170 if (unparsed_part == nullptr) {
171 return Array();
174 return make_map_array(
175 s_tm_sec, parsed_time.tm_sec,
176 s_tm_min, parsed_time.tm_min,
177 s_tm_hour, parsed_time.tm_hour,
178 s_tm_mday, parsed_time.tm_mday,
179 s_tm_mon, parsed_time.tm_mon,
180 s_tm_year, parsed_time.tm_year,
181 s_tm_wday, parsed_time.tm_wday,
182 s_tm_yday, parsed_time.tm_yday,
183 s_unparsed, String(unparsed_part, CopyString)
187 Array DateTime::ParseTime(timelib_time* parsed_time,
188 struct timelib_error_container* error) {
189 Array ret;
190 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_year, y);
191 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_month, m);
192 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_day, d);
193 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_hour, h);
194 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_minute, i);
195 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_second, s);
197 if (parsed_time->f == -99999) {
198 ret.set(s_fraction, false);
199 } else {
200 ret.set(s_fraction, parsed_time->f);
203 setLastErrors(error);
205 Array warnings = DateTime::getLastWarnings();
206 ret.set(s_warning_count, warnings.size());
207 ret.set(s_warnings, warnings);
210 Array errors = DateTime::getLastErrors();
211 ret.set(s_error_count, errors.size());
212 ret.set(s_errors, errors);
215 ret.set(s_is_localtime, (bool)parsed_time->is_localtime);
216 if (parsed_time->is_localtime) {
217 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_zone_type, zone_type);
218 switch (parsed_time->zone_type) {
219 case TIMELIB_ZONETYPE_OFFSET:
220 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_zone, z);
221 ret.set(s_is_dst, (bool)parsed_time->dst);
222 break;
223 case TIMELIB_ZONETYPE_ID:
224 if (parsed_time->tz_abbr) {
225 ret.set(s_tz_abbr, String(parsed_time->tz_abbr, CopyString));
227 if (parsed_time->tz_info) {
228 ret.set(s_tz_id, String(parsed_time->tz_info->name, CopyString));
230 break;
231 case TIMELIB_ZONETYPE_ABBR:
232 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_zone, z);
233 ret.set(s_is_dst, (bool)parsed_time->dst);
234 ret.set(s_tz_abbr, String(parsed_time->tz_abbr, CopyString));
235 break;
240 Array element;
241 if (parsed_time->have_relative) {
242 element.set(s_year, parsed_time->relative.y);
243 element.set(s_month, parsed_time->relative.m);
244 element.set(s_day, parsed_time->relative.d);
245 element.set(s_hour, parsed_time->relative.h);
246 element.set(s_minute, parsed_time->relative.i);
247 element.set(s_second, parsed_time->relative.s);
248 #if defined(TIMELIB_VERSION)
249 if (parsed_time->relative.have_weekday_relative) {
250 #else
251 if (parsed_time->have_weekday_relative) {
252 #endif
253 element.set(s_weekday, parsed_time->relative.weekday);
255 ret.set(s_relative, element);
259 timelib_time_dtor(parsed_time);
260 return ret;
263 ///////////////////////////////////////////////////////////////////////////////
264 // constructors
266 DateTime::DateTime() : m_timestamp(-1), m_timestampSet(false) {
267 m_time = TimePtr(timelib_time_ctor(), time_deleter());
268 setTimezone(TimeZone::Current());
271 DateTime::DateTime(int64_t timestamp, bool utc /* = false */) {
272 fromTimeStamp(timestamp, utc);
275 DateTime::DateTime(int64_t timestamp, req::ptr<TimeZone> tz) : m_tz(tz) {
276 fromTimeStamp(timestamp);
279 DateTime::DateTime(const DateTime& dt) :
280 m_tz(dt.m_tz),
281 m_timestamp(dt.m_timestamp),
282 m_timestampSet(dt.m_timestampSet) {
284 auto t = timelib_time_clone(dt.m_time.get());
285 m_time = TimePtr(t, time_deleter());
288 void DateTime::fromTimeStamp(int64_t timestamp, bool utc /* = false */) {
289 m_timestamp = timestamp;
290 m_timestampSet = true;
292 timelib_time *t = timelib_time_ctor();
293 if (utc) {
294 t->zone_type = TIMELIB_ZONETYPE_OFFSET;
295 t->z = 0;
296 timelib_unixtime2gmt(t, (timelib_sll)m_timestamp);
297 } else {
298 if (!m_tz.get()) {
299 m_tz = TimeZone::Current();
301 t->tz_info = m_tz->get();
302 if (!t->tz_info) {
303 raise_error("No tz info found for timezone check for tzdata package.");
305 t->zone_type = TIMELIB_ZONETYPE_ID;
306 timelib_unixtime2local(t, (timelib_sll)m_timestamp);
308 m_time = TimePtr(t, time_deleter());
311 void DateTime::sweep() {
312 m_time.reset();
315 ///////////////////////////////////////////////////////////////////////////////
316 // informational
318 int DateTime::beat() const {
319 int retval = ((((long)m_time->sse)-(((long)m_time->sse) -
320 ((((long)m_time->sse) % 86400) +
321 3600))) * 10);
322 while (retval < 0) {
323 retval += 864000;
325 retval = (retval / 864) % 1000;
326 return retval;
329 int DateTime::dow() const {
330 return timelib_day_of_week(year(), month(), day());
333 int DateTime::doy() const {
334 return timelib_day_of_year(year(), month(), day());
337 int DateTime::isoWeek() const {
338 timelib_sll iw, iy;
339 timelib_isoweek_from_date(year(), month(), day(), &iw, &iy);
340 return iw;
343 int DateTime::isoYear() const {
344 timelib_sll iw, iy;
345 timelib_isoweek_from_date(year(), month(), day(), &iw, &iy);
346 return iy;
349 int DateTime::isoDow() const {
350 return timelib_iso_day_of_week(year(), month(), day());
353 int DateTime::offset() const {
354 if (local()) {
355 switch (m_time->zone_type) {
356 case TIMELIB_ZONETYPE_ABBR:
357 case TIMELIB_ZONETYPE_OFFSET:
358 return (m_time->z - (m_time->dst * 60)) * -60;
359 default:
361 bool error;
362 timelib_time_offset *offset =
363 timelib_get_time_zone_info(toTimeStamp(error), m_tz->get());
364 int ret = offset->offset;
365 timelib_time_offset_dtor(offset);
366 return ret;
370 return 0;
373 const char *DateTime::weekdayName() const {
374 return GetWeekdayName(year(), month(), day());
377 const char *DateTime::shortWeekdayName() const {
378 return GetShortWeekdayName(year(), month(), day());
381 const char *DateTime::monthName() const {
382 return MonthNames[month() - 1];
385 const char *DateTime::shortMonthName() const {
386 return ShortMonthNames[month() - 1];
389 ///////////////////////////////////////////////////////////////////////////////
390 // modifications
392 void DateTime::update() {
393 if (utc()) {
394 timelib_update_ts(m_time.get(), nullptr);
395 } else {
396 timelib_update_ts(m_time.get(), m_tz->get());
398 m_timestamp = 0;
399 m_timestampSet = false;
402 void DateTime::set(int hou, int min, int sec, int mon, int day, int yea) {
403 /* Fill in the new data */
404 if (yea != INT_MAX) {
405 if (yea < 70) {
406 yea += 2000;
407 } else if (yea >= 70 && yea <= 100) {
408 yea += 1900;
410 m_time->y = yea;
412 if (day != INT_MAX) m_time->d = day;
413 if (mon != INT_MAX) m_time->m = mon;
414 if (sec != INT_MAX) m_time->s = sec;
415 if (min != INT_MAX) m_time->i = min;
416 if (hou != INT_MAX) m_time->h = hou;
417 update();
420 void DateTime::setDate(int y, int m, int d) {
421 m_time->y = y;
422 m_time->m = m;
423 m_time->d = d;
424 update();
427 void DateTime::setISODate(int y, int w, int d /* = 1 */) {
428 m_time->y = y;
429 m_time->m = 1;
430 m_time->d = 1;
431 m_time->relative.d = timelib_daynr_from_weeknr(y, w, d);
432 m_time->have_relative = 1;
433 update();
436 void DateTime::setTime(int hour, int minute, int second) {
437 m_time->h = hour;
438 m_time->i = minute;
439 m_time->s = second;
440 update();
443 void DateTime::setTimezone(req::ptr<TimeZone> timezone) {
444 if (timezone) {
445 m_tz = timezone->cloneTimeZone();
446 if (m_tz.get()) {
447 timelib_set_timezone(m_time.get(), m_tz->get());
448 timelib_unixtime2local(m_time.get(), m_time->sse);
453 bool DateTime::modify(const String& diff) {
454 timelib_error_container* error = nullptr;
455 timelib_time *tmp_time = timelib_strtotime((char*)diff.data(), diff.size(),
456 &error, TimeZone::GetDatabase(),
457 TimeZone::GetTimeZoneInfoRaw);
458 SCOPE_EXIT {
459 timelib_time_dtor(tmp_time);
460 if (error) timelib_error_container_dtor(error);
463 if (error && error->error_count > 0) {
464 raise_warning("DateTime::modify(): Failed to parse time string (%s)"
465 " at position %d (%c): %s",
466 diff.c_str(), error->error_messages[0].position,
467 error->error_messages[0].character, error->error_messages[0].message
469 return false;
471 internalModify(tmp_time);
472 return true;
475 void DateTime::internalModify(timelib_time *t) {
476 // TIMELIB_UNSET (and other constants) defined in timelib.h
477 // (see hhvm-third-party)
478 if (t->y != TIMELIB_UNSET) {
479 m_time->y = t->y;
481 if (t->m != TIMELIB_UNSET) {
482 m_time->m = t->m;
484 if (t->d != TIMELIB_UNSET) {
485 m_time->d = t->d;
487 if (t->h != TIMELIB_UNSET) {
488 m_time->h = t->h;
489 m_time->i = 0;
490 m_time->s = 0;
491 if (t->i != TIMELIB_UNSET) {
492 m_time->i = t->i;
493 if (t->s != TIMELIB_UNSET) {
494 m_time->s = t->s;
498 internalModifyRelative(&(t->relative), t->have_relative, 1);
501 void DateTime::internalModifyRelative(timelib_rel_time *rel,
502 bool have_relative, int8_t bias) {
503 m_time->relative.y = rel->y * bias;
504 m_time->relative.m = rel->m * bias;
505 m_time->relative.d = rel->d * bias;
506 m_time->relative.h = rel->h * bias;
507 m_time->relative.i = rel->i * bias;
508 m_time->relative.s = rel->s * bias;
509 m_time->relative.weekday = rel->weekday;
510 m_time->have_relative = have_relative;
511 m_time->relative.special = rel->special;
512 m_time->relative.have_special_relative = rel->have_special_relative;
513 m_time->relative.have_weekday_relative = rel->have_weekday_relative;
514 m_time->relative.weekday_behavior = rel->weekday_behavior;
515 m_time->relative.first_last_day_of = rel->first_last_day_of;
516 m_time->sse_uptodate = 0;
517 update();
518 timelib_update_from_sse(m_time.get());
521 void DateTime::add(const req::ptr<DateInterval>& interval) {
522 timelib_rel_time *rel = interval->get();
523 internalModifyRelative(rel, true, rel->invert ? -1 : 1);
526 void DateTime::sub(const req::ptr<DateInterval>& interval) {
527 timelib_rel_time *rel = interval->get();
528 internalModifyRelative(rel, true, rel->invert ? 1 : -1);
531 ///////////////////////////////////////////////////////////////////////////////
532 // conversions
534 void DateTime::toTm(struct tm &ta) const {
535 // TODO: Fixme under MSVC!
536 ta.tm_sec = second();
537 ta.tm_min = minute();
538 ta.tm_hour = hour();
539 ta.tm_mday = day();
540 ta.tm_mon = month() - 1;
541 ta.tm_year = year() - 1900;
542 ta.tm_wday = dow();
543 ta.tm_yday = doy();
544 if (utc()) {
545 ta.tm_isdst = 0;
546 #ifndef _MSC_VER
547 ta.tm_gmtoff = 0;
548 ta.tm_zone = "GMT";
549 #endif
550 } else {
551 timelib_time_offset *offset =
552 timelib_get_time_zone_info(m_time->sse, m_time->tz_info);
553 ta.tm_isdst = offset->is_dst;
554 #ifndef _MSC_VER
555 ta.tm_gmtoff = offset->offset;
556 ta.tm_zone = offset->abbr;
557 #endif
558 timelib_time_offset_dtor(offset);
562 int64_t DateTime::toTimeStamp(bool &err) const {
563 err = false;
564 if (!m_timestampSet) {
565 int error;
566 m_timestamp = timelib_date_to_int(m_time.get(), &error);
567 if (error) {
568 err = true;
569 } else {
570 m_timestampSet = true;
573 return m_timestamp;
576 int64_t DateTime::toInteger(char format) const {
577 bool error;
578 switch (format) {
579 case 'd':
580 case 'j': return day();
581 case 'w': return dow();
582 case 'z': return doy();
583 case 'W': return isoWeek();
584 case 'm':
585 case 'n': return month();
586 case 't': return DaysInMonth(year(), month());
587 case 'L': return DateTime::IsLeap(year());
588 case 'y': return (year() % 100);
589 case 'Y': return year();
590 case 'B': return beat();
591 case 'g':
592 case 'h': return hour12();
593 case 'H':
594 case 'G': return hour();
595 case 'i': return minute();
596 case 's': return second();
597 case 'I': return (!utc() && m_tz->dst(toTimeStamp(error))) ? 1 : 0;
598 case 'Z': return utc() ? 0 : m_tz->offset(toTimeStamp(error));
599 case 'U': return toTimeStamp(error);
601 throw_invalid_argument("unknown format char: %d", (int)format);
602 return -1;
605 String DateTime::toString(const String& format, bool stdc /* = false */) const {
606 if (format.empty()) return String();
607 return stdc ? stdcFormat(format) : rfcFormat(format);
610 String DateTime::toString(DateFormat format) const {
611 switch (format) {
612 case DateFormat::RFC822: return rfcFormat(DateFormatRFC822);
613 case DateFormat::RFC850: return rfcFormat(DateFormatRFC850);
614 case DateFormat::RFC1036: return rfcFormat(DateFormatRFC1036);
615 case DateFormat::RFC1123: return rfcFormat(DateFormatRFC1123);
616 case DateFormat::RFC2822: return rfcFormat(DateFormatRFC2822);
617 case DateFormat::RFC3339: return rfcFormat(DateFormatRFC3339);
618 case DateFormat::ISO8601: return rfcFormat(DateFormatISO8601);
619 case DateFormat::Cookie: return rfcFormat(DateFormatCookie);
620 case DateFormat::HttpHeader: return rfcFormat(DateFormatHttpHeader);
621 default:
622 assert(false);
624 throw_invalid_argument("format: %d", static_cast<int>(format));
625 return String();
628 String DateTime::rfcFormat(const String& format) const {
629 StringBuffer s;
630 bool rfc_colon = false;
631 bool error;
632 for (int i = 0; i < format.size(); i++) {
633 switch (format.charAt(i)) {
634 case 'd': s.printf("%02d", day()); break;
635 case 'D': s.append(shortWeekdayName()); break;
636 case 'j': s.append(day()); break;
637 case 'l': s.append(weekdayName()); break;
638 case 'S': s.append(OrdinalSuffix(day())); break;
639 case 'w': s.append(dow()); break;
640 case 'N': s.append(isoDow()); break;
641 case 'z': s.append(doy()); break;
642 case 'W': s.printf("%02d", isoWeek()); break;
643 case 'o': s.append(isoYear()); break;
644 case 'F': s.append(monthName()); break;
645 case 'm': s.printf("%02d", month()); break;
646 case 'M': s.append(shortMonthName()); break;
647 case 'n': s.append(month()); break;
648 case 't': s.append(DaysInMonth(year(), month())); break;
649 case 'L': s.append(IsLeap(year())); break;
650 case 'y': s.printf("%02d", year() % 100); break;
651 case 'Y': s.printf("%s%04d", year() < 0 ? "-" : "", abs(year()));
652 break;
653 case 'a': s.append(hour() >= 12 ? "pm" : "am"); break;
654 case 'A': s.append(hour() >= 12 ? "PM" : "AM"); break;
655 case 'B': s.printf("%03d", beat()); break;
656 case 'g': s.append((hour() % 12) ? (int)hour() % 12 : 12); break;
657 case 'G': s.append(hour()); break;
658 case 'h': s.printf("%02d", (hour() % 12) ? (int)hour() % 12 : 12); break;
659 case 'H': s.printf("%02d", (int)hour()); break;
660 case 'i': s.printf("%02d", (int)minute()); break;
661 case 's': s.printf("%02d", (int)second()); break;
662 case 'u': s.printf("%06d", (int)floor(fraction() * 1000000)); break;
663 case 'v': s.printf("%03d", (int)floor(fraction() * 1000)); break;
664 case 'I': s.append(!utc() && m_tz->dst(toTimeStamp(error)) ? 1 : 0);
665 break;
666 case 'P': rfc_colon = true; /* break intentionally missing */
667 case 'O':
668 if (utc()) {
669 s.printf("+00%s00", rfc_colon ? ":" : "");
670 } else {
671 int offset = this->offset();
672 s.printf("%c%02d%s%02d",
673 (offset < 0 ? '-' : '+'), abs(offset / 3600),
674 rfc_colon ? ":" : "", abs((offset % 3600) / 60));
676 break;
677 case 'T':
678 if (utc()) {
679 s.append("GMT");
680 } else {
681 if (m_time->zone_type != TIMELIB_ZONETYPE_OFFSET) {
682 s.append(m_time->tz_abbr);
683 } else {
684 auto offset = m_time->z * -60;
685 char abbr[9] = {0};
686 snprintf(abbr, 9, "GMT%c%02d%02d",
687 ((offset < 0) ? '-' : '+'),
688 abs(offset / 3600),
689 abs((offset % 3600) / 60));
690 s.append(abbr);
693 break;
694 case 'e':
695 if (utc()) {
696 s.append("UTC");
697 } else {
698 if (m_time->zone_type != TIMELIB_ZONETYPE_OFFSET) {
699 s.append(m_tz->name());
700 } else {
701 auto offset = m_time->z * -60;
702 char abbr[7] = {0};
703 snprintf(abbr, 7, "%c%02d:%02d",
704 ((offset < 0) ? '-' : '+'),
705 abs(offset / 3600),
706 abs((offset % 3600) / 60));
707 s.append(abbr);
710 break;
711 case 'Z': s.append(utc() ? 0 : this->offset()); break;
712 case 'c':
713 if (utc()) {
714 s.printf("%04d-%02d-%02dT%02d:%02d:%02d+00:00",
715 year(), month(), day(), hour(), minute(), second());
716 } else {
717 int offset = this->offset();
718 s.printf("%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
719 year(), month(), day(), hour(), minute(), second(),
720 (offset < 0 ? '-' : '+'),
721 abs(offset / 3600), abs((offset % 3600) / 60));
723 break;
724 case 'r':
725 if (utc()) {
726 s.printf("%3s, %02d %3s %04d %02d:%02d:%02d +0000",
727 shortWeekdayName(), day(), shortMonthName(), year(),
728 hour(), minute(), second());
729 } else {
730 int offset = this->offset();
731 s.printf("%3s, %02d %3s %04d %02d:%02d:%02d %c%02d%02d",
732 shortWeekdayName(), day(), shortMonthName(), year(),
733 hour(), minute(), second(),
734 (offset < 0 ? '-' : '+'),
735 abs(offset / 3600), abs((offset % 3600) / 60));
737 break;
738 case 'U': s.printf("%" PRId64, toTimeStamp(error)); break;
739 case '\\':
740 if (i < format.size()) i++; /* break intentionally missing */
741 default:
742 s.append(format[i]);
743 break;
746 return s.detach();
749 String DateTime::stdcFormat(const String& format) const {
750 // TODO: Fixme under MSVC!
751 struct tm ta;
752 timelib_time_offset *offset = nullptr;
753 ta.tm_sec = second();
754 ta.tm_min = minute();
755 ta.tm_hour = hour();
756 ta.tm_mday = day();
757 ta.tm_mon = month() - 1;
758 ta.tm_year = year() - 1900;
759 ta.tm_wday = dow();
760 ta.tm_yday = doy();
761 if (utc()) {
762 ta.tm_isdst = 0;
763 #ifndef _MSC_VER
764 ta.tm_gmtoff = 0;
765 ta.tm_zone = "GMT";
766 #endif
767 } else {
768 offset = timelib_get_time_zone_info(m_time->sse, m_time->tz_info);
769 ta.tm_isdst = offset->is_dst;
770 #ifndef _MSC_VER
771 ta.tm_gmtoff = offset->offset;
772 ta.tm_zone = offset->abbr;
773 #endif
776 if ((ta.tm_sec < 0 || ta.tm_sec > 60) ||
777 (ta.tm_min < 0 || ta.tm_min > 59) ||
778 (ta.tm_hour < 0 || ta.tm_hour > 23) ||
779 (ta.tm_mday < 1 || ta.tm_mday > 31) ||
780 (ta.tm_mon < 0 || ta.tm_mon > 11) ||
781 (ta.tm_wday < 0 || ta.tm_wday > 6) ||
782 (ta.tm_yday < 0 || ta.tm_yday > 365)) {
783 throw_invalid_argument("argument: invalid time");
784 return String();
787 int max_reallocs = 5;
788 size_t buf_len = 256, real_len;
789 char *buf = (char *)malloc(buf_len);
790 while ((real_len = strftime(buf, buf_len, format.data(), &ta)) == buf_len ||
791 real_len == 0) {
792 buf_len *= 2;
793 free(buf);
794 buf = (char *)malloc(buf_len);
795 if (!--max_reallocs) {
796 break;
799 if (!utc()) {
800 timelib_time_offset_dtor(offset);
802 if (real_len && real_len != buf_len) {
803 return String(buf, real_len, AttachString);
805 free(buf);
806 throw_invalid_argument("format: (over internal buffer)");
807 return String();
810 Array DateTime::toArray(ArrayFormat format) const {
811 bool error;
812 switch (format) {
813 case ArrayFormat::TimeMap:
814 return make_map_array(
815 s_seconds, second(),
816 s_minutes, minute(),
817 s_hours, hour(),
818 s_mday, day(),
819 s_wday, dow(),
820 s_mon, month(),
821 s_year, year(),
822 s_yday, doy(),
823 s_weekday, weekdayName(),
824 s_month, monthName(),
825 0, toTimeStamp(error)
827 case ArrayFormat::TmMap:
829 struct tm tm;
830 toTm(tm);
831 return make_map_array(
832 s_tm_sec, tm.tm_sec,
833 s_tm_min, tm.tm_min,
834 s_tm_hour, tm.tm_hour,
835 s_tm_mday, tm.tm_mday,
836 s_tm_mon, tm.tm_mon,
837 s_tm_year, tm.tm_year,
838 s_tm_wday, tm.tm_wday,
839 s_tm_yday, tm.tm_yday,
840 s_tm_isdst, tm.tm_isdst
843 case ArrayFormat::TmVector:
845 struct tm tm;
846 toTm(tm);
847 return make_packed_array(
848 tm.tm_sec,
849 tm.tm_min,
850 tm.tm_hour,
851 tm.tm_mday,
852 tm.tm_mon,
853 tm.tm_year,
854 tm.tm_wday,
855 tm.tm_yday,
856 tm.tm_isdst
860 return empty_array();
863 bool DateTime::fromString(const String& input, req::ptr<TimeZone> tz,
864 const char* format /*=NUL*/,
865 bool throw_on_error /*= true*/) {
866 struct timelib_error_container *error;
867 timelib_time *t;
868 if (format) {
869 t = timelib_parse_from_format((char*)format, (char*)input.data(),
870 input.size(), &error, TimeZone::GetDatabase(),
871 TimeZone::GetTimeZoneInfoRaw);
872 } else {
873 t = timelib_strtotime((char*)input.data(), input.size(),
874 &error, TimeZone::GetDatabase(),
875 TimeZone::GetTimeZoneInfoRaw);
877 int error1 = error->error_count;
878 setLastErrors(error);
879 if (error1) {
880 timelib_time_dtor(t);
881 if (!throw_on_error) {
882 return false;
884 auto msg = folly::format(
885 "DateTime::__construct(): Failed to parse time string "
886 "({}) at position {} ({}): {}",
887 input,
888 error->error_messages[0].position,
889 error->error_messages[0].character,
890 error->error_messages[0].message
891 ).str();
892 SystemLib::throwExceptionObject(msg);
895 if (m_timestamp == -1) {
896 fromTimeStamp(0);
898 if (tz.get() && (input.size() <= 0 || input[0] != '@')) {
899 setTimezone(tz);
900 } else {
901 setTimezone(TimeZone::Current());
904 // needed if any date part is missing
905 timelib_fill_holes(t, m_time.get(), TIMELIB_NO_CLONE);
906 timelib_update_ts(t, m_tz->get());
907 timelib_update_from_sse(t);
909 int error2;
910 m_timestamp = timelib_date_to_int(t, &error2);
911 if (error1 || error2) {
912 // Don't free t->tz_info, it belongs to GetTimeZoneInfo
913 timelib_time_dtor(t);
914 return false;
917 m_time = TimePtr(t, time_deleter());
918 if (t->tz_info != m_tz->get()) {
919 m_tz = req::make<TimeZone>(t->tz_info);
921 return true;
924 req::ptr<DateTime> DateTime::cloneDateTime() const {
925 return req::make<DateTime>(*this);
928 ///////////////////////////////////////////////////////////////////////////////
929 // comparison
931 req::ptr<DateInterval>
932 DateTime::diff(req::ptr<DateTime> datetime2, bool absolute) {
933 timelib_rel_time *rel = timelib_diff(m_time.get(), datetime2.get()->m_time.get());
934 if (absolute) {
935 rel->invert = 0;
937 return req::make<DateInterval>(rel);
940 int DateTime::compare(req::ptr<DateTime> datetime2) {
941 return timelib_time_compare(m_time.get(), datetime2.get()->m_time.get());
944 ///////////////////////////////////////////////////////////////////////////////
945 // sun
947 const StaticString
948 s_sunrise("sunrise"),
949 s_sunset("sunset"),
950 s_transit("transit"),
951 s_civil_twilight_begin("civil_twilight_begin"),
952 s_civil_twilight_end("civil_twilight_end"),
953 s_nautical_twilight_begin("nautical_twilight_begin"),
954 s_nautical_twilight_end("nautical_twilight_end"),
955 s_astronomical_twilight_begin("astronomical_twilight_begin"),
956 s_astronomical_twilight_end("astronomical_twilight_end");
958 Array DateTime::getSunInfo(double latitude, double longitude) const {
959 Array ret;
960 timelib_sll sunrise, sunset, transit;
961 double ddummy;
963 /* Get sun up/down and transit */
964 int rs = timelib_astro_rise_set_altitude(m_time.get(), longitude, latitude,
965 -35.0/60, 1, &ddummy, &ddummy,
966 &sunrise, &sunset, &transit);
967 switch (rs) {
968 case -1: /* always below */
969 ret.set(s_sunrise, false);
970 ret.set(s_sunset, false);
971 break;
972 case 1: /* always above */
973 ret.set(s_sunrise, true);
974 ret.set(s_sunset, true);
975 break;
976 default:
977 ret.set(s_sunrise, sunrise);
978 ret.set(s_sunset, sunset);
980 ret.set(s_transit, transit);
982 /* Get civil twilight */
983 rs = timelib_astro_rise_set_altitude(m_time.get(), longitude, latitude,
984 -6.0, 0,
985 &ddummy, &ddummy, &sunrise, &sunset,
986 &transit);
987 switch (rs) {
988 case -1: /* always below */
989 ret.set(s_civil_twilight_begin, false);
990 ret.set(s_civil_twilight_end, false);
991 break;
992 case 1: /* always above */
993 ret.set(s_civil_twilight_begin, true);
994 ret.set(s_civil_twilight_end, true);
995 break;
996 default:
997 ret.set(s_civil_twilight_begin, sunrise);
998 ret.set(s_civil_twilight_end, sunset);
1001 /* Get nautical twilight */
1002 rs = timelib_astro_rise_set_altitude(m_time.get(), longitude, latitude,
1003 -12.0, 0,
1004 &ddummy, &ddummy, &sunrise, &sunset,
1005 &transit);
1006 switch (rs) {
1007 case -1: /* always below */
1008 ret.set(s_nautical_twilight_begin, false);
1009 ret.set(s_nautical_twilight_end, false);
1010 break;
1011 case 1: /* always above */
1012 ret.set(s_nautical_twilight_begin, true);
1013 ret.set(s_nautical_twilight_end, true);
1014 break;
1015 default:
1016 ret.set(s_nautical_twilight_begin, sunrise);
1017 ret.set(s_nautical_twilight_end, sunset);
1020 /* Get astronomical twilight */
1021 rs = timelib_astro_rise_set_altitude(m_time.get(), longitude, latitude,
1022 -18.0, 0,
1023 &ddummy, &ddummy, &sunrise, &sunset,
1024 &transit);
1025 switch (rs) {
1026 case -1: /* always below */
1027 ret.set(s_astronomical_twilight_begin, false);
1028 ret.set(s_astronomical_twilight_end, false);
1029 break;
1030 case 1: /* always above */
1031 ret.set(s_astronomical_twilight_begin, true);
1032 ret.set(s_astronomical_twilight_end, true);
1033 break;
1034 default:
1035 ret.set(s_astronomical_twilight_begin, sunrise);
1036 ret.set(s_astronomical_twilight_end, sunset);
1038 return ret;
1041 Variant DateTime::getSunInfo(SunInfoFormat retformat,
1042 double latitude, double longitude,
1043 double zenith, double utc_offset,
1044 bool calc_sunset) const {
1045 if (retformat != SunInfoFormat::ReturnTimeStamp &&
1046 retformat != SunInfoFormat::ReturnString &&
1047 retformat != SunInfoFormat::ReturnDouble) {
1048 raise_warning("Wrong return format given, pick one of "
1049 "SUNFUNCS_RET_TIMESTAMP, SUNFUNCS_RET_STRING or "
1050 "SUNFUNCS_RET_DOUBLE");
1051 return false;
1053 double altitude = 90 - zenith;
1054 double h_rise, h_set;
1055 timelib_sll sunrise, sunset, transit;
1056 int rs = timelib_astro_rise_set_altitude(m_time.get(), longitude, latitude,
1057 altitude, 1,
1058 &h_rise, &h_set, &sunrise, &sunset,
1059 &transit);
1060 if (rs != 0) {
1061 return false;
1064 if (retformat == SunInfoFormat::ReturnTimeStamp) {
1065 return calc_sunset ? sunset : sunrise;
1068 double N = (calc_sunset ? h_set : h_rise) + utc_offset;
1069 if (N > 24 || N < 0) {
1070 N -= floor(N / 24) * 24;
1073 if (retformat == SunInfoFormat::ReturnString) {
1074 char retstr[6];
1075 snprintf(retstr, sizeof(retstr),
1076 "%02d:%02d", (int) N, (int) (60 * (N - (int) N)));
1077 return String(retstr, CopyString);
1080 assert(retformat == SunInfoFormat::ReturnDouble);
1081 return N;
1084 ///////////////////////////////////////////////////////////////////////////////