Remove support for old versions of timelib in dateinterval.h
[hiphop-php.git] / hphp / runtime / base / datetime.cpp
blobd566d2867045fa0134d8a7d47fb1c5aabf96a378
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2015 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/type-conversions.h"
22 #include "hphp/runtime/base/builtin-functions.h"
23 #include "hphp/runtime/base/array-init.h"
25 namespace HPHP {
27 ///////////////////////////////////////////////////////////////////////////////
28 // statics
30 IMPLEMENT_REQUEST_LOCAL(DateTime::LastErrors, DateTime::s_lastErrors);
32 const char *DateTime::DateFormatRFC822 = "D, d M y H:i:s O";
33 const char *DateTime::DateFormatRFC850 = "l, d-M-y H:i:s T";
34 const char *DateTime::DateFormatRFC1036 = "D, d M y H:i:s O";
35 const char *DateTime::DateFormatRFC1123 = "D, d M Y H:i:s O";
36 const char *DateTime::DateFormatRFC2822 = "D, d M Y H:i:s O";
37 const char *DateTime::DateFormatRFC3339 = "Y-m-d\\TH:i:sP";
38 const char *DateTime::DateFormatISO8601 = "Y-m-d\\TH:i:sO";
39 const char *DateTime::DateFormatCookie = "D, d-M-Y H:i:s T";
40 const char *DateTime::DateFormatHttpHeader = "D, d M Y H:i:s T";
42 const char *DateTime::MonthNames[] = {
43 "January", "February", "March", "April", "May", "June",
44 "July", "August", "September", "October", "November", "December"
47 const char *DateTime::ShortMonthNames[] = {
48 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
49 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
52 const char *DateTime::WeekdayNames[] = {
53 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
56 const char *DateTime::ShortWeekdayNames[] = {
57 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
60 const char *DateTime::GetWeekdayName(int y, int m, int d) {
61 int day_of_week = timelib_day_of_week(y, m, d);
62 if (day_of_week < 0) {
63 return "Unknown";
65 return WeekdayNames[day_of_week];
68 const char *DateTime::GetShortWeekdayName(int y, int m, int d) {
69 int day_of_week = timelib_day_of_week(y, m, d);
70 if (day_of_week < 0) {
71 return "Unknown";
73 return ShortWeekdayNames[day_of_week];
76 const char *DateTime::OrdinalSuffix(int number) {
77 if (number >= 10 && number <= 19) {
78 return "th";
80 switch (number % 10) {
81 case 1: return "st";
82 case 2: return "nd";
83 case 3: return "rd";
85 return "th";
88 bool DateTime::IsLeap(int year) {
89 return timelib_is_leap(year);
92 int DateTime::DaysInMonth(int y, int m) {
93 return timelib_days_in_month(y, m);
96 bool DateTime::IsValid(int y, int m, int d) {
97 return y >= 1 && y <= 32767 && m >= 1 && m <= 12 && d >= 1 &&
98 d <= timelib_days_in_month(y, m);
101 req::ptr<DateTime> DateTime::Current(bool utc /* = false */) {
102 return req::make<DateTime>(time(0), utc);
105 const StaticString
106 s_year("year"),
107 s_month("month"),
108 s_day("day"),
109 s_hour("hour"),
110 s_minute("minute"),
111 s_second("second"),
112 s_zone("zone"),
113 s_zone_type("zone_type"),
114 s_fraction("fraction"),
115 s_warning_count("warning_count"),
116 s_warnings("warnings"),
117 s_error_count("error_count"),
118 s_errors("errors"),
119 s_is_localtime("is_localtime"),
120 s_is_dst("is_dst"),
121 s_tz_abbr("tz_abbr"),
122 s_tz_id("tz_id"),
123 s_weekday("weekday"),
124 s_relative("relative"),
125 s_tm_sec("tm_sec"),
126 s_tm_min("tm_min"),
127 s_tm_hour("tm_hour"),
128 s_tm_mday("tm_mday"),
129 s_tm_mon("tm_mon"),
130 s_tm_year("tm_year"),
131 s_tm_wday("tm_wday"),
132 s_tm_yday("tm_yday"),
133 s_tm_isdst("tm_isdst"),
134 s_unparsed("unparsed"),
135 s_seconds("seconds"),
136 s_minutes("minutes"),
137 s_hours("hours"),
138 s_mday("mday"),
139 s_wday("wday"),
140 s_mon("mon"),
141 s_yday("yday");
143 #define PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(name, elem) \
144 if ((int)parsed_time->elem == -99999) { \
145 ret.set(name, false); \
146 } else { \
147 ret.set(name, (int)parsed_time->elem); \
150 Array DateTime::Parse(const String& datetime) {
151 struct timelib_error_container* error;
152 timelib_time* parsed_time =
153 timelib_strtotime((char *)datetime.data(), datetime.size(), &error,
154 TimeZone::GetDatabase(), TimeZone::GetTimeZoneInfoRaw);
155 return DateTime::ParseTime(parsed_time, error);
158 Array DateTime::Parse(const String& format, const String& date) {
159 struct timelib_error_container* error;
160 timelib_time* parsed_time =
161 timelib_parse_from_format((char *)format.data(), (char *)date.data(),
162 date.size(), &error, TimeZone::GetDatabase(),
163 TimeZone::GetTimeZoneInfoRaw);
164 return DateTime::ParseTime(parsed_time, error);
167 Array DateTime::ParseAsStrptime(const String& format, const String& date) {
168 struct tm parsed_time;
169 memset(&parsed_time, 0, sizeof(parsed_time));
170 char* unparsed_part = strptime(date.data(), format.data(), &parsed_time);
171 if (unparsed_part == nullptr) {
172 return Array();
175 return make_map_array(
176 s_tm_sec, parsed_time.tm_sec,
177 s_tm_min, parsed_time.tm_min,
178 s_tm_hour, parsed_time.tm_hour,
179 s_tm_mday, parsed_time.tm_mday,
180 s_tm_mon, parsed_time.tm_mon,
181 s_tm_year, parsed_time.tm_year,
182 s_tm_wday, parsed_time.tm_wday,
183 s_tm_yday, parsed_time.tm_yday,
184 s_unparsed, String(unparsed_part, CopyString)
188 Array DateTime::ParseTime(timelib_time* parsed_time,
189 struct timelib_error_container* error) {
190 Array ret;
191 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_year, y);
192 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_month, m);
193 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_day, d);
194 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_hour, h);
195 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_minute, i);
196 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_second, s);
198 if (parsed_time->f == -99999) {
199 ret.set(s_fraction, false);
200 } else {
201 ret.set(s_fraction, parsed_time->f);
204 setLastErrors(error);
206 Array warnings = DateTime::getLastWarnings();
207 ret.set(s_warning_count, warnings.size());
208 ret.set(s_warnings, warnings);
211 Array errors = DateTime::getLastErrors();
212 ret.set(s_error_count, errors.size());
213 ret.set(s_errors, errors);
216 ret.set(s_is_localtime, (bool)parsed_time->is_localtime);
217 if (parsed_time->is_localtime) {
218 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_zone_type, zone_type);
219 switch (parsed_time->zone_type) {
220 case TIMELIB_ZONETYPE_OFFSET:
221 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_zone, z);
222 ret.set(s_is_dst, (bool)parsed_time->dst);
223 break;
224 case TIMELIB_ZONETYPE_ID:
225 if (parsed_time->tz_abbr) {
226 ret.set(s_tz_abbr, String(parsed_time->tz_abbr, CopyString));
228 if (parsed_time->tz_info) {
229 ret.set(s_tz_id, String(parsed_time->tz_info->name, CopyString));
231 break;
232 case TIMELIB_ZONETYPE_ABBR:
233 PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_zone, z);
234 ret.set(s_is_dst, (bool)parsed_time->dst);
235 ret.set(s_tz_abbr, String(parsed_time->tz_abbr, CopyString));
236 break;
241 Array element;
242 if (parsed_time->have_relative) {
243 element.set(s_year, parsed_time->relative.y);
244 element.set(s_month, parsed_time->relative.m);
245 element.set(s_day, parsed_time->relative.d);
246 element.set(s_hour, parsed_time->relative.h);
247 element.set(s_minute, parsed_time->relative.i);
248 element.set(s_second, parsed_time->relative.s);
249 #if defined(TIMELIB_VERSION)
250 if (parsed_time->relative.have_weekday_relative) {
251 #else
252 if (parsed_time->have_weekday_relative) {
253 #endif
254 element.set(s_weekday, parsed_time->relative.weekday);
256 ret.set(s_relative, element);
260 timelib_time_dtor(parsed_time);
261 return ret;
264 ///////////////////////////////////////////////////////////////////////////////
265 // constructors
267 DateTime::DateTime() : m_timestamp(-1), m_timestampSet(false) {
268 m_time = TimePtr(timelib_time_ctor(), time_deleter());
269 setTimezone(TimeZone::Current());
272 DateTime::DateTime(int64_t timestamp, bool utc /* = false */) {
273 fromTimeStamp(timestamp, utc);
276 DateTime::DateTime(int64_t timestamp, req::ptr<TimeZone> tz) : m_tz(tz) {
277 fromTimeStamp(timestamp);
280 DateTime::DateTime(const DateTime& dt) :
281 m_tz(dt.m_tz),
282 m_timestamp(dt.m_timestamp),
283 m_timestampSet(dt.m_timestampSet) {
285 auto t = timelib_time_clone(dt.m_time.get());
286 m_time = TimePtr(t, time_deleter());
289 void DateTime::fromTimeStamp(int64_t timestamp, bool utc /* = false */) {
290 m_timestamp = timestamp;
291 m_timestampSet = true;
293 timelib_time *t = timelib_time_ctor();
294 if (utc) {
295 t->zone_type = TIMELIB_ZONETYPE_OFFSET;
296 t->z = 0;
297 timelib_unixtime2gmt(t, (timelib_sll)m_timestamp);
298 } else {
299 if (!m_tz.get()) {
300 m_tz = TimeZone::Current();
302 t->tz_info = m_tz->get();
303 t->zone_type = TIMELIB_ZONETYPE_ID;
304 timelib_unixtime2local(t, (timelib_sll)m_timestamp);
306 m_time = TimePtr(t, time_deleter());
309 void DateTime::sweep() {
310 m_time.reset();
313 ///////////////////////////////////////////////////////////////////////////////
314 // informational
316 int DateTime::beat() const {
317 int retval = (((((long)m_time->sse)-(((long)m_time->sse) -
318 ((((long)m_time->sse) % 86400) +
319 3600))) * 10) / 864);
320 while (retval < 0) {
321 retval += 1000;
323 retval = retval % 1000;
324 return retval;
327 int DateTime::dow() const {
328 return timelib_day_of_week(year(), month(), day());
331 int DateTime::doy() const {
332 return timelib_day_of_year(year(), month(), day());
335 int DateTime::isoWeek() const {
336 timelib_sll iw, iy;
337 timelib_isoweek_from_date(year(), month(), day(), &iw, &iy);
338 return iw;
341 int DateTime::isoYear() const {
342 timelib_sll iw, iy;
343 timelib_isoweek_from_date(year(), month(), day(), &iw, &iy);
344 return iy;
347 int DateTime::isoDow() const {
348 return timelib_iso_day_of_week(year(), month(), day());
351 int DateTime::offset() const {
352 if (local()) {
353 switch (m_time->zone_type) {
354 case TIMELIB_ZONETYPE_ABBR:
355 case TIMELIB_ZONETYPE_OFFSET:
356 return (m_time->z - (m_time->dst * 60)) * -60;
357 default:
359 bool error;
360 timelib_time_offset *offset =
361 timelib_get_time_zone_info(toTimeStamp(error), m_tz->get());
362 int ret = offset->offset;
363 timelib_time_offset_dtor(offset);
364 return ret;
368 return 0;
371 const char *DateTime::weekdayName() const {
372 return GetWeekdayName(year(), month(), day());
375 const char *DateTime::shortWeekdayName() const {
376 return GetShortWeekdayName(year(), month(), day());
379 const char *DateTime::monthName() const {
380 return MonthNames[month() - 1];
383 const char *DateTime::shortMonthName() const {
384 return ShortMonthNames[month() - 1];
387 ///////////////////////////////////////////////////////////////////////////////
388 // modifications
390 void DateTime::update() {
391 if (utc()) {
392 timelib_update_ts(m_time.get(), nullptr);
393 } else {
394 timelib_update_ts(m_time.get(), m_tz->get());
396 m_timestamp = 0;
397 m_timestampSet = false;
400 void DateTime::set(int hou, int min, int sec, int mon, int day, int yea) {
401 /* Fill in the new data */
402 if (yea != INT_MAX) {
403 if (yea < 70) {
404 yea += 2000;
405 } else if (yea >= 70 && yea <= 100) {
406 yea += 1900;
408 m_time->y = yea;
410 if (day != INT_MAX) m_time->d = day;
411 if (mon != INT_MAX) m_time->m = mon;
412 if (sec != INT_MAX) m_time->s = sec;
413 if (min != INT_MAX) m_time->i = min;
414 if (hou != INT_MAX) m_time->h = hou;
415 update();
418 void DateTime::setDate(int y, int m, int d) {
419 m_time->y = y;
420 m_time->m = m;
421 m_time->d = d;
422 update();
425 void DateTime::setISODate(int y, int w, int d /* = 1 */) {
426 m_time->y = y;
427 m_time->m = 1;
428 m_time->d = 1;
429 m_time->relative.d = timelib_daynr_from_weeknr(y, w, d);
430 m_time->have_relative = 1;
431 update();
434 void DateTime::setTime(int hour, int minute, int second) {
435 m_time->h = hour;
436 m_time->i = minute;
437 m_time->s = second;
438 update();
441 void DateTime::setTimezone(req::ptr<TimeZone> timezone) {
442 if (timezone) {
443 m_tz = timezone->cloneTimeZone();
444 if (m_tz.get() && m_tz->get()) {
445 timelib_set_timezone(m_time.get(), m_tz->get());
446 timelib_unixtime2local(m_time.get(), m_time->sse);
451 void DateTime::modify(const String& diff) {
452 timelib_time *tmp_time = timelib_strtotime((char*)diff.data(), diff.size(),
453 nullptr, TimeZone::GetDatabase(),
454 TimeZone::GetTimeZoneInfoRaw);
455 internalModify(tmp_time);
456 timelib_time_dtor(tmp_time);
459 void DateTime::internalModify(timelib_time *t) {
460 // TIMELIB_UNSET (and other constants) defined in timelib.h
461 // (see hhvm-third-party)
462 if (t->y != TIMELIB_UNSET) {
463 m_time->y = t->y;
465 if (t->m != TIMELIB_UNSET) {
466 m_time->m = t->m;
468 if (t->d != TIMELIB_UNSET) {
469 m_time->d = t->d;
471 if (t->h != TIMELIB_UNSET) {
472 m_time->h = t->h;
473 m_time->i = 0;
474 m_time->s = 0;
475 if (t->i != TIMELIB_UNSET) {
476 m_time->i = t->i;
477 if (t->s != TIMELIB_UNSET) {
478 m_time->s = t->s;
482 internalModifyRelative(&(t->relative), t->have_relative, 1);
485 void DateTime::internalModifyRelative(timelib_rel_time *rel,
486 bool have_relative, int8_t bias) {
487 m_time->relative.y = rel->y * bias;
488 m_time->relative.m = rel->m * bias;
489 m_time->relative.d = rel->d * bias;
490 m_time->relative.h = rel->h * bias;
491 m_time->relative.i = rel->i * bias;
492 m_time->relative.s = rel->s * bias;
493 m_time->relative.weekday = rel->weekday;
494 m_time->have_relative = have_relative;
495 m_time->relative.special = rel->special;
496 m_time->relative.have_special_relative = rel->have_special_relative;
497 m_time->relative.have_weekday_relative = rel->have_weekday_relative;
498 m_time->relative.weekday_behavior = rel->weekday_behavior;
499 m_time->relative.first_last_day_of = rel->first_last_day_of;
500 m_time->sse_uptodate = 0;
501 update();
502 timelib_update_from_sse(m_time.get());
505 void DateTime::add(const req::ptr<DateInterval>& interval) {
506 timelib_rel_time *rel = interval->get();
507 internalModifyRelative(rel, true, rel->invert ? -1 : 1);
510 void DateTime::sub(const req::ptr<DateInterval>& interval) {
511 timelib_rel_time *rel = interval->get();
512 internalModifyRelative(rel, true, rel->invert ? 1 : -1);
515 ///////////////////////////////////////////////////////////////////////////////
516 // conversions
518 void DateTime::toTm(struct tm &ta) const {
519 // TODO: Fixme under MSVC!
520 ta.tm_sec = second();
521 ta.tm_min = minute();
522 ta.tm_hour = hour();
523 ta.tm_mday = day();
524 ta.tm_mon = month() - 1;
525 ta.tm_year = year() - 1900;
526 ta.tm_wday = dow();
527 ta.tm_yday = doy();
528 if (utc()) {
529 ta.tm_isdst = 0;
530 #ifndef _MSC_VER
531 ta.tm_gmtoff = 0;
532 ta.tm_zone = "GMT";
533 #endif
534 } else {
535 timelib_time_offset *offset =
536 timelib_get_time_zone_info(m_time->sse, m_time->tz_info);
537 ta.tm_isdst = offset->is_dst;
538 #ifndef _MSC_VER
539 ta.tm_gmtoff = offset->offset;
540 ta.tm_zone = offset->abbr;
541 #endif
542 timelib_time_offset_dtor(offset);
546 int64_t DateTime::toTimeStamp(bool &err) const {
547 err = false;
548 if (!m_timestampSet) {
549 int error;
550 m_timestamp = timelib_date_to_int(m_time.get(), &error);
551 if (error) {
552 err = true;
553 } else {
554 m_timestampSet = true;
557 return m_timestamp;
560 int64_t DateTime::toInteger(char format) const {
561 bool error;
562 switch (format) {
563 case 'd':
564 case 'j': return day();
565 case 'w': return dow();
566 case 'z': return doy();
567 case 'W': return isoWeek();
568 case 'm':
569 case 'n': return month();
570 case 't': return DaysInMonth(year(), month());
571 case 'L': return DateTime::IsLeap(year());
572 case 'y': return (year() % 100);
573 case 'Y': return year();
574 case 'B': return beat();
575 case 'g':
576 case 'h': return hour12();
577 case 'H':
578 case 'G': return hour();
579 case 'i': return minute();
580 case 's': return second();
581 case 'I': return (!utc() && m_tz->dst(toTimeStamp(error))) ? 1 : 0;
582 case 'Z': return utc() ? 0 : m_tz->offset(toTimeStamp(error));
583 case 'U': return toTimeStamp(error);
585 throw_invalid_argument("unknown format char: %d", (int)format);
586 return -1;
589 String DateTime::toString(const String& format, bool stdc /* = false */) const {
590 if (format.empty()) return String();
591 return stdc ? stdcFormat(format) : rfcFormat(format);
594 String DateTime::toString(DateFormat format) const {
595 switch (format) {
596 case DateFormat::RFC822: return rfcFormat(DateFormatRFC822);
597 case DateFormat::RFC850: return rfcFormat(DateFormatRFC850);
598 case DateFormat::RFC1036: return rfcFormat(DateFormatRFC1036);
599 case DateFormat::RFC1123: return rfcFormat(DateFormatRFC1123);
600 case DateFormat::RFC2822: return rfcFormat(DateFormatRFC2822);
601 case DateFormat::RFC3339: return rfcFormat(DateFormatRFC3339);
602 case DateFormat::ISO8601: return rfcFormat(DateFormatISO8601);
603 case DateFormat::Cookie: return rfcFormat(DateFormatCookie);
604 case DateFormat::HttpHeader: return rfcFormat(DateFormatHttpHeader);
605 default:
606 assert(false);
608 throw_invalid_argument("format: %d", static_cast<int>(format));
609 return String();
612 String DateTime::rfcFormat(const String& format) const {
613 StringBuffer s;
614 bool rfc_colon = false;
615 bool error;
616 for (int i = 0; i < format.size(); i++) {
617 switch (format.charAt(i)) {
618 case 'd': s.printf("%02d", day()); break;
619 case 'D': s.append(shortWeekdayName()); break;
620 case 'j': s.append(day()); break;
621 case 'l': s.append(weekdayName()); break;
622 case 'S': s.append(OrdinalSuffix(day())); break;
623 case 'w': s.append(dow()); break;
624 case 'N': s.append(isoDow()); break;
625 case 'z': s.append(doy()); break;
626 case 'W': s.printf("%02d", isoWeek()); break;
627 case 'o': s.append(isoYear()); break;
628 case 'F': s.append(monthName()); break;
629 case 'm': s.printf("%02d", month()); break;
630 case 'M': s.append(shortMonthName()); break;
631 case 'n': s.append(month()); break;
632 case 't': s.append(DaysInMonth(year(), month())); break;
633 case 'L': s.append(IsLeap(year())); break;
634 case 'y': s.printf("%02d", year() % 100); break;
635 case 'Y': s.printf("%s%04d", year() < 0 ? "-" : "", abs(year()));
636 break;
637 case 'a': s.append(hour() >= 12 ? "pm" : "am"); break;
638 case 'A': s.append(hour() >= 12 ? "PM" : "AM"); break;
639 case 'B': s.printf("%03d", beat()); break;
640 case 'g': s.append((hour() % 12) ? (int)hour() % 12 : 12); break;
641 case 'G': s.append(hour()); break;
642 case 'h': s.printf("%02d", (hour() % 12) ? (int)hour() % 12 : 12); break;
643 case 'H': s.printf("%02d", (int)hour()); break;
644 case 'i': s.printf("%02d", (int)minute()); break;
645 case 's': s.printf("%02d", (int)second()); break;
646 case 'u': s.printf("%06d", (int)floor(fraction() * 1000000)); break;
647 case 'I': s.append(!utc() && m_tz->dst(toTimeStamp(error)) ? 1 : 0);
648 break;
649 case 'P': rfc_colon = true; /* break intentionally missing */
650 case 'O':
651 if (utc()) {
652 s.printf("+00%s00", rfc_colon ? ":" : "");
653 } else {
654 int offset = this->offset();
655 s.printf("%c%02d%s%02d",
656 (offset < 0 ? '-' : '+'), abs(offset / 3600),
657 rfc_colon ? ":" : "", abs((offset % 3600) / 60));
659 break;
660 case 'T':
661 if (utc()) {
662 s.append("GMT");
663 } else {
664 if (m_time->zone_type != TIMELIB_ZONETYPE_OFFSET) {
665 s.append(m_time->tz_abbr);
666 } else {
667 auto offset = m_time->z * -60;
668 char abbr[9] = {0};
669 snprintf(abbr, 9, "GMT%c%02d%02d",
670 ((offset < 0) ? '-' : '+'),
671 abs(offset / 3600),
672 abs((offset % 3600) / 60));
673 s.append(abbr);
676 break;
677 case 'e':
678 if (utc()) {
679 s.append("UTC");
680 } else {
681 if (m_time->zone_type != TIMELIB_ZONETYPE_OFFSET) {
682 s.append(m_tz->name());
683 } else {
684 auto offset = m_time->z * -60;
685 char abbr[7] = {0};
686 snprintf(abbr, 7, "%c%02d:%02d",
687 ((offset < 0) ? '-' : '+'),
688 abs(offset / 3600),
689 abs((offset % 3600) / 60));
690 s.append(abbr);
693 break;
694 case 'Z': s.append(utc() ? 0 : this->offset()); break;
695 case 'c':
696 if (utc()) {
697 s.printf("%04d-%02d-%02dT%02d:%02d:%02d+00:00",
698 year(), month(), day(), hour(), minute(), second());
699 } else {
700 int offset = this->offset();
701 s.printf("%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
702 year(), month(), day(), hour(), minute(), second(),
703 (offset < 0 ? '-' : '+'),
704 abs(offset / 3600), abs((offset % 3600) / 60));
706 break;
707 case 'r':
708 if (utc()) {
709 s.printf("%3s, %02d %3s %04d %02d:%02d:%02d +0000",
710 shortWeekdayName(), day(), shortMonthName(), year(),
711 hour(), minute(), second());
712 } else {
713 int offset = this->offset();
714 s.printf("%3s, %02d %3s %04d %02d:%02d:%02d %c%02d%02d",
715 shortWeekdayName(), day(), shortMonthName(), year(),
716 hour(), minute(), second(),
717 (offset < 0 ? '-' : '+'),
718 abs(offset / 3600), abs((offset % 3600) / 60));
720 break;
721 case 'U': s.printf("%" PRId64, toTimeStamp(error)); break;
722 case '\\':
723 if (i < format.size()) i++; /* break intentionally missing */
724 default:
725 s.append(format[i]);
726 break;
729 return s.detach();
732 String DateTime::stdcFormat(const String& format) const {
733 // TODO: Fixme under MSVC!
734 struct tm ta;
735 timelib_time_offset *offset = nullptr;
736 ta.tm_sec = second();
737 ta.tm_min = minute();
738 ta.tm_hour = hour();
739 ta.tm_mday = day();
740 ta.tm_mon = month() - 1;
741 ta.tm_year = year() - 1900;
742 ta.tm_wday = dow();
743 ta.tm_yday = doy();
744 if (utc()) {
745 ta.tm_isdst = 0;
746 #ifndef _MSC_VER
747 ta.tm_gmtoff = 0;
748 ta.tm_zone = "GMT";
749 #endif
750 } else {
751 offset = timelib_get_time_zone_info(m_time->sse, m_time->tz_info);
752 ta.tm_isdst = offset->is_dst;
753 #ifndef _MSC_VER
754 ta.tm_gmtoff = offset->offset;
755 ta.tm_zone = offset->abbr;
756 #endif
759 if ((ta.tm_sec < 0 || ta.tm_sec > 60) ||
760 (ta.tm_min < 0 || ta.tm_min > 59) ||
761 (ta.tm_hour < 0 || ta.tm_hour > 23) ||
762 (ta.tm_mday < 1 || ta.tm_mday > 31) ||
763 (ta.tm_mon < 0 || ta.tm_mon > 11) ||
764 (ta.tm_wday < 0 || ta.tm_wday > 6) ||
765 (ta.tm_yday < 0 || ta.tm_yday > 365)) {
766 throw_invalid_argument("argument: invalid time");
767 return String();
770 int max_reallocs = 5;
771 size_t buf_len = 256, real_len;
772 char *buf = (char *)malloc(buf_len);
773 while ((real_len = strftime(buf, buf_len, format.data(), &ta)) == buf_len ||
774 real_len == 0) {
775 buf_len *= 2;
776 free(buf);
777 buf = (char *)malloc(buf_len);
778 if (!--max_reallocs) {
779 break;
782 if (!utc()) {
783 timelib_time_offset_dtor(offset);
785 if (real_len && real_len != buf_len) {
786 return String(buf, real_len, AttachString);
788 free(buf);
789 throw_invalid_argument("format: (over internal buffer)");
790 return String();
793 Array DateTime::toArray(ArrayFormat format) const {
794 bool error;
795 switch (format) {
796 case ArrayFormat::TimeMap:
797 return make_map_array(
798 s_seconds, second(),
799 s_minutes, minute(),
800 s_hours, hour(),
801 s_mday, day(),
802 s_wday, dow(),
803 s_mon, month(),
804 s_year, year(),
805 s_yday, doy(),
806 s_weekday, weekdayName(),
807 s_month, monthName(),
808 0, toTimeStamp(error)
810 case ArrayFormat::TmMap:
812 struct tm tm;
813 toTm(tm);
814 return make_map_array(
815 s_tm_sec, tm.tm_sec,
816 s_tm_min, tm.tm_min,
817 s_tm_hour, tm.tm_hour,
818 s_tm_mday, tm.tm_mday,
819 s_tm_mon, tm.tm_mon,
820 s_tm_year, tm.tm_year,
821 s_tm_wday, tm.tm_wday,
822 s_tm_yday, tm.tm_yday,
823 s_tm_isdst, tm.tm_isdst
826 case ArrayFormat::TmVector:
828 struct tm tm;
829 toTm(tm);
830 return make_packed_array(
831 tm.tm_sec,
832 tm.tm_min,
833 tm.tm_hour,
834 tm.tm_mday,
835 tm.tm_mon,
836 tm.tm_year,
837 tm.tm_wday,
838 tm.tm_yday,
839 tm.tm_isdst
843 return empty_array();
846 bool DateTime::fromString(const String& input, req::ptr<TimeZone> tz,
847 const char* format /*=NUL*/,
848 bool throw_on_error /*= true*/) {
849 struct timelib_error_container *error;
850 timelib_time *t;
851 if (format) {
852 t = timelib_parse_from_format((char*)format, (char*)input.data(),
853 input.size(), &error, TimeZone::GetDatabase(),
854 TimeZone::GetTimeZoneInfoRaw);
855 } else {
856 t = timelib_strtotime((char*)input.data(), input.size(),
857 &error, TimeZone::GetDatabase(),
858 TimeZone::GetTimeZoneInfoRaw);
860 int error1 = error->error_count;
861 setLastErrors(error);
862 if (error1) {
863 timelib_time_dtor(t);
864 if (!throw_on_error) {
865 return false;
867 auto msg = folly::format(
868 "DateTime::__construct(): Failed to parse time string "
869 "({}) at position {} ({}): {}",
870 input,
871 error->error_messages[0].position,
872 error->error_messages[0].character,
873 error->error_messages[0].message
874 ).str();
875 SystemLib::throwExceptionObject(msg);
878 if (m_timestamp == -1) {
879 fromTimeStamp(0);
881 if (tz.get() && (input.size() <= 0 || input[0] != '@')) {
882 setTimezone(tz);
883 } else {
884 setTimezone(TimeZone::Current());
887 // needed if any date part is missing
888 timelib_fill_holes(t, m_time.get(), TIMELIB_NO_CLONE);
889 timelib_update_ts(t, m_tz->get());
891 int error2;
892 m_timestamp = timelib_date_to_int(t, &error2);
893 if (error1 || error2) {
894 // Don't free t->tz_info, it belongs to GetTimeZoneInfo
895 timelib_time_dtor(t);
896 return false;
899 m_time = TimePtr(t, time_deleter());
900 if (t->tz_info != m_tz->get()) {
901 m_tz = req::make<TimeZone>(t->tz_info);
903 return true;
906 req::ptr<DateTime> DateTime::cloneDateTime() const {
907 return req::make<DateTime>(*this);
910 ///////////////////////////////////////////////////////////////////////////////
911 // comparison
913 req::ptr<DateInterval>
914 DateTime::diff(req::ptr<DateTime> datetime2, bool absolute) {
915 timelib_rel_time *rel = timelib_diff(m_time.get(), datetime2.get()->m_time.get());
916 if (absolute) {
917 rel->invert = 0;
919 return req::make<DateInterval>(rel);
922 ///////////////////////////////////////////////////////////////////////////////
923 // sun
925 const StaticString
926 s_sunrise("sunrise"),
927 s_sunset("sunset"),
928 s_transit("transit"),
929 s_civil_twilight_begin("civil_twilight_begin"),
930 s_civil_twilight_end("civil_twilight_end"),
931 s_nautical_twilight_begin("nautical_twilight_begin"),
932 s_nautical_twilight_end("nautical_twilight_end"),
933 s_astronomical_twilight_begin("astronomical_twilight_begin"),
934 s_astronomical_twilight_end("astronomical_twilight_end");
936 Array DateTime::getSunInfo(double latitude, double longitude) const {
937 Array ret;
938 timelib_sll sunrise, sunset, transit;
939 double ddummy;
941 /* Get sun up/down and transit */
942 int rs = timelib_astro_rise_set_altitude(m_time.get(), longitude, latitude,
943 -35.0/60, 1, &ddummy, &ddummy,
944 &sunrise, &sunset, &transit);
945 switch (rs) {
946 case -1: /* always below */
947 ret.set(s_sunrise, false);
948 ret.set(s_sunset, false);
949 break;
950 case 1: /* always above */
951 ret.set(s_sunrise, true);
952 ret.set(s_sunset, true);
953 break;
954 default:
955 ret.set(s_sunrise, sunrise);
956 ret.set(s_sunset, sunset);
958 ret.set(s_transit, transit);
960 /* Get civil twilight */
961 rs = timelib_astro_rise_set_altitude(m_time.get(), longitude, latitude,
962 -6.0, 0,
963 &ddummy, &ddummy, &sunrise, &sunset,
964 &transit);
965 switch (rs) {
966 case -1: /* always below */
967 ret.set(s_civil_twilight_begin, false);
968 ret.set(s_civil_twilight_end, false);
969 break;
970 case 1: /* always above */
971 ret.set(s_civil_twilight_begin, true);
972 ret.set(s_civil_twilight_end, true);
973 break;
974 default:
975 ret.set(s_civil_twilight_begin, sunrise);
976 ret.set(s_civil_twilight_end, sunset);
979 /* Get nautical twilight */
980 rs = timelib_astro_rise_set_altitude(m_time.get(), longitude, latitude,
981 -12.0, 0,
982 &ddummy, &ddummy, &sunrise, &sunset,
983 &transit);
984 switch (rs) {
985 case -1: /* always below */
986 ret.set(s_nautical_twilight_begin, false);
987 ret.set(s_nautical_twilight_end, false);
988 break;
989 case 1: /* always above */
990 ret.set(s_nautical_twilight_begin, true);
991 ret.set(s_nautical_twilight_end, true);
992 break;
993 default:
994 ret.set(s_nautical_twilight_begin, sunrise);
995 ret.set(s_nautical_twilight_end, sunset);
998 /* Get astronomical twilight */
999 rs = timelib_astro_rise_set_altitude(m_time.get(), longitude, latitude,
1000 -18.0, 0,
1001 &ddummy, &ddummy, &sunrise, &sunset,
1002 &transit);
1003 switch (rs) {
1004 case -1: /* always below */
1005 ret.set(s_astronomical_twilight_begin, false);
1006 ret.set(s_astronomical_twilight_end, false);
1007 break;
1008 case 1: /* always above */
1009 ret.set(s_astronomical_twilight_begin, true);
1010 ret.set(s_astronomical_twilight_end, true);
1011 break;
1012 default:
1013 ret.set(s_astronomical_twilight_begin, sunrise);
1014 ret.set(s_astronomical_twilight_end, sunset);
1016 return ret;
1019 Variant DateTime::getSunInfo(SunInfoFormat retformat,
1020 double latitude, double longitude,
1021 double zenith, double utc_offset,
1022 bool calc_sunset) const {
1023 if (retformat != SunInfoFormat::ReturnTimeStamp &&
1024 retformat != SunInfoFormat::ReturnString &&
1025 retformat != SunInfoFormat::ReturnDouble) {
1026 raise_warning("Wrong return format given, pick one of "
1027 "SUNFUNCS_RET_TIMESTAMP, SUNFUNCS_RET_STRING or "
1028 "SUNFUNCS_RET_DOUBLE");
1029 return false;
1031 double altitude = 90 - zenith;
1032 double h_rise, h_set;
1033 timelib_sll sunrise, sunset, transit;
1034 int rs = timelib_astro_rise_set_altitude(m_time.get(), longitude, latitude,
1035 altitude, 1,
1036 &h_rise, &h_set, &sunrise, &sunset,
1037 &transit);
1038 if (rs != 0) {
1039 return false;
1042 if (retformat == SunInfoFormat::ReturnTimeStamp) {
1043 return calc_sunset ? sunset : sunrise;
1046 double N = (calc_sunset ? h_set : h_rise) + utc_offset;
1047 if (N > 24 || N < 0) {
1048 N -= floor(N / 24) * 24;
1051 if (retformat == SunInfoFormat::ReturnString) {
1052 char retstr[6];
1053 snprintf(retstr, sizeof(retstr),
1054 "%02d:%02d", (int) N, (int) (60 * (N - (int) N)));
1055 return String(retstr, CopyString);
1058 assert(retformat == SunInfoFormat::ReturnDouble);
1059 return N;
1062 ///////////////////////////////////////////////////////////////////////////////