hphp/runtime/base/datetime.cpp (1,030 lines of code) (raw):

/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #include "hphp/runtime/base/datetime.h" #include "hphp/runtime/base/dateinterval.h" #include "hphp/runtime/base/string-buffer.h" #include "hphp/runtime/base/runtime-error.h" #include "hphp/runtime/base/builtin-functions.h" #include "hphp/runtime/base/array-init.h" namespace HPHP { /////////////////////////////////////////////////////////////////////////////// // statics RDS_LOCAL(DateTime::LastErrors, DateTime::s_lastErrors); const char *DateTime::DateFormatRFC822 = "D, d M y H:i:s O"; const char *DateTime::DateFormatRFC850 = "l, d-M-y H:i:s T"; const char *DateTime::DateFormatRFC1036 = "D, d M y H:i:s O"; const char *DateTime::DateFormatRFC1123 = "D, d M Y H:i:s O"; const char *DateTime::DateFormatRFC2822 = "D, d M Y H:i:s O"; const char *DateTime::DateFormatRFC3339 = "Y-m-d\\TH:i:sP"; const char *DateTime::DateFormatISO8601 = "Y-m-d\\TH:i:sO"; const char *DateTime::DateFormatCookie = "D, d-M-Y H:i:s T"; const char *DateTime::DateFormatHttpHeader = "D, d M Y H:i:s T"; const char *DateTime::MonthNames[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; const char *DateTime::ShortMonthNames[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; const char *DateTime::WeekdayNames[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; const char *DateTime::ShortWeekdayNames[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; const char *DateTime::GetWeekdayName(int y, int m, int d) { int day_of_week = timelib_day_of_week(y, m, d); if (day_of_week < 0) { return "Unknown"; } return WeekdayNames[day_of_week]; } const char *DateTime::GetShortWeekdayName(int y, int m, int d) { int day_of_week = timelib_day_of_week(y, m, d); if (day_of_week < 0) { return "Unknown"; } return ShortWeekdayNames[day_of_week]; } const char *DateTime::OrdinalSuffix(int number) { if (number >= 10 && number <= 19) { return "th"; } switch (number % 10) { case 1: return "st"; case 2: return "nd"; case 3: return "rd"; } return "th"; } bool DateTime::IsLeap(int year) { // Aka "timelib_is_leap(year)" (from "timelib_private.h"). return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); } int DateTime::DaysInMonth(int y, int m) { return timelib_days_in_month(y, m); } bool DateTime::IsValid(int y, int m, int d) { return y >= 1 && y <= 32767 && m >= 1 && m <= 12 && d >= 1 && d <= timelib_days_in_month(y, m); } req::ptr<DateTime> DateTime::Current(bool utc /* = false */) { return req::make<DateTime>(time(0), utc); } const StaticString s_year("year"), s_month("month"), s_day("day"), s_hour("hour"), s_minute("minute"), s_second("second"), s_zone("zone"), s_zone_type("zone_type"), s_fraction("fraction"), s_warning_count("warning_count"), s_warnings("warnings"), s_error_count("error_count"), s_errors("errors"), s_is_localtime("is_localtime"), s_is_dst("is_dst"), s_tz_abbr("tz_abbr"), s_tz_id("tz_id"), s_weekday("weekday"), s_relative("relative"), s_tm_sec("tm_sec"), s_tm_min("tm_min"), s_tm_hour("tm_hour"), s_tm_mday("tm_mday"), s_tm_mon("tm_mon"), s_tm_year("tm_year"), s_tm_wday("tm_wday"), s_tm_yday("tm_yday"), s_tm_isdst("tm_isdst"), s_unparsed("unparsed"), s_seconds("seconds"), s_minutes("minutes"), s_hours("hours"), s_mday("mday"), s_wday("wday"), s_mon("mon"), s_yday("yday"); #define PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(name, elem) \ if ((int)parsed_time->elem == -99999) { \ ret.set(name, false); \ } else { \ ret.set(name, (int)parsed_time->elem); \ } Array DateTime::Parse(const String& datetime) { timelib_error_container* error; timelib_time* parsed_time = timelib_strtotime((char *)datetime.data(), datetime.size(), &error, TimeZone::GetDatabase(), TimeZone::GetTimeZoneInfoRaw); return DateTime::ParseTime(parsed_time, error); } Array DateTime::Parse(const String& format, const String& date) { timelib_error_container* error; timelib_time* parsed_time = timelib_parse_from_format((char *)format.data(), (char *)date.data(), date.size(), &error, TimeZone::GetDatabase(), TimeZone::GetTimeZoneInfoRaw); return DateTime::ParseTime(parsed_time, error); } Array DateTime::ParseAsStrptime(const String& format, const String& date) { struct tm parsed_time; memset(&parsed_time, 0, sizeof(parsed_time)); char* unparsed_part = strptime(date.data(), format.data(), &parsed_time); if (unparsed_part == nullptr) { return Array(); } return make_dict_array( s_tm_sec, parsed_time.tm_sec, s_tm_min, parsed_time.tm_min, s_tm_hour, parsed_time.tm_hour, s_tm_mday, parsed_time.tm_mday, s_tm_mon, parsed_time.tm_mon, s_tm_year, parsed_time.tm_year, s_tm_wday, parsed_time.tm_wday, s_tm_yday, parsed_time.tm_yday, s_unparsed, String(unparsed_part, CopyString) ); } Array DateTime::ParseTime(timelib_time* parsed_time, timelib_error_container* error) { auto ret = Array::CreateDict(); PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_year, y); PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_month, m); PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_day, d); PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_hour, h); PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_minute, i); PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_second, s); #if TIMELIB_VERSION >= TIMELIB_MODERN if (parsed_time->us == TIMELIB_UNSET) { ret.set(s_fraction, false); } else { ret.set(s_fraction, (double) parsed_time->us / 1000000.0); } #else if (parsed_time->f == -99999) { ret.set(s_fraction, false); } else { ret.set(s_fraction, parsed_time->f); } #endif setLastErrors(error); { Array warnings = DateTime::getLastWarnings(); ret.set(s_warning_count, warnings.size()); ret.set(s_warnings, warnings); } { Array errors = DateTime::getLastErrors(); ret.set(s_error_count, errors.size()); ret.set(s_errors, errors); } ret.set(s_is_localtime, (bool)parsed_time->is_localtime); if (parsed_time->is_localtime) { PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_zone_type, zone_type); switch (parsed_time->zone_type) { case TIMELIB_ZONETYPE_OFFSET: PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_zone, z); ret.set(s_is_dst, (bool)parsed_time->dst); break; case TIMELIB_ZONETYPE_ID: if (parsed_time->tz_abbr) { ret.set(s_tz_abbr, String(parsed_time->tz_abbr, CopyString)); } if (parsed_time->tz_info) { ret.set(s_tz_id, String(parsed_time->tz_info->name, CopyString)); } break; case TIMELIB_ZONETYPE_ABBR: PHP_DATE_PARSE_DATE_SET_TIME_ELEMENT(s_zone, z); ret.set(s_is_dst, (bool)parsed_time->dst); ret.set(s_tz_abbr, String(parsed_time->tz_abbr, CopyString)); break; } } if (parsed_time->have_relative) { auto element = make_dict_array( s_year, parsed_time->relative.y, s_month, parsed_time->relative.m, s_day, parsed_time->relative.d, s_hour, parsed_time->relative.h, s_minute, parsed_time->relative.i, s_second, parsed_time->relative.s ); if ( #if defined(TIMELIB_VERSION) parsed_time->relative.have_weekday_relative #else parsed_time->have_weekday_relative #endif ) { element.set(s_weekday, parsed_time->relative.weekday); } ret.set(s_relative, element); } timelib_time_dtor(parsed_time); return ret; } /////////////////////////////////////////////////////////////////////////////// // constructors DateTime::DateTime() : m_timestamp(-1), m_timestampSet(false) { m_time = TimePtr(timelib_time_ctor(), time_deleter()); setTimezone(TimeZone::Current()); } DateTime::DateTime(int64_t timestamp, bool utc /* = false */) { fromTimeStamp(timestamp, utc); } DateTime::DateTime(int64_t timestamp, req::ptr<TimeZone> tz) : m_tz(tz) { fromTimeStamp(timestamp); } DateTime::DateTime(const DateTime& dt) : m_tz(dt.m_tz), m_timestamp(dt.m_timestamp), m_timestampSet(dt.m_timestampSet) { auto t = timelib_time_clone(dt.m_time.get()); m_time = TimePtr(t, time_deleter()); } void DateTime::fromTimeStamp(int64_t timestamp, bool utc /* = false */) { m_timestamp = timestamp; m_timestampSet = true; timelib_time *t = timelib_time_ctor(); if (utc) { t->zone_type = TIMELIB_ZONETYPE_OFFSET; t->z = 0; timelib_unixtime2gmt(t, (timelib_sll)m_timestamp); } else { if (!m_tz.get()) { m_tz = TimeZone::Current(); } if (!m_tz->isValid()) { raise_error("No tz info found for timezone check for tzdata package."); } // We could get a correct result for this time stamp by just using // ZONETYPE_OFFSET and m_tz->offset(timestamp); the problem is that // potentially breaks if we do another timezone-sensitive operation // on the result. t->zone_type = m_tz->type(); switch (t->zone_type) { case TIMELIB_ZONETYPE_OFFSET: t->z = m_tz->offset(timestamp); break; case TIMELIB_ZONETYPE_ABBR: t->z = m_tz->offset(timestamp); t->dst = m_tz->dst(timestamp); t->tz_abbr = strdup(m_tz->abbr().data()); break; case TIMELIB_ZONETYPE_ID: t->tz_info = m_tz->getTZInfo(); break; } timelib_unixtime2local(t, (timelib_sll)m_timestamp); } m_time = TimePtr(t, time_deleter()); } void DateTime::sweep() { m_time.reset(); } /////////////////////////////////////////////////////////////////////////////// // informational int DateTime::beat() const { int retval = ((((long)m_time->sse)-(((long)m_time->sse) - ((((long)m_time->sse) % 86400) + 3600))) * 10); while (retval < 0) { retval += 864000; } retval = (retval / 864) % 1000; return retval; } int DateTime::dow() const { return timelib_day_of_week(year(), month(), day()); } int DateTime::doy() const { return timelib_day_of_year(year(), month(), day()); } int DateTime::isoWeek() const { timelib_sll iw, iy; timelib_isoweek_from_date(year(), month(), day(), &iw, &iy); return iw; } int DateTime::isoYear() const { timelib_sll iw, iy; timelib_isoweek_from_date(year(), month(), day(), &iw, &iy); return iy; } int DateTime::isoDow() const { return timelib_iso_day_of_week(year(), month(), day()); } int DateTime::offset() const { if (local()) { switch (m_time->zone_type) { case TIMELIB_ZONETYPE_ABBR: case TIMELIB_ZONETYPE_OFFSET: #if TIMELIB_VERSION >= TIMELIB_MODERN return m_time->z + m_time->dst * 3600; #else return (m_time->z - (m_time->dst * 60)) * -60; #endif default: { bool error; timelib_time_offset *offset = timelib_get_time_zone_info(toTimeStamp(error), m_tz->getTZInfo()); int ret = offset->offset; timelib_time_offset_dtor(offset); return ret; } } } return 0; } const char *DateTime::weekdayName() const { return GetWeekdayName(year(), month(), day()); } const char *DateTime::shortWeekdayName() const { return GetShortWeekdayName(year(), month(), day()); } const char *DateTime::monthName() const { return MonthNames[month() - 1]; } const char *DateTime::shortMonthName() const { return ShortMonthNames[month() - 1]; } /////////////////////////////////////////////////////////////////////////////// // modifications void DateTime::update() { if (utc()) { timelib_update_ts(m_time.get(), nullptr); } else { timelib_update_ts(m_time.get(), m_tz->getTZInfo()); } m_timestamp = 0; m_timestampSet = false; } void DateTime::set(int hou, int min, int sec, int mon, int day, int yea) { /* Fill in the new data */ if (yea != INT_MAX) { if (yea < 70) { yea += 2000; } else if (yea >= 70 && yea <= 100) { yea += 1900; } m_time->y = yea; } if (day != INT_MAX) m_time->d = day; if (mon != INT_MAX) m_time->m = mon; if (sec != INT_MAX) m_time->s = sec; if (min != INT_MAX) m_time->i = min; if (hou != INT_MAX) m_time->h = hou; update(); } void DateTime::setDate(int y, int m, int d) { m_time->y = y; m_time->m = m; m_time->d = d; update(); } void DateTime::setISODate(int y, int w, int d /* = 1 */) { m_time->y = y; m_time->m = 1; m_time->d = 1; m_time->relative.d = timelib_daynr_from_weeknr(y, w, d); m_time->have_relative = 1; update(); } void DateTime::setTime(int hour, int minute, int second) { m_time->h = hour; m_time->i = minute; m_time->s = second; update(); #if TIMELIB_VERSION >= TIMELIB_MODERN timelib_update_from_sse(m_time.get()); #endif } void DateTime::setTimezone(req::ptr<TimeZone> timezone) { if (!timezone) { return; } m_tz = timezone->cloneTimeZone(); if (!m_tz.get()) { return; } assertx(m_tz->isValid()); switch (m_tz->type()) { case TIMELIB_ZONETYPE_ID: timelib_set_timezone(m_time.get(), m_tz->getTZInfo()); break; case TIMELIB_ZONETYPE_OFFSET: timelib_set_timezone_from_offset(m_time.get(), m_tz->offset(0)); break; case TIMELIB_ZONETYPE_ABBR: { timelib_abbr_info abbr; abbr.utc_offset = m_tz->offset(0); abbr.abbr = strdup(m_tz->abbr().data()); abbr.dst = m_tz->dst(0); timelib_set_timezone_from_abbr(m_time.get(), abbr); break; } } timelib_unixtime2local(m_time.get(), m_time->sse); } bool DateTime::modify(const String& diff) { timelib_error_container* error = nullptr; timelib_time *tmp_time = timelib_strtotime((char*)diff.data(), diff.size(), &error, TimeZone::GetDatabase(), TimeZone::GetTimeZoneInfoRaw); SCOPE_EXIT { timelib_time_dtor(tmp_time); if (error) timelib_error_container_dtor(error); }; if (error && error->error_count > 0) { raise_warning("DateTime::modify(): Failed to parse time string (%s)" " at position %d (%c): %s", diff.c_str(), error->error_messages[0].position, error->error_messages[0].character, error->error_messages[0].message ); return false; } internalModify(tmp_time); return true; } void DateTime::internalModify(timelib_time *t) { // TIMELIB_UNSET (and other constants) defined in timelib.h // (see hhvm-third-party) if (t->y != TIMELIB_UNSET) { m_time->y = t->y; } if (t->m != TIMELIB_UNSET) { m_time->m = t->m; } if (t->d != TIMELIB_UNSET) { m_time->d = t->d; } if (t->h != TIMELIB_UNSET) { m_time->h = t->h; m_time->i = 0; m_time->s = 0; if (t->i != TIMELIB_UNSET) { m_time->i = t->i; if (t->s != TIMELIB_UNSET) { m_time->s = t->s; } } } #if TIMELIB_VERSION >= TIMELIB_MODERN if (t->us != TIMELIB_UNSET) { m_time->us = t->us; } #endif internalModifyRelative(&(t->relative), t->have_relative, 1); } void DateTime::internalModifyRelative(timelib_rel_time *rel, bool have_relative, int8_t bias) { m_time->relative.y = rel->y * bias; m_time->relative.m = rel->m * bias; m_time->relative.d = rel->d * bias; m_time->relative.h = rel->h * bias; m_time->relative.i = rel->i * bias; m_time->relative.s = rel->s * bias; #if TIMELIB_VERSION >= TIMELIB_MODERN m_time->relative.us = rel->us * bias; #endif m_time->relative.weekday = rel->weekday; m_time->have_relative = have_relative; m_time->relative.special = rel->special; m_time->relative.have_special_relative = rel->have_special_relative; m_time->relative.have_weekday_relative = rel->have_weekday_relative; m_time->relative.weekday_behavior = rel->weekday_behavior; m_time->relative.first_last_day_of = rel->first_last_day_of; m_time->sse_uptodate = 0; update(); timelib_update_from_sse(m_time.get()); } void DateTime::add(const req::ptr<DateInterval>& interval) { timelib_rel_time *rel = interval->get(); internalModifyRelative(rel, true, rel->invert ? -1 : 1); } void DateTime::sub(const req::ptr<DateInterval>& interval) { timelib_rel_time *rel = interval->get(); #if TIMELIB_VERSION >= TIMELIB_MODERN timelib_time* new_time; new_time = timelib_sub(m_time.get(), rel); m_timestamp = 0; m_timestampSet = false; m_time = TimePtr(new_time, time_deleter()); #else internalModifyRelative(rel, true, rel->invert ? 1 : -1); #endif } /////////////////////////////////////////////////////////////////////////////// // conversions void DateTime::toTm(struct tm &ta) const { // TODO: Fixme under MSVC! ta.tm_sec = second(); ta.tm_min = minute(); ta.tm_hour = hour(); ta.tm_mday = day(); ta.tm_mon = month() - 1; ta.tm_year = year() - 1900; ta.tm_wday = dow(); ta.tm_yday = doy(); if (utc()) { ta.tm_isdst = 0; #ifndef _MSC_VER ta.tm_gmtoff = 0; ta.tm_zone = "GMT"; #endif } else { timelib_time_offset *offset = timelib_get_time_zone_info(m_time->sse, m_time->tz_info); ta.tm_isdst = offset->is_dst; #ifndef _MSC_VER ta.tm_gmtoff = offset->offset; ta.tm_zone = offset->abbr; #endif timelib_time_offset_dtor(offset); } } int64_t DateTime::toTimeStamp(bool &err) const { err = false; if (!m_timestampSet) { int error; m_timestamp = timelib_date_to_int(m_time.get(), &error); if (error) { err = true; } else { m_timestampSet = true; } } return m_timestamp; } int64_t DateTime::toInteger(char format) const { bool error; switch (format) { case 'd': case 'j': return day(); case 'w': return dow(); case 'z': return doy(); case 'W': return isoWeek(); case 'm': case 'n': return month(); case 't': return DaysInMonth(year(), month()); case 'L': return DateTime::IsLeap(year()); case 'y': return (year() % 100); case 'Y': return year(); case 'B': return beat(); case 'g': case 'h': return hour12(); case 'H': case 'G': return hour(); case 'i': return minute(); case 's': return second(); case 'I': return (!utc() && m_tz->dst(toTimeStamp(error))) ? 1 : 0; case 'Z': return utc() ? 0 : m_tz->offset(toTimeStamp(error)); case 'U': return toTimeStamp(error); } raise_invalid_argument_warning("unknown format char: %d", (int)format); return -1; } String DateTime::toString(const String& format, bool stdc /* = false */) const { if (format.empty()) return String(); return stdc ? stdcFormat(format) : rfcFormat(format); } String DateTime::toString(DateFormat format) const { switch (format) { case DateFormat::RFC822: return rfcFormat(DateFormatRFC822); case DateFormat::RFC850: return rfcFormat(DateFormatRFC850); case DateFormat::RFC1036: return rfcFormat(DateFormatRFC1036); case DateFormat::RFC1123: return rfcFormat(DateFormatRFC1123); case DateFormat::RFC2822: return rfcFormat(DateFormatRFC2822); case DateFormat::RFC3339: return rfcFormat(DateFormatRFC3339); case DateFormat::ISO8601: return rfcFormat(DateFormatISO8601); case DateFormat::Cookie: return rfcFormat(DateFormatCookie); case DateFormat::HttpHeader: return rfcFormat(DateFormatHttpHeader); default: assertx(false); } raise_invalid_argument_warning("format: %d", static_cast<int>(format)); return String(); } String DateTime::rfcFormat(const String& format) const { StringBuffer s; bool rfc_colon = false; bool error; for (int i = 0; i < format.size(); i++) { switch (format.charAt(i)) { case 'd': s.printf("%02d", day()); break; case 'D': s.append(shortWeekdayName()); break; case 'j': s.append(day()); break; case 'l': s.append(weekdayName()); break; case 'S': s.append(OrdinalSuffix(day())); break; case 'w': s.append(dow()); break; case 'N': s.append(isoDow()); break; case 'z': s.append(doy()); break; case 'W': s.printf("%02d", isoWeek()); break; case 'o': s.append(isoYear()); break; case 'F': s.append(monthName()); break; case 'm': s.printf("%02d", month()); break; case 'M': s.append(shortMonthName()); break; case 'n': s.append(month()); break; case 't': s.append(DaysInMonth(year(), month())); break; case 'L': s.append(IsLeap(year())); break; case 'y': s.printf("%02d", year() % 100); break; case 'Y': s.printf("%s%04d", year() < 0 ? "-" : "", abs(year())); break; case 'a': s.append(hour() >= 12 ? "pm" : "am"); break; case 'A': s.append(hour() >= 12 ? "PM" : "AM"); break; case 'B': s.printf("%03d", beat()); break; case 'g': s.append((hour() % 12) ? (int)hour() % 12 : 12); break; case 'G': s.append(hour()); break; case 'h': s.printf("%02d", (hour() % 12) ? (int)hour() % 12 : 12); break; case 'H': s.printf("%02d", (int)hour()); break; case 'i': s.printf("%02d", (int)minute()); break; case 's': s.printf("%02d", (int)second()); break; #if TIMELIB_VERSION >= TIMELIB_MODERN case 'u': s.printf("%06d", (int)m_time->us); break; case 'v': s.printf("%03d", (int)(m_time->us / 1000)); break; #else case 'u': s.printf("%06d", (int)floor(fraction() * 1000000)); break; case 'v': s.printf("%03d", (int)floor(fraction() * 1000)); break; #endif case 'I': s.append(!utc() && m_tz->dst(toTimeStamp(error)) ? 1 : 0); break; case 'P': rfc_colon = true; /* break intentionally missing */ case 'O': if (utc()) { s.printf("+00%s00", rfc_colon ? ":" : ""); } else { int offset = this->offset(); s.printf("%c%02d%s%02d", (offset < 0 ? '-' : '+'), abs(offset / 3600), rfc_colon ? ":" : "", abs((offset % 3600) / 60)); } break; case 'T': if (utc()) { s.append("GMT"); } else { if (m_time->zone_type != TIMELIB_ZONETYPE_OFFSET) { s.append(m_time->tz_abbr); } else { #if TIMELIB_VERSION >= TIMELIB_MODERN auto offset = m_time->z; #else auto offset = m_time->z * -60; #endif char abbr[9] = {0}; snprintf(abbr, 9, "GMT%c%02d%02d", ((offset < 0) ? '-' : '+'), abs(offset / 3600) % 100, // % 100 to convince compiler we have 2 digits abs((offset % 3600) / 60)); s.append(abbr); } } break; case 'e': if (utc()) { s.append("UTC"); } else { if (m_time->zone_type != TIMELIB_ZONETYPE_OFFSET) { s.append(m_tz->name()); } else { #if TIMELIB_VERSION >= TIMELIB_MODERN auto offset = m_time->z; #else auto offset = m_time->z * -60; #endif char abbr[7] = {0}; snprintf(abbr, 7, "%c%02d:%02d", ((offset < 0) ? '-' : '+'), abs(offset / 3600) % 100, // % 100 to convince compiler we have 2 digits abs((offset % 3600) / 60)); s.append(abbr); } } break; case 'Z': s.append(utc() ? 0 : this->offset()); break; case 'c': if (utc()) { s.printf("%04d-%02d-%02dT%02d:%02d:%02d+00:00", year(), month(), day(), hour(), minute(), second()); } else { int offset = this->offset(); s.printf("%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d", year(), month(), day(), hour(), minute(), second(), (offset < 0 ? '-' : '+'), abs(offset / 3600), abs((offset % 3600) / 60)); } break; case 'r': if (utc()) { s.printf("%3s, %02d %3s %04d %02d:%02d:%02d +0000", shortWeekdayName(), day(), shortMonthName(), year(), hour(), minute(), second()); } else { int offset = this->offset(); s.printf("%3s, %02d %3s %04d %02d:%02d:%02d %c%02d%02d", shortWeekdayName(), day(), shortMonthName(), year(), hour(), minute(), second(), (offset < 0 ? '-' : '+'), abs(offset / 3600), abs((offset % 3600) / 60)); } break; case 'U': s.printf("%" PRId64, toTimeStamp(error)); break; case '\\': if (i < format.size()) i++; /* break intentionally missing */ default: s.append(format[i]); break; } } return s.detach(); } String DateTime::stdcFormat(const String& format) const { // TODO: Fixme under MSVC! struct tm ta; timelib_time_offset *offset = nullptr; ta.tm_sec = second(); ta.tm_min = minute(); ta.tm_hour = hour(); ta.tm_mday = day(); ta.tm_mon = month() - 1; ta.tm_year = year() - 1900; ta.tm_wday = dow(); ta.tm_yday = doy(); if (utc()) { ta.tm_isdst = 0; #ifndef _MSC_VER ta.tm_gmtoff = 0; ta.tm_zone = "GMT"; #endif } else { offset = timelib_get_time_zone_info(m_time->sse, m_time->tz_info); ta.tm_isdst = offset->is_dst; #ifndef _MSC_VER ta.tm_gmtoff = offset->offset; ta.tm_zone = offset->abbr; #endif } if ((ta.tm_sec < 0 || ta.tm_sec > 60) || (ta.tm_min < 0 || ta.tm_min > 59) || (ta.tm_hour < 0 || ta.tm_hour > 23) || (ta.tm_mday < 1 || ta.tm_mday > 31) || (ta.tm_mon < 0 || ta.tm_mon > 11) || (ta.tm_wday < 0 || ta.tm_wday > 6) || (ta.tm_yday < 0 || ta.tm_yday > 365)) { raise_invalid_argument_warning("argument: invalid time"); return String(); } int max_reallocs = 5; size_t buf_len = 256, real_len; char *buf = (char *)malloc(buf_len); while ((real_len = strftime(buf, buf_len, format.data(), &ta)) == buf_len || real_len == 0) { buf_len *= 2; free(buf); buf = (char *)malloc(buf_len); if (!--max_reallocs) { break; } } if (!utc()) { timelib_time_offset_dtor(offset); } if (real_len && real_len != buf_len) { return String(buf, real_len, AttachString); } free(buf); raise_invalid_argument_warning("format: (over internal buffer)"); return String(); } Array DateTime::toArray(ArrayFormat format) const { bool error; switch (format) { case ArrayFormat::TimeMap: return make_dict_array( s_seconds, second(), s_minutes, minute(), s_hours, hour(), s_mday, day(), s_wday, dow(), s_mon, month(), s_year, year(), s_yday, doy(), s_weekday, weekdayName(), s_month, monthName(), 0, toTimeStamp(error) ); case ArrayFormat::TmMap: { struct tm tm; toTm(tm); return make_dict_array( s_tm_sec, tm.tm_sec, s_tm_min, tm.tm_min, s_tm_hour, tm.tm_hour, s_tm_mday, tm.tm_mday, s_tm_mon, tm.tm_mon, s_tm_year, tm.tm_year, s_tm_wday, tm.tm_wday, s_tm_yday, tm.tm_yday, s_tm_isdst, tm.tm_isdst ); } case ArrayFormat::TmVector: { struct tm tm; toTm(tm); return make_vec_array( tm.tm_sec, tm.tm_min, tm.tm_hour, tm.tm_mday, tm.tm_mon, tm.tm_year, tm.tm_wday, tm.tm_yday, tm.tm_isdst ); } } return empty_vec_array(); } bool DateTime::fromString(const String& input, req::ptr<TimeZone> tz, const char* format /*=NUL*/, bool throw_on_error /*= true*/) { timelib_error_container *error; timelib_time *t; if (format) { t = timelib_parse_from_format((char*)format, (char*)input.data(), input.size(), &error, TimeZone::GetDatabase(), TimeZone::GetTimeZoneInfoRaw); } else { t = timelib_strtotime((char*)input.data(), input.size(), &error, TimeZone::GetDatabase(), TimeZone::GetTimeZoneInfoRaw); } int error1 = error->error_count; setLastErrors(error); if (error1) { timelib_time_dtor(t); if (!throw_on_error) { return false; } auto msg = folly::format( "DateTime::__construct(): Failed to parse time string " "({}) at position {} ({}): {}", input, error->error_messages[0].position, error->error_messages[0].character, error->error_messages[0].message ).str(); SystemLib::throwExceptionObject(msg); } if (m_timestamp == -1) { fromTimeStamp(0); } if (tz.get() && tz->isValid() && (input.size() <= 0 || input[0] != '@')) { setTimezone(tz); } else { setTimezone(TimeZone::Current()); } int options = TIMELIB_NO_CLONE; #if TIMELIB_VERSION >= TIMELIB_MODERN if (format != nullptr) { options |= TIMELIB_OVERRIDE_TIME; } #endif timelib_fill_holes(t, m_time.get(), options); timelib_update_ts(t, m_tz->getTZInfo()); timelib_update_from_sse(t); int error2; m_timestamp = timelib_date_to_int(t, &error2); if (error1 || error2) { // Don't free t->tz_info, it belongs to GetTimeZoneInfo timelib_time_dtor(t); return false; } m_time = TimePtr(t, time_deleter()); if (t->tz_info != m_tz->getTZInfo()) { m_tz = req::make<TimeZone>(t->tz_info); } return true; } req::ptr<DateTime> DateTime::cloneDateTime() const { return req::make<DateTime>(*this); } /////////////////////////////////////////////////////////////////////////////// // comparison req::ptr<DateInterval> DateTime::diff(req::ptr<DateTime> datetime2, bool absolute) { timelib_rel_time *rel = timelib_diff(m_time.get(), datetime2.get()->m_time.get()); if (absolute) { rel->invert = 0; } return req::make<DateInterval>(rel); } int DateTime::compare(req::ptr<DateTime> datetime2) { return timelib_time_compare(m_time.get(), datetime2.get()->m_time.get()); } /////////////////////////////////////////////////////////////////////////////// // sun const StaticString s_sunrise("sunrise"), s_sunset("sunset"), s_transit("transit"), s_civil_twilight_begin("civil_twilight_begin"), s_civil_twilight_end("civil_twilight_end"), s_nautical_twilight_begin("nautical_twilight_begin"), s_nautical_twilight_end("nautical_twilight_end"), s_astronomical_twilight_begin("astronomical_twilight_begin"), s_astronomical_twilight_end("astronomical_twilight_end"); Array DateTime::getSunInfo(double latitude, double longitude) const { Array ret = Array::CreateDict(); timelib_sll sunrise, sunset, transit; double ddummy; /* Get sun up/down and transit */ int rs = timelib_astro_rise_set_altitude(m_time.get(), longitude, latitude, -35.0/60, 1, &ddummy, &ddummy, &sunrise, &sunset, &transit); switch (rs) { case -1: /* always below */ ret.set(s_sunrise, false); ret.set(s_sunset, false); break; case 1: /* always above */ ret.set(s_sunrise, true); ret.set(s_sunset, true); break; default: ret.set(s_sunrise, sunrise); ret.set(s_sunset, sunset); } ret.set(s_transit, transit); /* Get civil twilight */ rs = timelib_astro_rise_set_altitude(m_time.get(), longitude, latitude, -6.0, 0, &ddummy, &ddummy, &sunrise, &sunset, &transit); switch (rs) { case -1: /* always below */ ret.set(s_civil_twilight_begin, false); ret.set(s_civil_twilight_end, false); break; case 1: /* always above */ ret.set(s_civil_twilight_begin, true); ret.set(s_civil_twilight_end, true); break; default: ret.set(s_civil_twilight_begin, sunrise); ret.set(s_civil_twilight_end, sunset); } /* Get nautical twilight */ rs = timelib_astro_rise_set_altitude(m_time.get(), longitude, latitude, -12.0, 0, &ddummy, &ddummy, &sunrise, &sunset, &transit); switch (rs) { case -1: /* always below */ ret.set(s_nautical_twilight_begin, false); ret.set(s_nautical_twilight_end, false); break; case 1: /* always above */ ret.set(s_nautical_twilight_begin, true); ret.set(s_nautical_twilight_end, true); break; default: ret.set(s_nautical_twilight_begin, sunrise); ret.set(s_nautical_twilight_end, sunset); } /* Get astronomical twilight */ rs = timelib_astro_rise_set_altitude(m_time.get(), longitude, latitude, -18.0, 0, &ddummy, &ddummy, &sunrise, &sunset, &transit); switch (rs) { case -1: /* always below */ ret.set(s_astronomical_twilight_begin, false); ret.set(s_astronomical_twilight_end, false); break; case 1: /* always above */ ret.set(s_astronomical_twilight_begin, true); ret.set(s_astronomical_twilight_end, true); break; default: ret.set(s_astronomical_twilight_begin, sunrise); ret.set(s_astronomical_twilight_end, sunset); } return ret; } Variant DateTime::getSunInfo(SunInfoFormat retformat, double latitude, double longitude, double zenith, double utc_offset, bool calc_sunset) const { if (retformat != SunInfoFormat::ReturnTimeStamp && retformat != SunInfoFormat::ReturnString && retformat != SunInfoFormat::ReturnDouble) { raise_warning("Wrong return format given, pick one of " "SUNFUNCS_RET_TIMESTAMP, SUNFUNCS_RET_STRING or " "SUNFUNCS_RET_DOUBLE"); return false; } double altitude = 90 - zenith; double h_rise, h_set; timelib_sll sunrise, sunset, transit; int rs = timelib_astro_rise_set_altitude(m_time.get(), longitude, latitude, altitude, 1, &h_rise, &h_set, &sunrise, &sunset, &transit); if (rs != 0) { return false; } if (retformat == SunInfoFormat::ReturnTimeStamp) { return calc_sunset ? sunset : sunrise; } double N = (calc_sunset ? h_set : h_rise) + utc_offset; if (N > 24 || N < 0) { N -= floor(N / 24) * 24; } if (retformat == SunInfoFormat::ReturnString) { char retstr[6]; snprintf(retstr, sizeof(retstr), "%02d:%02d", (int) N, (int) (60 * (N - (int) N))); return String(retstr, CopyString); } assertx(retformat == SunInfoFormat::ReturnDouble); return N; } /////////////////////////////////////////////////////////////////////////////// }