// Copyright 2010 Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/datetime.hpp" extern "C" { #include #include } #include "utils/optional.ipp" #include "utils/sanity.hpp" namespace datetime = utils::datetime; using utils::none; using utils::optional; namespace { /// Fake value for the current time. static optional< datetime::timestamp > mock_now = none; } // anonymous namespace /// Creates a zero time delta. datetime::delta::delta(void) : seconds(0), useconds(0) { } /// Creates a time delta. /// /// \param seconds_ The seconds in the delta. /// \param useconds_ The microseconds in the delta. datetime::delta::delta(const int64_t seconds_, const unsigned long useconds_) : seconds(seconds_), useconds(useconds_) { } /// Converts a time expressed in microseconds to a delta. /// /// \param useconds The amount of microseconds representing the delta. /// /// \return A new delta object. datetime::delta datetime::delta::from_microseconds(const int64_t useconds) { return delta(useconds / 1000000, useconds % 1000000); } /// Convers the delta to a flat representation expressed in microseconds. /// /// \return The amount of microseconds that corresponds to this delta. int64_t datetime::delta::to_microseconds(void) const { return seconds * 1000000 + useconds; } /// Checks if two time deltas are equal. /// /// \param other The object to compare to. /// /// \return True if the two time deltas are equals; false otherwise. bool datetime::delta::operator==(const datetime::delta& other) const { return seconds == other.seconds && useconds == other.useconds; } /// Checks if two time deltas are different. /// /// \param other The object to compare to. /// /// \return True if the two time deltas are different; false otherwise. bool datetime::delta::operator!=(const datetime::delta& other) const { return !(*this == other); } /// Adds a time delta to this one. /// /// \param other The time delta to add. /// /// \return The addition of this time delta with the other time delta. datetime::delta datetime::delta::operator+(const datetime::delta& other) const { return delta::from_microseconds(to_microseconds() + other.to_microseconds()); } /// Adds a time delta to this one and updates this with the result. /// /// \param other The time delta to add. /// /// \return The addition of this time delta with the other time delta. datetime::delta datetime::delta::operator+=(const datetime::delta& other) { *this = *this + other; return *this; } /// Injects the object into a stream. /// /// \param output The stream into which to inject the object. /// \param object The object to format. /// /// \return The output stream. std::ostream& datetime::operator<<(std::ostream& output, const delta& object) { return (output << object.to_microseconds() << "us"); } namespace utils { namespace datetime { /// Internal representation for datetime::timestamp. struct timestamp::impl { /// The raw timestamp as provided by libc. ::timeval data; /// Constructs an impl object from initialized data. /// /// \param data_ The raw timestamp to use. impl(const ::timeval& data_) : data(data_) { } }; } // namespace datetime } // namespace utils /// Constructs a new timestamp. /// /// \param pimpl_ An existing impl representation. datetime::timestamp::timestamp(std::shared_ptr< impl > pimpl_) : _pimpl(pimpl_) { } /// Constructs a timestamp from the amount of microseconds since the epoch. /// /// \param value Microseconds since the epoch in UTC. Must be positive. /// /// \return A new timestamp. datetime::timestamp datetime::timestamp::from_microseconds(const int64_t value) { PRE(value >= 0); ::timeval data; data.tv_sec = static_cast< time_t >(value / 1000000); data.tv_usec = static_cast< suseconds_t >(value % 1000000); return timestamp(std::shared_ptr< impl >(new impl(data))); } /// Constructs a timestamp based on user-friendly values. /// /// \param year The year in the [1900,inf) range. /// \param month The month in the [1,12] range. /// \param day The day in the [1,30] range. /// \param hour The hour in the [0,23] range. /// \param minute The minute in the [0,59] range. /// \param second The second in the [0,60] range. Yes, that is 60, which can be /// the case on leap seconds. /// \param microsecond The microsecond in the [0,999999] range. /// /// \return A new timestamp. datetime::timestamp datetime::timestamp::from_values(const int year, const int month, const int day, const int hour, const int minute, const int second, const int microsecond) { PRE(year >= 1900); PRE(month >= 1 && month <= 12); PRE(day >= 1 && day <= 30); PRE(hour >= 0 && hour <= 23); PRE(minute >= 0 && minute <= 59); PRE(second >= 0 && second <= 60); PRE(microsecond >= 0 && microsecond <= 999999); // The code below is quite convoluted. The problem is that we can't assume // that some fields (like tm_zone) of ::tm exist, and thus we can't blindly // set them from the code. Instead of detecting their presence in the // configure script, we just query the current time to initialize such // fields and then we override the ones we are interested in. (There might // be some better way to do this, but I don't know it and the documentation // does not shed much light into how to create your own fake date.) const time_t current_time = ::time(NULL); ::tm timedata; if (::gmtime_r(¤t_time, &timedata) == NULL) UNREACHABLE; timedata.tm_sec = second; timedata.tm_min = minute; timedata.tm_hour = hour; timedata.tm_mday = day; timedata.tm_mon = month - 1; timedata.tm_year = year - 1900; // Ignored: timedata.tm_wday // Ignored: timedata.tm_yday ::timeval data; data.tv_sec = ::mktime(&timedata); data.tv_usec = static_cast< suseconds_t >(microsecond); return timestamp(std::shared_ptr< impl >(new impl(data))); } /// Constructs a new timestamp representing the current time in UTC. /// /// \return A new timestamp. datetime::timestamp datetime::timestamp::now(void) { if (mock_now) return mock_now.get(); ::timeval data; { const int ret = ::gettimeofday(&data, NULL); INV(ret != -1); } return timestamp(std::shared_ptr< impl >(new impl(data))); } /// Formats a timestamp. /// /// \param format The format string to use as consumed by strftime(3). /// /// \return The formatted time. std::string datetime::timestamp::strftime(const std::string& format) const { ::tm timedata; // This conversion to time_t is necessary because tv_sec is not guaranteed // to be a time_t. For example, it isn't in NetBSD 5.x ::time_t epoch_seconds; epoch_seconds = _pimpl->data.tv_sec; if (::gmtime_r(&epoch_seconds, &timedata) == NULL) UNREACHABLE_MSG("gmtime_r(3) did not accept the value returned by " "gettimeofday(2)"); char buf[128]; if (::strftime(buf, sizeof(buf), format.c_str(), &timedata) == 0) UNREACHABLE_MSG("Arbitrary-long format strings are unimplemented"); return buf; } /// Returns the number of microseconds since the epoch in UTC. /// /// \return A number of microseconds. int64_t datetime::timestamp::to_microseconds(void) const { return static_cast< int64_t >(_pimpl->data.tv_sec) * 1000000 + _pimpl->data.tv_usec; } /// Returns the number of seconds since the epoch in UTC. /// /// \return A number of seconds. int64_t datetime::timestamp::to_seconds(void) const { return static_cast< int64_t >(_pimpl->data.tv_sec); } /// Sets the current time for testing purposes. void datetime::set_mock_now(const int year, const int month, const int day, const int hour, const int minute, const int second, const int microsecond) { mock_now = timestamp::from_values(year, month, day, hour, minute, second, microsecond); } /// Checks if two timestamps are equal. /// /// \param other The object to compare to. /// /// \return True if the two timestamps are equals; false otherwise. bool datetime::timestamp::operator==(const datetime::timestamp& other) const { return _pimpl->data.tv_sec == other._pimpl->data.tv_sec && _pimpl->data.tv_usec == other._pimpl->data.tv_usec; } /// Checks if two timestamps are different. /// /// \param other The object to compare to. /// /// \return True if the two timestamps are different; false otherwise. bool datetime::timestamp::operator!=(const datetime::timestamp& other) const { return !(*this == other); } /// Calculates the delta between two timestamps. /// /// \param other The subtrahend. /// /// \return The difference between this object and the other object. datetime::delta datetime::timestamp::operator-(const datetime::timestamp& other) const { return datetime::delta::from_microseconds(to_microseconds() - other.to_microseconds()); } /// Injects the object into a stream. /// /// \param output The stream into which to inject the object. /// \param object The object to format. /// /// \return The output stream. std::ostream& datetime::operator<<(std::ostream& output, const timestamp& object) { return (output << object.to_microseconds() << "us"); }