diff options
Diffstat (limited to 'src/mongo/util/time_support.cpp')
| -rw-r--r-- | src/mongo/util/time_support.cpp | 610 |
1 files changed, 590 insertions, 20 deletions
diff --git a/src/mongo/util/time_support.cpp b/src/mongo/util/time_support.cpp index 901cedbe6f8..8de951a9608 100644 --- a/src/mongo/util/time_support.cpp +++ b/src/mongo/util/time_support.cpp @@ -14,7 +14,7 @@ */ #include "mongo/platform/basic.h" -#include "mongo/platform/cstdint.h" + #include "mongo/util/time_support.h" #include <cstdio> @@ -24,16 +24,40 @@ #include <boost/thread/tss.hpp> #include <boost/thread/xtime.hpp> +#include "mongo/base/parse_number.h" +#include "mongo/bson/util/builder.h" +#include "mongo/platform/cstdint.h" #include "mongo/util/assert_util.h" #ifdef _WIN32 #include <boost/date_time/filetime_functions.hpp> #include "mongo/util/concurrency/mutex.h" #include "mongo/util/timer.h" + +// NOTE(schwerin): MSVC's _snprintf is not a drop-in replacement for C99's snprintf(). In +// particular, when the target buffer is too small, behaviors differ. Consult the documentation +// from MSDN and form the BSD or Linux man pages before using. +#define snprintf _snprintf +#endif + +#ifdef __sunos__ +// Some versions of Solaris do not have timegm defined, so fall back to our implementation when +// building on Solaris. See SERVER-13446. +extern "C" time_t +timegm(struct tm *const tmp); #endif namespace mongo { + bool Date_t::isFormatable() const { + if (sizeof(time_t) == sizeof(int32_t)) { + return millis < 2147483647000ULL; // "2038-01-19T03:14:07Z" + } + else { + return millis < 32535215999000ULL; // "3000-12-31T23:59:59Z" + } + } + // jsTime_virtual_skew is just for testing. a test command manipulates it. long long jsTime_virtual_skew = 0; boost::thread_specific_ptr<long long> jsTime_virtual_thread_skew; @@ -54,25 +78,30 @@ namespace mongo { #endif } + std::string time_t_to_String(time_t t) { + char buf[64]; #if defined(_WIN32) - void curTimeString(char* timeStr) { - boost::xtime xt; - boost::xtime_get(&xt, MONGO_BOOST_TIME_UTC); - time_t_to_String(xt.sec, timeStr); - - char* milliSecStr = timeStr + 19; - _snprintf(milliSecStr, 5, ".%03d", static_cast<int32_t>(xt.nsec / 1000000)); - } + ctime_s(buf, sizeof(buf), &t); #else - void curTimeString(char* timeStr) { - struct timeval tv; - gettimeofday(&tv, NULL); - time_t_to_String(tv.tv_sec, timeStr); - - char* milliSecStr = timeStr + 19; - snprintf(milliSecStr, 5, ".%03d", static_cast<int32_t>(tv.tv_usec / 1000)); + ctime_r(&t, buf); +#endif + buf[24] = 0; // don't want the \n + return buf; } + + std::string time_t_to_String_short(time_t t) { + char buf[64]; +#if defined(_WIN32) + ctime_s(buf, sizeof(buf), &t); +#else + ctime_r(&t, buf); #endif + buf[19] = 0; + if( buf[0] && buf[1] && buf[2] && buf[3] ) + return buf + 4; // skip day of week + return buf; + } + // uses ISO 8601 dates without trailing Z // colonsOk should be false when creating filenames @@ -86,16 +115,550 @@ namespace mongo { return buf; } +#define MONGO_ISO_DATE_FMT_NO_TZ "%Y-%m-%dT%H:%M:%S" string timeToISOString(time_t time) { struct tm t; time_t_to_Struct( time, &t ); - const char* fmt = "%Y-%m-%dT%H:%M:%SZ"; + const char* fmt = MONGO_ISO_DATE_FMT_NO_TZ "Z"; char buf[32]; fassert(16227, strftime(buf, sizeof(buf), fmt, &t) == 20); return buf; } + static inline std::string _dateToISOString(Date_t date, bool local) { + invariant(date.isFormatable()); + const int bufSize = 32; + char buf[bufSize]; + struct tm t; + time_t_to_Struct(date.toTimeT(), &t, local); + int pos = strftime(buf, bufSize, MONGO_ISO_DATE_FMT_NO_TZ, &t); + fassert(16981, 0 < pos); + char* cur = buf + pos; + int bufRemaining = bufSize - pos; + pos = snprintf(cur, bufRemaining, ".%03d", static_cast<int32_t>(date.asInt64() % 1000)); + fassert(16982, bufRemaining > pos && pos > 0); + cur += pos; + bufRemaining -= pos; + if (local) { + fassert(16983, bufRemaining >= 6); +#ifdef _WIN32 + // NOTE(schwerin): The value stored by _get_timezone is the value one adds to local time + // to get UTC. This is opposite of the ISO-8601 meaning of the timezone offset. + // NOTE(schwerin): Microsoft's timezone code always assumes US rules for daylight + // savings time. We can do no better without completely reimplementing localtime_s and + // related time library functions. + long msTimeZone; + _get_timezone(&msTimeZone); + if (t.tm_isdst) msTimeZone -= 3600; + const bool tzIsWestOfUTC = msTimeZone > 0; + const long tzOffsetSeconds = msTimeZone* (tzIsWestOfUTC ? 1 : -1); + const long tzOffsetHoursPart = tzOffsetSeconds / 3600; + const long tzOffsetMinutesPart = (tzOffsetSeconds / 60) % 60; + snprintf(cur, 6, "%c%02ld%02ld", + tzIsWestOfUTC ? '-' : '+', + tzOffsetHoursPart, + tzOffsetMinutesPart); +#else + strftime(cur, bufRemaining, "%z", &t); +#endif + } + else { + fassert(16984, bufRemaining >= 2); + *cur = 'Z'; + ++cur; + *cur = '\0'; + } + return buf; + } + + std::string dateToISOStringUTC(Date_t date) { + return _dateToISOString(date, false); + } + + std::string dateToISOStringLocal(Date_t date) { + return _dateToISOString(date, true); + } + +namespace { + StringData getNextToken(const StringData& currentString, + const StringData& terminalChars, + size_t startIndex, + size_t* endIndex) { + size_t index = startIndex; + + if (index == std::string::npos) { + *endIndex = std::string::npos; + return StringData(); + } + + for (; index < currentString.size(); index++) { + if (terminalChars.find(currentString[index]) != std::string::npos) { + break; + } + } + + // substr just returns the rest of the string if the length passed in is greater than the + // number of characters remaining, and since std::string::npos is the length of the largest + // possible string we know (std::string::npos - startIndex) is at least as long as the rest + // of the string. That means this handles both the case where we hit a terminating + // character and we want a substring, and the case where didn't and just want the rest of + // the string. + *endIndex = (index < currentString.size() ? index : std::string::npos); + return currentString.substr(startIndex, index - startIndex); + } + + // Check to make sure that the string only consists of digits + bool isOnlyDigits(const StringData& toCheck) { + StringData digits("0123456789"); + for (StringData::const_iterator iterator = toCheck.begin(); + iterator != toCheck.end(); iterator++) { + if (digits.find(*iterator) == std::string::npos) { + return false; + } + } + return true; + } + + Status parseTimeZoneFromToken(const StringData& tzStr, int* tzAdjSecs) { + + *tzAdjSecs = 0; + + if (!tzStr.empty()) { + if (tzStr[0] == 'Z') { + if (tzStr.size() != 1) { + StringBuilder sb; + sb << "Found trailing characters in time zone specifier: " << tzStr; + return Status(ErrorCodes::BadValue, sb.str()); + } + } + else if (tzStr[0] == '+' || tzStr[0] == '-') { + if (tzStr.size() != 5 || !isOnlyDigits(tzStr.substr(1, 4))) { + StringBuilder sb; + sb << "Time zone adjustment string should be four digits: " << tzStr; + return Status(ErrorCodes::BadValue, sb.str()); + } + + // Parse the hours component of the time zone offset. Note that + // parseNumberFromStringWithBase correctly handles the sign bit, so leave that in. + StringData tzHoursStr = tzStr.substr(0, 3); + int tzAdjHours = 0; + Status status = parseNumberFromStringWithBase(tzHoursStr, 10, &tzAdjHours); + if (!status.isOK()) { + return status; + } + + if (tzAdjHours < -23 || tzAdjHours > 23) { + StringBuilder sb; + sb << "Time zone hours adjustment out of range: " << tzAdjHours; + return Status(ErrorCodes::BadValue, sb.str()); + } + + StringData tzMinutesStr = tzStr.substr(3, 2); + int tzAdjMinutes = 0; + status = parseNumberFromStringWithBase(tzMinutesStr, 10, &tzAdjMinutes); + if (!status.isOK()) { + return status; + } + + if (tzAdjMinutes < 0 || tzAdjMinutes > 59) { + StringBuilder sb; + sb << "Time zone minutes adjustment out of range: " << tzAdjMinutes; + return Status(ErrorCodes::BadValue, sb.str()); + } + + // Use the sign that parseNumberFromStringWithBase found to determine if we need to + // flip the sign of our minutes component. Also, we need to flip the sign of our + // final result, because the offset passed in by the user represents how far off the + // time they are giving us is from UTC, which means that we have to go the opposite + // way to compensate and get the UTC time + *tzAdjSecs = (-1) * ((tzAdjHours < 0 ? -1 : 1) * (tzAdjMinutes * 60) + + (tzAdjHours * 60 * 60)); + + // Disallow adjustiment of 24 hours or more in either direction (should be checked + // above as the separate components of minutes and hours) + fassert(17318, *tzAdjSecs > -86400 && *tzAdjSecs < 86400); + } + else { + StringBuilder sb; + sb << "Invalid time zone string: \"" << tzStr + << "\". Found invalid character at the beginning of time " + << "zone specifier: " << tzStr[0]; + return Status(ErrorCodes::BadValue, sb.str()); + } + } + else { + return Status(ErrorCodes::BadValue, "Missing required time zone specifier for date"); + } + + return Status::OK(); + } + + Status parseMillisFromToken( + const StringData& millisStr, + int* resultMillis) { + + *resultMillis = 0; + + if (!millisStr.empty()) { + if (millisStr.size() > 3 || !isOnlyDigits(millisStr)) { + StringBuilder sb; + sb << "Millisecond string should be at most three digits: " << millisStr; + return Status(ErrorCodes::BadValue, sb.str()); + } + + Status status = parseNumberFromStringWithBase(millisStr, 10, resultMillis); + if (!status.isOK()) { + return status; + } + + // Treat the digits differently depending on how many there are. 1 digit = hundreds of + // milliseconds, 2 digits = tens of milliseconds, 3 digits = milliseconds. + int millisMagnitude = 1; + if (millisStr.size() == 2) { + millisMagnitude = 10; + } + else if (millisStr.size() == 1) { + millisMagnitude = 100; + } + + *resultMillis = *resultMillis * millisMagnitude; + + if (*resultMillis < 0 || *resultMillis > 1000) { + StringBuilder sb; + sb << "Millisecond out of range: " << *resultMillis; + return Status(ErrorCodes::BadValue, sb.str()); + } + } + + return Status::OK(); + } + + Status parseTmFromTokens( + const StringData& yearStr, + const StringData& monthStr, + const StringData& dayStr, + const StringData& hourStr, + const StringData& minStr, + const StringData& secStr, + std::tm* resultTm) { + + memset(resultTm, 0, sizeof(*resultTm)); + + // Parse year + if (yearStr.size() != 4 || !isOnlyDigits(yearStr)) { + StringBuilder sb; + sb << "Year string should be four digits: " << yearStr; + return Status(ErrorCodes::BadValue, sb.str()); + } + + Status status = parseNumberFromStringWithBase(yearStr, 10, &resultTm->tm_year); + if (!status.isOK()) { + return status; + } + + if (resultTm->tm_year < 1970 || resultTm->tm_year > 9999) { + StringBuilder sb; + sb << "Year out of range: " << resultTm->tm_year; + return Status(ErrorCodes::BadValue, sb.str()); + } + + resultTm->tm_year -= 1900; + + // Parse month + if (monthStr.size() != 2 || !isOnlyDigits(monthStr)) { + StringBuilder sb; + sb << "Month string should be two digits: " << monthStr; + return Status(ErrorCodes::BadValue, sb.str()); + } + + status = parseNumberFromStringWithBase(monthStr, 10, &resultTm->tm_mon); + if (!status.isOK()) { + return status; + } + + if (resultTm->tm_mon < 1 || resultTm->tm_mon > 12) { + StringBuilder sb; + sb << "Month out of range: " << resultTm->tm_mon; + return Status(ErrorCodes::BadValue, sb.str()); + } + + resultTm->tm_mon -= 1; + + // Parse day + if (dayStr.size() != 2 || !isOnlyDigits(dayStr)) { + StringBuilder sb; + sb << "Day string should be two digits: " << dayStr; + return Status(ErrorCodes::BadValue, sb.str()); + } + + status = parseNumberFromStringWithBase(dayStr, 10, &resultTm->tm_mday); + if (!status.isOK()) { + return status; + } + + if (resultTm->tm_mday < 1 || resultTm->tm_mday > 31) { + StringBuilder sb; + sb << "Day out of range: " << resultTm->tm_mday; + return Status(ErrorCodes::BadValue, sb.str()); + } + + // Parse hour + if (hourStr.size() != 2 || !isOnlyDigits(hourStr)) { + StringBuilder sb; + sb << "Hour string should be two digits: " << hourStr; + return Status(ErrorCodes::BadValue, sb.str()); + } + + status = parseNumberFromStringWithBase(hourStr, 10, &resultTm->tm_hour); + if (!status.isOK()) { + return status; + } + + if (resultTm->tm_hour < 0 || resultTm->tm_hour > 23) { + StringBuilder sb; + sb << "Hour out of range: " << resultTm->tm_hour; + return Status(ErrorCodes::BadValue, sb.str()); + } + + // Parse minute + if (minStr.size() != 2 || !isOnlyDigits(minStr)) { + StringBuilder sb; + sb << "Minute string should be two digits: " << minStr; + return Status(ErrorCodes::BadValue, sb.str()); + } + + status = parseNumberFromStringWithBase(minStr, 10, &resultTm->tm_min); + if (!status.isOK()) { + return status; + } + + if (resultTm->tm_min < 0 || resultTm->tm_min > 59) { + StringBuilder sb; + sb << "Minute out of range: " << resultTm->tm_min; + return Status(ErrorCodes::BadValue, sb.str()); + } + + // Parse second if it exists + if (secStr.empty()) { + return Status::OK(); + } + + if (secStr.size() != 2 || !isOnlyDigits(secStr)) { + StringBuilder sb; + sb << "Second string should be two digits: " << secStr; + return Status(ErrorCodes::BadValue, sb.str()); + } + + status = parseNumberFromStringWithBase(secStr, 10, &resultTm->tm_sec); + if (!status.isOK()) { + return status; + } + + if (resultTm->tm_sec < 0 || resultTm->tm_sec > 59) { + StringBuilder sb; + sb << "Second out of range: " << resultTm->tm_sec; + return Status(ErrorCodes::BadValue, sb.str()); + } + + return Status::OK(); + } + + Status parseTm(const StringData& dateString, + std::tm* resultTm, + int* resultMillis, + int* tzAdjSecs) { + size_t yearEnd = std::string::npos; + size_t monthEnd = std::string::npos; + size_t dayEnd = std::string::npos; + size_t hourEnd = std::string::npos; + size_t minEnd = std::string::npos; + size_t secEnd = std::string::npos; + size_t millisEnd = std::string::npos; + size_t tzEnd = std::string::npos; + StringData yearStr, monthStr, dayStr, hourStr, minStr, secStr, millisStr, tzStr; + + yearStr = getNextToken(dateString, "-", 0, &yearEnd); + monthStr = getNextToken(dateString, "-", yearEnd + 1, &monthEnd); + dayStr = getNextToken(dateString, "T", monthEnd + 1, &dayEnd); + hourStr = getNextToken(dateString, ":", dayEnd + 1, &hourEnd); + minStr = getNextToken(dateString, ":+-Z", hourEnd + 1, &minEnd); + + // Only look for seconds if the character we matched for the end of the minutes token is a + // colon + if (minEnd != std::string::npos && dateString[minEnd] == ':') { + // Make sure the string doesn't end with ":" + if (minEnd == dateString.size() - 1) { + StringBuilder sb; + sb << "Invalid date: " << dateString << ". Ends with \"" << dateString[minEnd] + << "\" character"; + return Status(ErrorCodes::BadValue, sb.str()); + } + + secStr = getNextToken(dateString, ".+-Z", minEnd + 1, &secEnd); + + // Make sure we actually got something for seconds, since here we know they are expected + if (secStr.empty()) { + StringBuilder sb; + sb << "Missing seconds in date: " << dateString; + return Status(ErrorCodes::BadValue, sb.str()); + } + } + + // Only look for milliseconds if the character we matched for the end of the seconds token + // is a period + if (secEnd != std::string::npos && dateString[secEnd] == '.') { + // Make sure the string doesn't end with "." + if (secEnd == dateString.size() - 1) { + StringBuilder sb; + sb << "Invalid date: " << dateString << ". Ends with \"" << dateString[secEnd] + << "\" character"; + return Status(ErrorCodes::BadValue, sb.str()); + } + + millisStr = getNextToken(dateString, "+-Z", secEnd + 1, &millisEnd); + + // Make sure we actually got something for millis, since here we know they are expected + if (millisStr.empty()) { + StringBuilder sb; + sb << "Missing seconds in date: " << dateString; + return Status(ErrorCodes::BadValue, sb.str()); + } + } + + // Now look for the time zone specifier depending on which prefix of the time we provided + if (millisEnd != std::string::npos) { + tzStr = getNextToken(dateString, "", millisEnd, &tzEnd); + } + else if (secEnd != std::string::npos && dateString[secEnd] != '.') { + tzStr = getNextToken(dateString, "", secEnd, &tzEnd); + } + else if (minEnd != std::string::npos && dateString[minEnd] != ':') { + tzStr = getNextToken(dateString, "", minEnd, &tzEnd); + } + + Status status = parseTmFromTokens(yearStr, monthStr, dayStr, hourStr, minStr, secStr, + resultTm); + if (!status.isOK()) { + return status; + } + + status = parseTimeZoneFromToken(tzStr, tzAdjSecs); + if (!status.isOK()) { + return status; + } + + status = parseMillisFromToken(millisStr, resultMillis); + if (!status.isOK()) { + return status; + } + + return Status::OK(); + } + +} // namespace + + StatusWith<Date_t> dateFromISOString(const StringData& dateString) { + std::tm theTime; + int millis = 0; + int tzAdjSecs = 0; + Status status = parseTm(dateString, &theTime, &millis, &tzAdjSecs); + if (!status.isOK()) { + return StatusWith<Date_t>(ErrorCodes::BadValue, status.reason()); + } + + unsigned long long resultMillis = 0; + +#if defined(_WIN32) + SYSTEMTIME dateStruct; + dateStruct.wMilliseconds = millis; + dateStruct.wSecond = theTime.tm_sec; + dateStruct.wMinute = theTime.tm_min; + dateStruct.wHour = theTime.tm_hour; + dateStruct.wDay = theTime.tm_mday; + dateStruct.wDayOfWeek = -1; /* ignored */ + dateStruct.wMonth = theTime.tm_mon + 1; + dateStruct.wYear = theTime.tm_year + 1900; + + // Output parameter for SystemTimeToFileTime + FILETIME fileTime; + + // the wDayOfWeek member of SYSTEMTIME is ignored by this function + if (SystemTimeToFileTime(&dateStruct, &fileTime) == 0) { + StringBuilder sb; + sb << "Error converting Windows system time to file time for date: " << dateString + << ". Error code: " << GetLastError(); + return StatusWith<Date_t>(ErrorCodes::BadValue, sb.str()); + } + + // The Windows FILETIME structure contains two parts of a 64-bit value representing the + // number of 100-nanosecond intervals since January 1, 1601 + unsigned long long windowsTimeOffset = + (static_cast<unsigned long long>(fileTime.dwHighDateTime) << 32) | + fileTime.dwLowDateTime; + + // There are 11644473600 seconds between the unix epoch and the windows epoch + // 100-nanoseconds = milliseconds * 10000 + unsigned long long epochDifference = 11644473600000 * 10000; + + // removes the diff between 1970 and 1601 + windowsTimeOffset -= epochDifference; + + // 1 milliseconds = 1000000 nanoseconds = 10000 100-nanosecond intervals + resultMillis = windowsTimeOffset / 10000; +#else + struct tm dateStruct = { 0 }; + dateStruct.tm_sec = theTime.tm_sec; + dateStruct.tm_min = theTime.tm_min; + dateStruct.tm_hour = theTime.tm_hour; + dateStruct.tm_mday = theTime.tm_mday; + dateStruct.tm_mon = theTime.tm_mon; + dateStruct.tm_year = theTime.tm_year; + dateStruct.tm_wday = 0; + dateStruct.tm_yday = 0; + + resultMillis = (1000 * static_cast<unsigned long long>(timegm(&dateStruct))) + millis; +#endif + + resultMillis += (tzAdjSecs * 1000); + + return StatusWith<Date_t>(resultMillis); + } + +#undef MONGO_ISO_DATE_FMT_NO_TZ + + void Date_t::toTm(tm* buf) { + time_t dtime = toTimeT(); +#if defined(_WIN32) + gmtime_s(buf, &dtime); +#else + gmtime_r(&dtime, buf); +#endif + } + + std::string Date_t::toString() const { + return time_t_to_String(toTimeT()); + } + + time_t Date_t::toTimeT() const { + verify((long long)millis >= 0); // TODO when millis is signed, delete + verify(((long long)millis/1000) < (std::numeric_limits<time_t>::max)()); + return millis / 1000; + } + + std::string dateToCtimeString(Date_t date) { + time_t t = date.toTimeT(); + char buf[64]; +#if defined(_WIN32) + ctime_s(buf, sizeof(buf), &t); +#else + ctime_r(&t, buf); +#endif + char* milliSecStr = buf + 19; + snprintf(milliSecStr, 5, ".%03d", static_cast<int32_t>(date.asInt64() % 1000)); + return buf; + } + boost::gregorian::date currentDate() { boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); return now.date(); @@ -208,6 +771,15 @@ namespace mongo { unsigned long long lastErrorTimeMillis = _lastErrorTimeMillis; _lastErrorTimeMillis = currTimeMillis; + lastSleepMillis = getNextSleepMillis(lastSleepMillis, currTimeMillis, lastErrorTimeMillis); + + // Store the last slept time + _lastSleepMillis = lastSleepMillis; + sleepmillis( lastSleepMillis ); + } + + int Backoff::getNextSleepMillis(int lastSleepMillis, unsigned long long currTimeMillis, + unsigned long long lastErrorTimeMillis) const { // Backoff logic // Get the time since the last error @@ -227,9 +799,7 @@ namespace mongo { if( lastSleepMillis == 0 ) lastSleepMillis = 1; else lastSleepMillis = std::min( lastSleepMillis * 2, _maxSleepMillis ); - // Store the last slept time - _lastSleepMillis = lastSleepMillis; - sleepmillis( lastSleepMillis ); + return lastSleepMillis; } extern long long jsTime_virtual_skew; |
