/************************************************************************* * * Copyright 2016 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **************************************************************************/ #ifndef REALM_TIMESTAMP_HPP #define REALM_TIMESTAMP_HPP #include #include #include #include #include #include namespace realm { class Timestamp { public: // Construct from the number of seconds and nanoseconds since the UNIX epoch: 00:00:00 UTC on 1 January 1970 // // To split a native nanosecond representation, only division and modulo are necessary: // // s = native_nano / nanoseconds_per_second // n = native_nano % nanoseconds_per_second // Timestamp ts(s, n); // // To convert back into native nanosecond representation, simple multiply and add: // // native_nano = ts.s * nanoseconds_per_second + ts.n // // Specifically this allows the nanosecond part to become negative (only) for Timestamps before the UNIX epoch. // Usually this will not need special attention, but for reference, valid Timestamps will have one of the // following sign combinations: // // s | n // ----- // + | + // + | 0 // 0 | + // 0 | 0 // 0 | - // - | 0 // - | - // // Examples: // The UNIX epoch is constructed by Timestamp(0, 0) // Relative times are constructed as follows: // +1 second is constructed by Timestamp(1, 0) // +1 nanosecond is constructed by Timestamp(0, 1) // +1.1 seconds (1100 milliseconds after the epoch) is constructed by Timestamp(1, 100000000) // -1.1 seconds (1100 milliseconds before the epoch) is constructed by Timestamp(-1, -100000000) // Timestamp(int64_t seconds, int32_t nanoseconds) : m_seconds(seconds) , m_nanoseconds(nanoseconds) , m_is_null(false) { REALM_ASSERT_EX(-nanoseconds_per_second < nanoseconds && nanoseconds < nanoseconds_per_second, nanoseconds); const bool both_non_negative = seconds >= 0 && nanoseconds >= 0; const bool both_non_positive = seconds <= 0 && nanoseconds <= 0; REALM_ASSERT_EX(both_non_negative || both_non_positive, both_non_negative, both_non_positive); } Timestamp(realm::null) : m_is_null(true) { } template Timestamp(std::chrono::time_point tp) : m_is_null(false) { int64_t native_nano = std::chrono::duration_cast(tp.time_since_epoch()).count(); m_seconds = native_nano / nanoseconds_per_second; m_nanoseconds = static_cast(native_nano % nanoseconds_per_second); } Timestamp() : Timestamp(null{}) { } bool is_null() const { return m_is_null; } int64_t get_seconds() const noexcept { REALM_ASSERT(!m_is_null); return m_seconds; } int32_t get_nanoseconds() const noexcept { REALM_ASSERT(!m_is_null); return m_nanoseconds; } template std::chrono::time_point get_time_point() const { REALM_ASSERT(!m_is_null); int64_t native_nano = m_seconds * nanoseconds_per_second + m_nanoseconds; auto duration = std::chrono::duration_cast(std::chrono::duration{native_nano}); return std::chrono::time_point(duration); } template explicit operator std::chrono::time_point() const { return get_time_point(); } bool operator==(const Timestamp& rhs) const { if (is_null() && rhs.is_null()) return true; if (is_null() != rhs.is_null()) return false; return m_seconds == rhs.m_seconds && m_nanoseconds == rhs.m_nanoseconds; } bool operator!=(const Timestamp& rhs) const { return !(*this == rhs); } bool operator>(const Timestamp& rhs) const { if (is_null()) { return false; } if (rhs.is_null()) { return true; } return (m_seconds > rhs.m_seconds) || (m_seconds == rhs.m_seconds && m_nanoseconds > rhs.m_nanoseconds); } bool operator<(const Timestamp& rhs) const { if (rhs.is_null()) { return false; } if (is_null()) { return true; } return (m_seconds < rhs.m_seconds) || (m_seconds == rhs.m_seconds && m_nanoseconds < rhs.m_nanoseconds); } bool operator<=(const Timestamp& rhs) const { if (is_null()) { return true; } if (rhs.is_null()) { return false; } return *this < rhs || *this == rhs; } bool operator>=(const Timestamp& rhs) const { if (rhs.is_null()) { return true; } if (is_null()) { return false; } return *this > rhs || *this == rhs; } Timestamp& operator=(const Timestamp& rhs) = default; size_t hash() const noexcept; template friend std::basic_ostream& operator<<(std::basic_ostream& out, const Timestamp&); static constexpr int32_t nanoseconds_per_second = 1000000000; private: int64_t m_seconds; int32_t m_nanoseconds; bool m_is_null; }; // LCOV_EXCL_START template inline std::basic_ostream& operator<<(std::basic_ostream& out, const Timestamp& d) { auto seconds = time_t(d.get_seconds()); struct tm buf; #ifdef _MSC_VER bool success = gmtime_s(&buf, &seconds) == 0; #else bool success = gmtime_r(&seconds, &buf) != nullptr; #endif if (success) { // We need a buffer for formatting dates. // Max size is 20 bytes (incl terminating zero) "YYYY-MM-DD HH:MM:SS"\0 char buffer[30]; if (strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &buf)) { out << buffer; } } return out; } // LCOV_EXCL_STOP inline size_t Timestamp::hash() const noexcept { return size_t(m_seconds) ^ size_t(m_nanoseconds); } } // namespace realm namespace std { template <> struct numeric_limits { static constexpr bool is_integer = false; static realm::Timestamp min() { return realm::Timestamp(numeric_limits::min(), 0); } static realm::Timestamp lowest() { return realm::Timestamp(numeric_limits::lowest(), 0); } static realm::Timestamp max() { return realm::Timestamp(numeric_limits::max(), 0); } }; } #endif // REALM_TIMESTAMP_HPP