diff options
Diffstat (limited to 'src/mongo/util/time_support.cpp')
-rw-r--r-- | src/mongo/util/time_support.cpp | 1605 |
1 files changed, 795 insertions, 810 deletions
diff --git a/src/mongo/util/time_support.cpp b/src/mongo/util/time_support.cpp index 7d44ce64972..30639380555 100644 --- a/src/mongo/util/time_support.cpp +++ b/src/mongo/util/time_support.cpp @@ -59,964 +59,949 @@ #ifdef __sun // 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); +extern "C" time_t timegm(struct tm* const tmp); #endif namespace mongo { namespace { - template <typename Stream> Stream& streamPut(Stream& os, Microseconds us) { - return os << us.count() << "\xce\xbcs"; - } - - template <typename Stream> Stream& streamPut(Stream& os, Milliseconds ms) { - return os << ms.count() << "ms"; - } - - template <typename Stream> Stream& streamPut(Stream& os, Seconds s) { - return os << s.count() << 's'; - } -} // namespace - - std::ostream& operator<<(std::ostream& os, Microseconds us) { - return streamPut(os, us); - } - - std::ostream& operator<<(std::ostream& os, Milliseconds ms) { - return streamPut(os, ms); - } - std::ostream& operator<<(std::ostream& os, Seconds s) { - return streamPut(os, s); - } - - template <typename Allocator> - StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Microseconds us) { - return streamPut(os, us); - } - - template <typename Allocator> - StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Milliseconds ms) { - return streamPut(os, ms); - } - - template <typename Allocator> - StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Seconds s) { - return streamPut(os, s); - } - - template StringBuilderImpl<StackAllocator>& operator<<(StringBuilderImpl<StackAllocator>&, - Microseconds); - template StringBuilderImpl<StackAllocator>& operator<<(StringBuilderImpl<StackAllocator>&, - Milliseconds); - template StringBuilderImpl<StackAllocator>& operator<<(StringBuilderImpl<StackAllocator>&, - Seconds); - template StringBuilderImpl<TrivialAllocator>& operator<<(StringBuilderImpl<TrivialAllocator>&, - Microseconds); - template StringBuilderImpl<TrivialAllocator>& operator<<(StringBuilderImpl<TrivialAllocator>&, - Milliseconds); - template StringBuilderImpl<TrivialAllocator>& operator<<(StringBuilderImpl<TrivialAllocator>&, - Seconds); - - Date_t Date_t::max() { - return fromMillisSinceEpoch(std::numeric_limits<long long>::max()); - } - - Date_t Date_t::now() { - return fromMillisSinceEpoch(curTimeMillis64()); - } - - Date_t::Date_t(stdx::chrono::system_clock::time_point tp) : - millis(durationCount<Milliseconds>(tp - stdx::chrono::system_clock::from_time_t(0))) {} - - stdx::chrono::system_clock::time_point Date_t::toSystemTimePoint() const { - return stdx::chrono::system_clock::from_time_t(0) + toDurationSinceEpoch(); - } - - bool Date_t::isFormattable() const { - if (millis < 0) { - return false; - } - if (sizeof(time_t) == sizeof(int32_t)) { - return millis < 2147483647000LL; // "2038-01-19T03:14:07Z" - } - else { - return millis < 32535215999000LL; // "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; - - using std::string; +template <typename Stream> +Stream& streamPut(Stream& os, Microseconds us) { + return os << us.count() << "\xce\xbcs"; +} + +template <typename Stream> +Stream& streamPut(Stream& os, Milliseconds ms) { + return os << ms.count() << "ms"; +} + +template <typename Stream> +Stream& streamPut(Stream& os, Seconds s) { + return os << s.count() << 's'; +} +} // namespace - void time_t_to_Struct(time_t t, struct tm * buf , bool local) { +std::ostream& operator<<(std::ostream& os, Microseconds us) { + return streamPut(os, us); +} + +std::ostream& operator<<(std::ostream& os, Milliseconds ms) { + return streamPut(os, ms); +} +std::ostream& operator<<(std::ostream& os, Seconds s) { + return streamPut(os, s); +} + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Microseconds us) { + return streamPut(os, us); +} + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Milliseconds ms) { + return streamPut(os, ms); +} + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Seconds s) { + return streamPut(os, s); +} + +template StringBuilderImpl<StackAllocator>& operator<<(StringBuilderImpl<StackAllocator>&, + Microseconds); +template StringBuilderImpl<StackAllocator>& operator<<(StringBuilderImpl<StackAllocator>&, + Milliseconds); +template StringBuilderImpl<StackAllocator>& operator<<(StringBuilderImpl<StackAllocator>&, Seconds); +template StringBuilderImpl<TrivialAllocator>& operator<<(StringBuilderImpl<TrivialAllocator>&, + Microseconds); +template StringBuilderImpl<TrivialAllocator>& operator<<(StringBuilderImpl<TrivialAllocator>&, + Milliseconds); +template StringBuilderImpl<TrivialAllocator>& operator<<(StringBuilderImpl<TrivialAllocator>&, + Seconds); + +Date_t Date_t::max() { + return fromMillisSinceEpoch(std::numeric_limits<long long>::max()); +} + +Date_t Date_t::now() { + return fromMillisSinceEpoch(curTimeMillis64()); +} + +Date_t::Date_t(stdx::chrono::system_clock::time_point tp) + : millis(durationCount<Milliseconds>(tp - stdx::chrono::system_clock::from_time_t(0))) {} + +stdx::chrono::system_clock::time_point Date_t::toSystemTimePoint() const { + return stdx::chrono::system_clock::from_time_t(0) + toDurationSinceEpoch(); +} + +bool Date_t::isFormattable() const { + if (millis < 0) { + return false; + } + if (sizeof(time_t) == sizeof(int32_t)) { + return millis < 2147483647000LL; // "2038-01-19T03:14:07Z" + } else { + return millis < 32535215999000LL; // "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; + +using std::string; + +void time_t_to_Struct(time_t t, struct tm* buf, bool local) { #if defined(_WIN32) - if ( local ) - localtime_s( buf , &t ); - else - gmtime_s(buf, &t); + if (local) + localtime_s(buf, &t); + else + gmtime_s(buf, &t); #else - if ( local ) - localtime_r(&t, buf); - else - gmtime_r(&t, buf); + if (local) + localtime_r(&t, buf); + else + gmtime_r(&t, buf); #endif - } +} - std::string time_t_to_String_short(time_t t) { - char buf[64]; +std::string time_t_to_String_short(time_t t) { + char buf[64]; #if defined(_WIN32) - ctime_s(buf, sizeof(buf), &t); + ctime_s(buf, sizeof(buf), &t); #else - ctime_r(&t, buf); + 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; - } + 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 - string terseCurrentTime(bool colonsOk) { - struct tm t; - time_t_to_Struct( time(0) , &t ); +// uses ISO 8601 dates without trailing Z +// colonsOk should be false when creating filenames +string terseCurrentTime(bool colonsOk) { + struct tm t; + time_t_to_Struct(time(0), &t); - const char* fmt = (colonsOk ? "%Y-%m-%dT%H:%M:%S" : "%Y-%m-%dT%H-%M-%S"); - char buf[32]; - fassert(16226, strftime(buf, sizeof(buf), fmt, &t) == 19); - return buf; - } + const char* fmt = (colonsOk ? "%Y-%m-%dT%H:%M:%S" : "%Y-%m-%dT%H-%M-%S"); + char buf[32]; + fassert(16226, strftime(buf, sizeof(buf), fmt, &t) == 19); + return buf; +} #define MONGO_ISO_DATE_FMT_NO_TZ "%Y-%m-%dT%H:%M:%S" namespace { - struct DateStringBuffer { - static const int dataCapacity = 64; - char data[dataCapacity]; - int size; - }; - - void _dateToISOString(Date_t date, bool local, DateStringBuffer* result) { - invariant(date.isFormattable()); - static const int bufSize = DateStringBuffer::dataCapacity; - char* const buf = result->data; - struct tm t; - time_t_to_Struct(date.toTimeT(), &t, local); - int pos = strftime(buf, bufSize, MONGO_ISO_DATE_FMT_NO_TZ, &t); - dassert(0 < pos); - char* cur = buf + pos; - int bufRemaining = bufSize - pos; - pos = snprintf(cur, bufRemaining, ".%03d", static_cast<int32_t>(date.asInt64() % 1000)); - dassert(bufRemaining > pos && pos > 0); - cur += pos; - bufRemaining -= pos; - if (local) { - static const int localTzSubstrLen = 5; - dassert(bufRemaining >= localTzSubstrLen + 1); +struct DateStringBuffer { + static const int dataCapacity = 64; + char data[dataCapacity]; + int size; +}; + +void _dateToISOString(Date_t date, bool local, DateStringBuffer* result) { + invariant(date.isFormattable()); + static const int bufSize = DateStringBuffer::dataCapacity; + char* const buf = result->data; + struct tm t; + time_t_to_Struct(date.toTimeT(), &t, local); + int pos = strftime(buf, bufSize, MONGO_ISO_DATE_FMT_NO_TZ, &t); + dassert(0 < pos); + char* cur = buf + pos; + int bufRemaining = bufSize - pos; + pos = snprintf(cur, bufRemaining, ".%03d", static_cast<int32_t>(date.asInt64() % 1000)); + dassert(bufRemaining > pos && pos > 0); + cur += pos; + bufRemaining -= pos; + if (local) { + static const int localTzSubstrLen = 5; + dassert(bufRemaining >= localTzSubstrLen + 1); #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, localTzSubstrLen + 1, "%c%02ld%02ld", - tzIsWestOfUTC ? '-' : '+', - tzOffsetHoursPart, - tzOffsetMinutesPart); + // 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, + localTzSubstrLen + 1, + "%c%02ld%02ld", + tzIsWestOfUTC ? '-' : '+', + tzOffsetHoursPart, + tzOffsetMinutesPart); #else - strftime(cur, bufRemaining, "%z", &t); + strftime(cur, bufRemaining, "%z", &t); #endif - cur += localTzSubstrLen; - } - else { - dassert(bufRemaining >= 2); - *cur = 'Z'; - ++cur; - } - result->size = cur - buf; - dassert(result->size < DateStringBuffer::dataCapacity); - } - - void _dateToCtimeString(Date_t date, DateStringBuffer* result) { - static const size_t ctimeSubstrLen = 19; - static const size_t millisSubstrLen = 4; - time_t t = date.toTimeT(); + cur += localTzSubstrLen; + } else { + dassert(bufRemaining >= 2); + *cur = 'Z'; + ++cur; + } + result->size = cur - buf; + dassert(result->size < DateStringBuffer::dataCapacity); +} + +void _dateToCtimeString(Date_t date, DateStringBuffer* result) { + static const size_t ctimeSubstrLen = 19; + static const size_t millisSubstrLen = 4; + time_t t = date.toTimeT(); #if defined(_WIN32) - ctime_s(result->data, sizeof(result->data), &t); + ctime_s(result->data, sizeof(result->data), &t); #else - ctime_r(&t, result->data); + ctime_r(&t, result->data); #endif - char* milliSecStr = result->data + ctimeSubstrLen; - snprintf(milliSecStr, millisSubstrLen + 1, ".%03d", static_cast<int32_t>(date.asInt64() % 1000)); - result->size = ctimeSubstrLen + millisSubstrLen; - } + char* milliSecStr = result->data + ctimeSubstrLen; + snprintf( + milliSecStr, millisSubstrLen + 1, ".%03d", static_cast<int32_t>(date.asInt64() % 1000)); + result->size = ctimeSubstrLen + millisSubstrLen; +} } // namespace - std::string dateToISOStringUTC(Date_t date) { - DateStringBuffer buf; - _dateToISOString(date, false, &buf); - return std::string(buf.data, buf.size); - } - - std::string dateToISOStringLocal(Date_t date) { - DateStringBuffer buf; - _dateToISOString(date, true, &buf); - return std::string(buf.data, buf.size); - } - - std::string dateToCtimeString(Date_t date) { - DateStringBuffer buf; - _dateToCtimeString(date, &buf); - return std::string(buf.data, buf.size); - } - - void outputDateAsISOStringUTC(std::ostream& os, Date_t date) { - DateStringBuffer buf; - _dateToISOString(date, false, &buf); - os << StringData(buf.data, buf.size); - } - - void outputDateAsISOStringLocal(std::ostream& os, Date_t date) { - DateStringBuffer buf; - _dateToISOString(date, true, &buf); - os << StringData(buf.data, buf.size); - - } - - void outputDateAsCtime(std::ostream& os, Date_t date) { - DateStringBuffer buf; - _dateToCtimeString(date, &buf); - os << StringData(buf.data, buf.size); - } +std::string dateToISOStringUTC(Date_t date) { + DateStringBuffer buf; + _dateToISOString(date, false, &buf); + return std::string(buf.data, buf.size); +} + +std::string dateToISOStringLocal(Date_t date) { + DateStringBuffer buf; + _dateToISOString(date, true, &buf); + return std::string(buf.data, buf.size); +} + +std::string dateToCtimeString(Date_t date) { + DateStringBuffer buf; + _dateToCtimeString(date, &buf); + return std::string(buf.data, buf.size); +} + +void outputDateAsISOStringUTC(std::ostream& os, Date_t date) { + DateStringBuffer buf; + _dateToISOString(date, false, &buf); + os << StringData(buf.data, buf.size); +} + +void outputDateAsISOStringLocal(std::ostream& os, Date_t date) { + DateStringBuffer buf; + _dateToISOString(date, true, &buf); + os << StringData(buf.data, buf.size); +} + +void outputDateAsCtime(std::ostream& os, Date_t date) { + DateStringBuffer buf; + _dateToCtimeString(date, &buf); + os << StringData(buf.data, buf.size); +} namespace { - StringData getNextToken(StringData currentString, - 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(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; - } +StringData getNextToken(StringData currentString, + 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(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; } + return true; +} - Status parseTimeZoneFromToken(StringData tzStr, int* tzAdjSecs) { - - *tzAdjSecs = 0; +Status parseTimeZoneFromToken(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 { + if (!tzStr.empty()) { + if (tzStr[0] == 'Z') { + if (tzStr.size() != 1) { StringBuilder sb; - sb << "Invalid time zone string: \"" << tzStr - << "\". Found invalid character at the beginning of time " - << "zone specifier: " << tzStr[0]; + sb << "Found trailing characters in time zone specifier: " << tzStr; return Status(ErrorCodes::BadValue, sb.str()); } - } - else { - return Status(ErrorCodes::BadValue, "Missing required time zone specifier for date"); - } - - return Status::OK(); - } - - Status parseMillisFromToken( - StringData millisStr, - int* resultMillis) { - - *resultMillis = 0; - - if (!millisStr.empty()) { - if (millisStr.size() > 3 || !isOnlyDigits(millisStr)) { + } else if (tzStr[0] == '+' || tzStr[0] == '-') { + if (tzStr.size() != 5 || !isOnlyDigits(tzStr.substr(1, 4))) { StringBuilder sb; - sb << "Millisecond string should be at most three digits: " << millisStr; + sb << "Time zone adjustment string should be four digits: " << tzStr; return Status(ErrorCodes::BadValue, sb.str()); } - Status status = parseNumberFromStringWithBase(millisStr, 10, resultMillis); + // 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; } - // 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; + if (tzAdjHours < -23 || tzAdjHours > 23) { + StringBuilder sb; + sb << "Time zone hours adjustment out of range: " << tzAdjHours; + return Status(ErrorCodes::BadValue, sb.str()); } - *resultMillis = *resultMillis * millisMagnitude; + StringData tzMinutesStr = tzStr.substr(3, 2); + int tzAdjMinutes = 0; + status = parseNumberFromStringWithBase(tzMinutesStr, 10, &tzAdjMinutes); + if (!status.isOK()) { + return status; + } - if (*resultMillis < 0 || *resultMillis > 1000) { + if (tzAdjMinutes < 0 || tzAdjMinutes > 59) { StringBuilder sb; - sb << "Millisecond out of range: " << *resultMillis; + sb << "Time zone minutes adjustment out of range: " << tzAdjMinutes; return Status(ErrorCodes::BadValue, sb.str()); } - } - - return Status::OK(); - } - - Status parseTmFromTokens( - StringData yearStr, - StringData monthStr, - StringData dayStr, - StringData hourStr, - StringData minStr, - StringData secStr, - std::tm* resultTm) { - - memset(resultTm, 0, sizeof(*resultTm)); - // Parse year - if (yearStr.size() != 4 || !isOnlyDigits(yearStr)) { + // 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 << "Year string should be four digits: " << yearStr; + 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"); + } - 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()); - } + return Status::OK(); +} - resultTm->tm_year -= 1900; +Status parseMillisFromToken(StringData millisStr, int* resultMillis) { + *resultMillis = 0; - // Parse month - if (monthStr.size() != 2 || !isOnlyDigits(monthStr)) { + if (!millisStr.empty()) { + if (millisStr.size() > 3 || !isOnlyDigits(millisStr)) { StringBuilder sb; - sb << "Month string should be two digits: " << monthStr; + sb << "Millisecond string should be at most three digits: " << millisStr; return Status(ErrorCodes::BadValue, sb.str()); } - status = parseNumberFromStringWithBase(monthStr, 10, &resultTm->tm_mon); + Status status = parseNumberFromStringWithBase(millisStr, 10, resultMillis); 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()); + // 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; } - resultTm->tm_mon -= 1; + *resultMillis = *resultMillis* millisMagnitude; - // Parse day - if (dayStr.size() != 2 || !isOnlyDigits(dayStr)) { + if (*resultMillis < 0 || *resultMillis > 1000) { StringBuilder sb; - sb << "Day string should be two digits: " << dayStr; + sb << "Millisecond out of range: " << *resultMillis; 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()); - } + return Status::OK(); +} - // 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 parseTmFromTokens(StringData yearStr, + StringData monthStr, + StringData dayStr, + StringData hourStr, + StringData minStr, + StringData secStr, + std::tm* resultTm) { + memset(resultTm, 0, sizeof(*resultTm)); - status = parseNumberFromStringWithBase(hourStr, 10, &resultTm->tm_hour); - if (!status.isOK()) { - return status; - } + // Parse year + if (yearStr.size() != 4 || !isOnlyDigits(yearStr)) { + StringBuilder sb; + sb << "Year string should be four digits: " << yearStr; + return Status(ErrorCodes::BadValue, sb.str()); + } - 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()); - } + Status status = parseNumberFromStringWithBase(yearStr, 10, &resultTm->tm_year); + if (!status.isOK()) { + return status; + } - // Parse minute - if (minStr.size() != 2 || !isOnlyDigits(minStr)) { - StringBuilder sb; - sb << "Minute string should be two digits: " << minStr; - return Status(ErrorCodes::BadValue, sb.str()); - } + 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()); + } - status = parseNumberFromStringWithBase(minStr, 10, &resultTm->tm_min); - if (!status.isOK()) { - return status; - } + resultTm->tm_year -= 1900; - 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 month + if (monthStr.size() != 2 || !isOnlyDigits(monthStr)) { + StringBuilder sb; + sb << "Month string should be two digits: " << monthStr; + return Status(ErrorCodes::BadValue, sb.str()); + } - // Parse second if it exists - if (secStr.empty()) { - return Status::OK(); - } + status = parseNumberFromStringWithBase(monthStr, 10, &resultTm->tm_mon); + if (!status.isOK()) { + return status; + } - if (secStr.size() != 2 || !isOnlyDigits(secStr)) { - StringBuilder sb; - sb << "Second string should be two digits: " << secStr; - return Status(ErrorCodes::BadValue, sb.str()); - } + 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()); + } - status = parseNumberFromStringWithBase(secStr, 10, &resultTm->tm_sec); - if (!status.isOK()) { - return status; - } + resultTm->tm_mon -= 1; - 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()); - } + // Parse day + if (dayStr.size() != 2 || !isOnlyDigits(dayStr)) { + StringBuilder sb; + sb << "Day string should be two digits: " << dayStr; + return Status(ErrorCodes::BadValue, sb.str()); + } - return Status::OK(); + status = parseNumberFromStringWithBase(dayStr, 10, &resultTm->tm_mday); + if (!status.isOK()) { + return status; } - Status parseTm(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()); - } + 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()); + } - secStr = getNextToken(dateString, ".+-Z", minEnd + 1, &secEnd); + // Parse hour + if (hourStr.size() != 2 || !isOnlyDigits(hourStr)) { + StringBuilder sb; + sb << "Hour string should be two digits: " << hourStr; + return Status(ErrorCodes::BadValue, sb.str()); + } - // 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()); - } - } + status = parseNumberFromStringWithBase(hourStr, 10, &resultTm->tm_hour); + if (!status.isOK()) { + return status; + } - // 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()); - } + 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()); + } - millisStr = getNextToken(dateString, "+-Z", secEnd + 1, &millisEnd); + // Parse minute + if (minStr.size() != 2 || !isOnlyDigits(minStr)) { + StringBuilder sb; + sb << "Minute string should be two digits: " << minStr; + return Status(ErrorCodes::BadValue, sb.str()); + } - // 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()); - } - } + status = parseNumberFromStringWithBase(minStr, 10, &resultTm->tm_min); + if (!status.isOK()) { + return status; + } - // 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); - } + 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()); + } - Status status = parseTmFromTokens(yearStr, monthStr, dayStr, hourStr, minStr, secStr, - resultTm); - if (!status.isOK()) { - return status; - } + // Parse second if it exists + if (secStr.empty()) { + return Status::OK(); + } - status = parseTimeZoneFromToken(tzStr, tzAdjSecs); - if (!status.isOK()) { - return status; - } + if (secStr.size() != 2 || !isOnlyDigits(secStr)) { + StringBuilder sb; + sb << "Second string should be two digits: " << secStr; + return Status(ErrorCodes::BadValue, sb.str()); + } - status = parseMillisFromToken(millisStr, resultMillis); - if (!status.isOK()) { - return status; - } + status = parseNumberFromStringWithBase(secStr, 10, &resultTm->tm_sec); + if (!status.isOK()) { + return status; + } - return Status::OK(); + 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()); } -} // namespace + return Status::OK(); +} - StatusWith<Date_t> dateFromISOString(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()); - } +Status parseTm(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; - unsigned long long resultMillis = 0; + 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); -#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) { + // 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 << "Error converting Windows system time to file time for date: " << dateString - << ". Error code: " << GetLastError(); - return StatusWith<Date_t>(ErrorCodes::BadValue, sb.str()); + sb << "Invalid date: " << dateString << ". Ends with \"" << dateString[minEnd] + << "\" character"; + return Status(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; + secStr = getNextToken(dateString, ".+-Z", minEnd + 1, &secEnd); - // 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); - - if (resultMillis > static_cast<unsigned long long>(std::numeric_limits<long long>::max())) { - return {ErrorCodes::BadValue, - str::stream() << dateString << " is too far in the future"}; + // 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()); } - return Date_t::fromMillisSinceEpoch(static_cast<long long>(resultMillis)); } -#undef MONGO_ISO_DATE_FMT_NO_TZ - - std::string Date_t::toString() const { - if (isFormattable()) { - return dateToISOStringLocal(*this); - } - else { - return str::stream() << "Date(" << millis << ")"; + // 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()); } - } - - time_t Date_t::toTimeT() const { - const auto secs = millis / 1000; - verify(secs >= std::numeric_limits<time_t>::min()); - verify(secs <= std::numeric_limits<time_t>::max()); - return secs; - } - - boost::gregorian::date currentDate() { - boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); - return now.date(); - } - // parses time of day in "hh:mm" format assuming 'hh' is 00-23 - bool toPointInTime( const string& str , boost::posix_time::ptime* timeOfDay ) { - int hh = 0; - int mm = 0; - if ( 2 != sscanf( str.c_str() , "%d:%d" , &hh , &mm ) ) { - return false; - } + millisStr = getNextToken(dateString, "+-Z", secEnd + 1, &millisEnd); - // verify that time is well formed - if ( ( hh / 24 ) || ( mm / 60 ) ) { - return false; + // 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()); } - - boost::posix_time::ptime res( currentDate() , boost::posix_time::hours( hh ) + boost::posix_time::minutes( mm ) ); - *timeOfDay = res; - return true; } -#if defined(_WIN32) - void sleepsecs(int s) { - stdx::this_thread::sleep_for(Seconds(s)); + // 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); } - void sleepmillis(long long s) { - stdx::this_thread::sleep_for(Milliseconds(s)); + Status status = parseTmFromTokens(yearStr, monthStr, dayStr, hourStr, minStr, secStr, resultTm); + if (!status.isOK()) { + return status; } - void sleepmicros(long long s) { - stdx::this_thread::sleep_for(Microseconds(s)); - } -#else - void sleepsecs(int s) { - struct timespec t; - t.tv_sec = s; - t.tv_nsec = 0; - if ( nanosleep( &t , 0 ) ) { - std::cout << "nanosleep failed" << std::endl; - } - } - void sleepmicros(long long s) { - if ( s <= 0 ) - return; - struct timespec t; - t.tv_sec = (int)(s / 1000000); - t.tv_nsec = 1000 * ( s % 1000000 ); - struct timespec out; - if ( nanosleep( &t , &out ) ) { - std::cout << "nanosleep failed" << std::endl; - } - } - void sleepmillis(long long s) { - sleepmicros( s * 1000 ); - } -#endif - void sleepFor(const Milliseconds& time) { - sleepmillis(time.count()); + status = parseTimeZoneFromToken(tzStr, tzAdjSecs); + if (!status.isOK()) { + return status; } - void Backoff::nextSleepMillis(){ - - // Get the current time - unsigned long long currTimeMillis = curTimeMillis64(); - - int lastSleepMillis = _lastSleepMillis; - - if( _lastErrorTimeMillis == 0 || _lastErrorTimeMillis > currTimeMillis /* VM bugs exist */ ) - _lastErrorTimeMillis = currTimeMillis; - unsigned long long lastErrorTimeMillis = _lastErrorTimeMillis; - _lastErrorTimeMillis = currTimeMillis; - - lastSleepMillis = getNextSleepMillis(lastSleepMillis, currTimeMillis, lastErrorTimeMillis); - - // Store the last slept time - _lastSleepMillis = lastSleepMillis; - sleepmillis( lastSleepMillis ); + status = parseMillisFromToken(millisStr, resultMillis); + if (!status.isOK()) { + return status; } - int Backoff::getNextSleepMillis(int lastSleepMillis, unsigned long long currTimeMillis, - unsigned long long lastErrorTimeMillis) const { - // Backoff logic - - // Get the time since the last error - unsigned long long timeSinceLastErrorMillis = currTimeMillis - lastErrorTimeMillis; - - // Makes the cast below safe - verify( _resetAfterMillis >= 0 ); + return Status::OK(); +} - // If we haven't seen another error recently (3x the max wait time), reset our - // wait counter. - if( timeSinceLastErrorMillis > (unsigned)( _resetAfterMillis ) ) lastSleepMillis = 0; - - // Makes the test below sane - verify( _maxSleepMillis > 0 ); - - // Wait a power of two millis - if( lastSleepMillis == 0 ) lastSleepMillis = 1; - else lastSleepMillis = std::min( lastSleepMillis * 2, _maxSleepMillis ); +} // namespace - return lastSleepMillis; +StatusWith<Date_t> dateFromISOString(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()); } - extern long long jsTime_virtual_skew; - extern boost::thread_specific_ptr<long long> jsTime_virtual_thread_skew; - - // DO NOT TOUCH except for testing - void jsTimeVirtualSkew( long long skew ){ - jsTime_virtual_skew = skew; - } - long long getJSTimeVirtualSkew(){ - return jsTime_virtual_skew; - } + unsigned long long resultMillis = 0; - void jsTimeVirtualThreadSkew( long long skew ){ - jsTime_virtual_thread_skew.reset(new long long(skew)); - } - long long getJSTimeVirtualThreadSkew(){ - if(jsTime_virtual_thread_skew.get()){ - return *(jsTime_virtual_thread_skew.get()); - } - else return 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 - /** Date_t is milliseconds since epoch */ - Date_t jsTime() { - return Date_t::now() + - Milliseconds(getJSTimeVirtualThreadSkew()) + - Milliseconds(getJSTimeVirtualSkew()); - } + resultMillis += (tzAdjSecs * 1000); -#ifdef _WIN32 // no gettimeofday on windows - unsigned long long curTimeMillis64() { - using stdx::chrono::system_clock; - return static_cast<unsigned long long>( - durationCount<Milliseconds>(system_clock::now() - system_clock::from_time_t(0))); + if (resultMillis > static_cast<unsigned long long>(std::numeric_limits<long long>::max())) { + return {ErrorCodes::BadValue, str::stream() << dateString << " is too far in the future"}; } + return Date_t::fromMillisSinceEpoch(static_cast<long long>(resultMillis)); +} - static unsigned long long getFiletime() { - FILETIME ft; - GetSystemTimeAsFileTime(&ft); - return *reinterpret_cast<unsigned long long*>(&ft); - } +#undef MONGO_ISO_DATE_FMT_NO_TZ - static unsigned long long getPerfCounter() { - LARGE_INTEGER li; - QueryPerformanceCounter(&li); - return li.QuadPart; +std::string Date_t::toString() const { + if (isFormattable()) { + return dateToISOStringLocal(*this); + } else { + return str::stream() << "Date(" << millis << ")"; } +} - static unsigned long long baseFiletime = 0; - static unsigned long long basePerfCounter = 0; - static unsigned long long resyncInterval = 0; - static SimpleMutex _curTimeMicros64ReadMutex; - static SimpleMutex _curTimeMicros64ResyncMutex; - - typedef WINBASEAPI VOID (WINAPI *pGetSystemTimePreciseAsFileTime) - (_Out_ LPFILETIME lpSystemTimeAsFileTime); +time_t Date_t::toTimeT() const { + const auto secs = millis / 1000; + verify(secs >= std::numeric_limits<time_t>::min()); + verify(secs <= std::numeric_limits<time_t>::max()); + return secs; +} - static pGetSystemTimePreciseAsFileTime GetSystemTimePreciseAsFileTimeFunc; +boost::gregorian::date currentDate() { + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + return now.date(); +} - MONGO_INITIALIZER(Init32TimeSupport)(InitializerContext*) { - HINSTANCE kernelLib = LoadLibraryA("kernel32.dll"); - if (kernelLib) { - GetSystemTimePreciseAsFileTimeFunc = reinterpret_cast<pGetSystemTimePreciseAsFileTime> - (GetProcAddress(kernelLib, "GetSystemTimePreciseAsFileTime")); - } - - return Status::OK(); +// parses time of day in "hh:mm" format assuming 'hh' is 00-23 +bool toPointInTime(const string& str, boost::posix_time::ptime* timeOfDay) { + int hh = 0; + int mm = 0; + if (2 != sscanf(str.c_str(), "%d:%d", &hh, &mm)) { + return false; } - static unsigned long long resyncTime() { - stdx::lock_guard<SimpleMutex> lkResync(_curTimeMicros64ResyncMutex); - unsigned long long ftOld; - unsigned long long ftNew; - ftOld = ftNew = getFiletime(); - do { - ftNew = getFiletime(); - } while (ftOld == ftNew); // wait for filetime to change - - unsigned long long newPerfCounter = getPerfCounter(); - - // Make sure that we use consistent values for baseFiletime and basePerfCounter. - // - stdx::lock_guard<SimpleMutex> lkRead(_curTimeMicros64ReadMutex); - baseFiletime = ftNew; - basePerfCounter = newPerfCounter; - resyncInterval = 60 * SystemTickSource::get()->getTicksPerSecond(); - return newPerfCounter; + // verify that time is well formed + if ((hh / 24) || (mm / 60)) { + return false; } - unsigned long long curTimeMicros64() { + boost::posix_time::ptime res(currentDate(), + boost::posix_time::hours(hh) + boost::posix_time::minutes(mm)); + *timeOfDay = res; + return true; +} - // Windows 8/2012 & later support a <1us time function - if (GetSystemTimePreciseAsFileTimeFunc != NULL) { - FILETIME time; - GetSystemTimePreciseAsFileTimeFunc(&time); - return boost::date_time::winapi::file_time_to_microseconds(time); - } +#if defined(_WIN32) +void sleepsecs(int s) { + stdx::this_thread::sleep_for(Seconds(s)); +} + +void sleepmillis(long long s) { + stdx::this_thread::sleep_for(Milliseconds(s)); +} +void sleepmicros(long long s) { + stdx::this_thread::sleep_for(Microseconds(s)); +} +#else +void sleepsecs(int s) { + struct timespec t; + t.tv_sec = s; + t.tv_nsec = 0; + if (nanosleep(&t, 0)) { + std::cout << "nanosleep failed" << std::endl; + } +} +void sleepmicros(long long s) { + if (s <= 0) + return; + struct timespec t; + t.tv_sec = (int)(s / 1000000); + t.tv_nsec = 1000 * (s % 1000000); + struct timespec out; + if (nanosleep(&t, &out)) { + std::cout << "nanosleep failed" << std::endl; + } +} +void sleepmillis(long long s) { + sleepmicros(s * 1000); +} +#endif - // Get a current value for QueryPerformanceCounter; if it is not time to resync we will - // use this value. - // - unsigned long long perfCounter = getPerfCounter(); - - // Periodically resync the timer so that we don't let timer drift accumulate. Testing - // suggests that we drift by about one microsecond per minute, so resynching once per - // minute should keep drift to no more than one microsecond. - // - if ((perfCounter - basePerfCounter) > resyncInterval) { - perfCounter = resyncTime(); - } +void sleepFor(const Milliseconds& time) { + sleepmillis(time.count()); +} - // Make sure that we use consistent values for baseFiletime and basePerfCounter. - // - stdx::lock_guard<SimpleMutex> lkRead(_curTimeMicros64ReadMutex); +void Backoff::nextSleepMillis() { + // Get the current time + unsigned long long currTimeMillis = curTimeMillis64(); - // Compute the current time in FILETIME format by adding our base FILETIME and an offset - // from that time based on QueryPerformanceCounter. The math is (logically) to compute the - // fraction of a second elapsed since 'baseFiletime' by taking the difference in ticks - // and dividing by the tick frequency, then scaling this fraction up to units of 100 - // nanoseconds to match the FILETIME format. We do the multiplication first to avoid - // truncation while using only integer instructions. - // - unsigned long long computedTime = baseFiletime + - ((perfCounter - basePerfCounter) * 10 * 1000 * 1000) / - SystemTickSource::get()->getTicksPerSecond(); + int lastSleepMillis = _lastSleepMillis; - // Convert the computed FILETIME into microseconds since the Unix epoch (1/1/1970). - // - return boost::date_time::winapi::file_time_to_microseconds(computedTime); - } + if (_lastErrorTimeMillis == 0 || _lastErrorTimeMillis > currTimeMillis /* VM bugs exist */) + _lastErrorTimeMillis = currTimeMillis; + 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 + unsigned long long timeSinceLastErrorMillis = currTimeMillis - lastErrorTimeMillis; + + // Makes the cast below safe + verify(_resetAfterMillis >= 0); + + // If we haven't seen another error recently (3x the max wait time), reset our + // wait counter. + if (timeSinceLastErrorMillis > (unsigned)(_resetAfterMillis)) + lastSleepMillis = 0; + + // Makes the test below sane + verify(_maxSleepMillis > 0); + + // Wait a power of two millis + if (lastSleepMillis == 0) + lastSleepMillis = 1; + else + lastSleepMillis = std::min(lastSleepMillis * 2, _maxSleepMillis); + + return lastSleepMillis; +} + +extern long long jsTime_virtual_skew; +extern boost::thread_specific_ptr<long long> jsTime_virtual_thread_skew; + +// DO NOT TOUCH except for testing +void jsTimeVirtualSkew(long long skew) { + jsTime_virtual_skew = skew; +} +long long getJSTimeVirtualSkew() { + return jsTime_virtual_skew; +} + +void jsTimeVirtualThreadSkew(long long skew) { + jsTime_virtual_thread_skew.reset(new long long(skew)); +} +long long getJSTimeVirtualThreadSkew() { + if (jsTime_virtual_thread_skew.get()) { + return *(jsTime_virtual_thread_skew.get()); + } else + return 0; +} + +/** Date_t is milliseconds since epoch */ +Date_t jsTime() { + return Date_t::now() + Milliseconds(getJSTimeVirtualThreadSkew()) + + Milliseconds(getJSTimeVirtualSkew()); +} + +#ifdef _WIN32 // no gettimeofday on windows +unsigned long long curTimeMillis64() { + using stdx::chrono::system_clock; + return static_cast<unsigned long long>( + durationCount<Milliseconds>(system_clock::now() - system_clock::from_time_t(0))); +} + +static unsigned long long getFiletime() { + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + return *reinterpret_cast<unsigned long long*>(&ft); +} + +static unsigned long long getPerfCounter() { + LARGE_INTEGER li; + QueryPerformanceCounter(&li); + return li.QuadPart; +} + +static unsigned long long baseFiletime = 0; +static unsigned long long basePerfCounter = 0; +static unsigned long long resyncInterval = 0; +static SimpleMutex _curTimeMicros64ReadMutex; +static SimpleMutex _curTimeMicros64ResyncMutex; + +typedef WINBASEAPI VOID(WINAPI* pGetSystemTimePreciseAsFileTime)(_Out_ LPFILETIME + lpSystemTimeAsFileTime); + +static pGetSystemTimePreciseAsFileTime GetSystemTimePreciseAsFileTimeFunc; + +MONGO_INITIALIZER(Init32TimeSupport)(InitializerContext*) { + HINSTANCE kernelLib = LoadLibraryA("kernel32.dll"); + if (kernelLib) { + GetSystemTimePreciseAsFileTimeFunc = reinterpret_cast<pGetSystemTimePreciseAsFileTime>( + GetProcAddress(kernelLib, "GetSystemTimePreciseAsFileTime")); + } + + return Status::OK(); +} + +static unsigned long long resyncTime() { + stdx::lock_guard<SimpleMutex> lkResync(_curTimeMicros64ResyncMutex); + unsigned long long ftOld; + unsigned long long ftNew; + ftOld = ftNew = getFiletime(); + do { + ftNew = getFiletime(); + } while (ftOld == ftNew); // wait for filetime to change + + unsigned long long newPerfCounter = getPerfCounter(); + + // Make sure that we use consistent values for baseFiletime and basePerfCounter. + // + stdx::lock_guard<SimpleMutex> lkRead(_curTimeMicros64ReadMutex); + baseFiletime = ftNew; + basePerfCounter = newPerfCounter; + resyncInterval = 60 * SystemTickSource::get()->getTicksPerSecond(); + return newPerfCounter; +} + +unsigned long long curTimeMicros64() { + // Windows 8/2012 & later support a <1us time function + if (GetSystemTimePreciseAsFileTimeFunc != NULL) { + FILETIME time; + GetSystemTimePreciseAsFileTimeFunc(&time); + return boost::date_time::winapi::file_time_to_microseconds(time); + } + + // Get a current value for QueryPerformanceCounter; if it is not time to resync we will + // use this value. + // + unsigned long long perfCounter = getPerfCounter(); + + // Periodically resync the timer so that we don't let timer drift accumulate. Testing + // suggests that we drift by about one microsecond per minute, so resynching once per + // minute should keep drift to no more than one microsecond. + // + if ((perfCounter - basePerfCounter) > resyncInterval) { + perfCounter = resyncTime(); + } + + // Make sure that we use consistent values for baseFiletime and basePerfCounter. + // + stdx::lock_guard<SimpleMutex> lkRead(_curTimeMicros64ReadMutex); + + // Compute the current time in FILETIME format by adding our base FILETIME and an offset + // from that time based on QueryPerformanceCounter. The math is (logically) to compute the + // fraction of a second elapsed since 'baseFiletime' by taking the difference in ticks + // and dividing by the tick frequency, then scaling this fraction up to units of 100 + // nanoseconds to match the FILETIME format. We do the multiplication first to avoid + // truncation while using only integer instructions. + // + unsigned long long computedTime = baseFiletime + + ((perfCounter - basePerfCounter) * 10 * 1000 * 1000) / + SystemTickSource::get()->getTicksPerSecond(); + + // Convert the computed FILETIME into microseconds since the Unix epoch (1/1/1970). + // + return boost::date_time::winapi::file_time_to_microseconds(computedTime); +} #else #include <sys/time.h> - unsigned long long curTimeMillis64() { - timeval tv; - gettimeofday(&tv, NULL); - return ((unsigned long long)tv.tv_sec) * 1000 + tv.tv_usec / 1000; - } - - unsigned long long curTimeMicros64() { - timeval tv; - gettimeofday(&tv, NULL); - return (((unsigned long long) tv.tv_sec) * 1000*1000) + tv.tv_usec; - } +unsigned long long curTimeMillis64() { + timeval tv; + gettimeofday(&tv, NULL); + return ((unsigned long long)tv.tv_sec) * 1000 + tv.tv_usec / 1000; +} + +unsigned long long curTimeMicros64() { + timeval tv; + gettimeofday(&tv, NULL); + return (((unsigned long long)tv.tv_sec) * 1000 * 1000) + tv.tv_usec; +} #endif } // namespace mongo |