From 22c226af3f5570514d3d13d82a399577ecd7d280 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Tue, 27 Mar 2018 11:50:03 -0400 Subject: C++ migration: make Timestamp class a part of public API (#944) * move Timestamp from model/ to the root directory; * move Timestamp to top-level firebase namespace and update all references; * add conversions to and from native date types; * add a specialization of std::hash; * add comments to public member functions; * rename nanos -> nanoseconds; * add public headers, including Timestamp, to CMake; * increase test coverage. --- .../Example/Firestore.xcodeproj/project.pbxproj | 6 +- Firestore/core/CMakeLists.txt | 2 + .../core/include/firebase/firestore/CMakeLists.txt | 25 ++ .../core/include/firebase/firestore/timestamp.h | 221 +++++++++++++++++ .../core/src/firebase/firestore/CMakeLists.txt | 8 + .../src/firebase/firestore/model/CMakeLists.txt | 2 - .../src/firebase/firestore/model/field_value.h | 2 +- .../firebase/firestore/model/snapshot_version.h | 2 +- .../core/src/firebase/firestore/model/timestamp.cc | 54 ---- .../core/src/firebase/firestore/model/timestamp.h | 94 ------- Firestore/core/src/firebase/firestore/timestamp.cc | 117 +++++++++ .../core/test/firebase/firestore/CMakeLists.txt | 1 + .../test/firebase/firestore/model/CMakeLists.txt | 1 - .../firebase/firestore/model/timestamp_test.cc | 49 ---- .../core/test/firebase/firestore/timestamp_test.cc | 272 +++++++++++++++++++++ 15 files changed, 650 insertions(+), 206 deletions(-) create mode 100644 Firestore/core/include/firebase/firestore/CMakeLists.txt create mode 100644 Firestore/core/include/firebase/firestore/timestamp.h delete mode 100644 Firestore/core/src/firebase/firestore/model/timestamp.cc delete mode 100644 Firestore/core/src/firebase/firestore/model/timestamp.h create mode 100644 Firestore/core/src/firebase/firestore/timestamp.cc delete mode 100644 Firestore/core/test/firebase/firestore/model/timestamp_test.cc create mode 100644 Firestore/core/test/firebase/firestore/timestamp_test.cc (limited to 'Firestore') diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 28627ec..eb88210 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -384,7 +384,7 @@ ABA495B9202B7E79008A7851 /* snapshot_version_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = snapshot_version_test.cc; sourceTree = ""; }; ABC1D7DF2023A3EF00BA84F0 /* token_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = token_test.cc; sourceTree = ""; }; ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = firebase_credentials_provider_test.mm; sourceTree = ""; }; - ABF6506B201131F8005F2C74 /* timestamp_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = timestamp_test.cc; sourceTree = ""; }; + ABF6506B201131F8005F2C74 /* timestamp_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = timestamp_test.cc; path = ../../core/test/firebase/firestore/timestamp_test.cc; sourceTree = ""; }; B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B6152AD5202A5385000E5744 /* document_key_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = document_key_test.cc; sourceTree = ""; }; B65D34A7203C99090076A5E1 /* FIRTimestampTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRTimestampTest.m; sourceTree = ""; }; @@ -504,6 +504,7 @@ 54740A561FC913EB00713A1A /* util */, 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */, AB7BAB332012B519001E0872 /* geo_point_test.cc */, + ABF6506B201131F8005F2C74 /* timestamp_test.cc */, ); name = GoogleTests; sourceTree = ""; @@ -554,8 +555,6 @@ 6003F58C195388D20070C39A /* Frameworks */, 6003F58B195388D20070C39A /* Products */, A47A1BF74A48BCAEAFBCBF1E /* Pods */, - 132E3B6B902D5CBD779D901A, - 132E37F450D23EF90B2352D9, ); sourceTree = ""; }; @@ -679,7 +678,6 @@ AB6B908520322E6D00CC290A /* maybe_document_test.cc */, AB6B908720322E8800CC290A /* no_document_test.cc */, ABA495B9202B7E79008A7851 /* snapshot_version_test.cc */, - ABF6506B201131F8005F2C74 /* timestamp_test.cc */, ); name = model; path = ../../core/test/firebase/firestore/model; diff --git a/Firestore/core/CMakeLists.txt b/Firestore/core/CMakeLists.txt index 43188e9..1a0c936 100644 --- a/Firestore/core/CMakeLists.txt +++ b/Firestore/core/CMakeLists.txt @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +add_subdirectory(include/firebase/firestore) + add_subdirectory(src/firebase/firestore) add_subdirectory(src/firebase/firestore/auth) add_subdirectory(src/firebase/firestore/core) diff --git a/Firestore/core/include/firebase/firestore/CMakeLists.txt b/Firestore/core/include/firebase/firestore/CMakeLists.txt new file mode 100644 index 0000000..e4e7acd --- /dev/null +++ b/Firestore/core/include/firebase/firestore/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright 2018 Google +# +# 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. + +# Hack to make the headers show up in IDEs +# (see https://stackoverflow.com/questions/27039019/ and open issue on CMake +# issue tracker: https://gitlab.kitware.com/cmake/cmake/issues/15234) +add_custom_target(firebase_firestore_types_ide SOURCES + document_reference.h + event_listener.h + firestore.h + firestore_errors.h + geo_point.h + timestamp.h +) diff --git a/Firestore/core/include/firebase/firestore/timestamp.h b/Firestore/core/include/firebase/firestore/timestamp.h new file mode 100644 index 0000000..096d121 --- /dev/null +++ b/Firestore/core/include/firebase/firestore/timestamp.h @@ -0,0 +1,221 @@ +/* + * Copyright 2018 Google + * + * 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 FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_TIMESTAMP_H_ +#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_TIMESTAMP_H_ + +#include +#include +#if !defined(_STLPORT_VERSION) +#include // NOLINT(build/c++11) +#endif // !defined(_STLPORT_VERSION) +#include +#include + +namespace firebase { + +/** + * A Timestamp represents a point in time independent of any time zone or + * calendar, represented as seconds and fractions of seconds at nanosecond + * resolution in UTC Epoch time. It is encoded using the Proleptic Gregorian + * Calendar which extends the Gregorian calendar backwards to year one. It is + * encoded assuming all minutes are 60 seconds long, i.e. leap seconds are + * "smeared" so that no leap second table is needed for interpretation. Range is + * from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. + * + * @see + * https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto + */ +class Timestamp { + public: + /** + * Creates a new timestamp representing the epoch (with seconds and + * nanoseconds set to 0). + */ + Timestamp(); + + /** + * Creates a new timestamp. + * + * @param seconds The number of seconds of UTC time since Unix epoch + * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + * 9999-12-31T23:59:59Z inclusive; otherwise, assertion failure will be + * triggered. + * @param nanoseconds The non-negative fractions of a second at nanosecond + * resolution. Negative second values with fractions must still have + * non-negative nanoseconds values that count forward in time. Must be + * from 0 to 999,999,999 inclusive; otherwise, assertion failure will be + * triggered. + */ + Timestamp(int64_t seconds, int32_t nanoseconds); + + /** + * Creates a new timestamp with the current date. + * + * The precision is up to nanoseconds, depending on the system clock. + * + * @return a new timestamp representing the current date. + */ + static Timestamp Now(); + + /** + * The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. + */ + int64_t seconds() const { + return seconds_; + } + + /** + * The non-negative fractions of a second at nanosecond resolution. Negative + * second values with fractions still have non-negative nanoseconds values + * that count forward in time. + */ + int32_t nanoseconds() const { + return nanoseconds_; + } + + /** + * Converts `time_t` to a `Timestamp`. + * + * @param seconds_since_unix_epoch + * @parblock + * The number of seconds of UTC time since Unix epoch + * 1970-01-01T00:00:00Z. Can be negative to represent dates before the + * epoch. Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z + * inclusive; otherwise, assertion failure will be triggered. + * + * Note that while the epoch of `time_t` is unspecified, it's usually Unix + * epoch. If this assumption is broken, this function will produce + * incorrect results. + * @endparblock + * + * @return a new timestamp with the given number of seconds and zero + * nanoseconds. + */ + static Timestamp FromTimeT(time_t seconds_since_unix_epoch); + +#if !defined(_STLPORT_VERSION) + /** + * Converts `std::chrono::time_point` to a `Timestamp`. + * + * @param time_point + * @parblock + * The time point with system clock's epoch, which is + * presumed to be Unix epoch 1970-01-01T00:00:00Z. Can be negative to + * represent dates before the epoch. Must be from 0001-01-01T00:00:00Z to + * 9999-12-31T23:59:59Z inclusive; otherwise, assertion failure will be + * triggered. + * + * Note that while the epoch of `std::chrono::system_clock` is + * unspecified, it's usually Unix epoch. If this assumption is broken, + * this constructor will produce incorrect results. + * @endparblock + */ + static Timestamp FromTimePoint( + std::chrono::time_point time_point); + + /** + * Converts this `Timestamp` to a `time_point`. + * + * Important: if overflow would occur, the returned value will be the maximum + * or minimum value that `Duration` can hold. Note in particular that `long + * long` is insufficient to hold the full range of `Timestamp` values with + * nanosecond precision (which is why `Duration` defaults to `microseconds`). + */ + template + std::chrono::time_point ToTimePoint() const; +#endif // !defined(_STLPORT_VERSION) + + /** + * Returns a string representation of this `Timestamp` for logging/debugging + * purposes. + * + * Note: the exact string representation is unspecified and subject to change; + * don't rely on the format of the string. + */ + std::string ToString() const; + + private: + // Checks that the number of seconds is within the supported date range, and + // that nanoseconds satisfy 0 <= ns <= 1second. + void ValidateBounds() const; + + int64_t seconds_ = 0; + int32_t nanoseconds_ = 0; +}; + +inline bool operator<(const Timestamp& lhs, const Timestamp& rhs) { + return lhs.seconds() < rhs.seconds() || + (lhs.seconds() == rhs.seconds() && + lhs.nanoseconds() < rhs.nanoseconds()); +} + +inline bool operator>(const Timestamp& lhs, const Timestamp& rhs) { + return rhs < lhs; +} + +inline bool operator>=(const Timestamp& lhs, const Timestamp& rhs) { + return !(lhs < rhs); +} + +inline bool operator<=(const Timestamp& lhs, const Timestamp& rhs) { + return !(lhs > rhs); +} + +inline bool operator!=(const Timestamp& lhs, const Timestamp& rhs) { + return lhs < rhs || lhs > rhs; +} + +inline bool operator==(const Timestamp& lhs, const Timestamp& rhs) { + return !(lhs != rhs); +} + +#if !defined(_STLPORT_VERSION) +template +std::chrono::time_point Timestamp::ToTimePoint() const { + namespace chr = std::chrono; + using TimePoint = chr::time_point; + + // Saturate on overflow + const auto max_value = std::numeric_limits::max(); + if (seconds_ > 0 && max_value / Duration::period::den <= seconds_) { + return TimePoint{Duration(max_value)}; + } + const auto min_value = std::numeric_limits::min(); + if (seconds_ < 0 && min_value / Duration::period::den >= seconds_) { + return TimePoint{Duration(min_value)}; + } + + const auto seconds = chr::duration_cast(chr::seconds(seconds_)); + const auto nanoseconds = + chr::duration_cast(chr::nanoseconds(nanoseconds_)); + return TimePoint{seconds + nanoseconds}; +} +#endif // !defined(_STLPORT_VERSION) + +} // namespace firebase + +namespace std { +template <> +struct hash { + // Note: specialization of `std::hash` is provided for convenience only. The + // implementation is subject to change. + size_t operator()(const firebase::Timestamp& timestamp) const; +}; +} // namespace std + +#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_TIMESTAMP_H_ diff --git a/Firestore/core/src/firebase/firestore/CMakeLists.txt b/Firestore/core/src/firebase/firestore/CMakeLists.txt index 3f5522c..aad2ebb 100644 --- a/Firestore/core/src/firebase/firestore/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/CMakeLists.txt @@ -17,6 +17,14 @@ cc_library( firebase_firestore_types SOURCES geo_point.cc + timestamp.cc DEPENDS firebase_firestore_util ) + +# Include the folder with public headers. +target_include_directories( + firebase_firestore_types + PUBLIC + ${PROJECT_SOURCE_DIR}/core/include +) diff --git a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt index e7824e3..78f5cd6 100644 --- a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt @@ -34,8 +34,6 @@ cc_library( resource_path.h snapshot_version.cc snapshot_version.h - timestamp.cc - timestamp.h types.h DEPENDS absl_strings diff --git a/Firestore/core/src/firebase/firestore/model/field_value.h b/Firestore/core/src/firebase/firestore/model/field_value.h index fc8619d..9111ffb 100644 --- a/Firestore/core/src/firebase/firestore/model/field_value.h +++ b/Firestore/core/src/firebase/firestore/model/field_value.h @@ -25,9 +25,9 @@ #include #include "Firestore/core/include/firebase/firestore/geo_point.h" +#include "Firestore/core/include/firebase/firestore/timestamp.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" #include "Firestore/core/src/firebase/firestore/model/document_key.h" -#include "Firestore/core/src/firebase/firestore/model/timestamp.h" #include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" namespace firebase { diff --git a/Firestore/core/src/firebase/firestore/model/snapshot_version.h b/Firestore/core/src/firebase/firestore/model/snapshot_version.h index 70f6f4a..56e8c50 100644 --- a/Firestore/core/src/firebase/firestore/model/snapshot_version.h +++ b/Firestore/core/src/firebase/firestore/model/snapshot_version.h @@ -17,7 +17,7 @@ #ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_SNAPSHOT_VERSION_H_ #define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_SNAPSHOT_VERSION_H_ -#include "Firestore/core/src/firebase/firestore/model/timestamp.h" +#include "Firestore/core/include/firebase/firestore/timestamp.h" namespace firebase { namespace firestore { diff --git a/Firestore/core/src/firebase/firestore/model/timestamp.cc b/Firestore/core/src/firebase/firestore/model/timestamp.cc deleted file mode 100644 index b3d1597..0000000 --- a/Firestore/core/src/firebase/firestore/model/timestamp.cc +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2018 Google - * - * 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. - */ - -#include "Firestore/core/src/firebase/firestore/model/timestamp.h" - -#include - -#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" - -namespace firebase { -namespace firestore { -namespace model { - -Timestamp::Timestamp(int64_t seconds, int32_t nanos) - : seconds_(seconds), nanos_(nanos) { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - nanos >= 0, nanos >= 0, "timestamp nanoseconds out of range: %d", nanos); - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - nanos < 1e9, nanos < 1e9, "timestamp nanoseconds out of range: %d", - nanos); - // Midnight at the beginning of 1/1/1 is the earliest timestamp Firestore - // supports. - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - seconds >= -62135596800L, seconds >= -62135596800L, - "timestamp seconds out of range: %lld", seconds); - // This will break in the year 10,000. - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - seconds < 253402300800L, seconds < 253402300800L, - "timestamp seconds out of range: %lld", seconds); -} - -Timestamp::Timestamp() : seconds_(0), nanos_(0) { -} - -Timestamp Timestamp::Now() { - return Timestamp(time(nullptr), 0); -} - -} // namespace model -} // namespace firestore -} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/timestamp.h b/Firestore/core/src/firebase/firestore/model/timestamp.h deleted file mode 100644 index dd0349c..0000000 --- a/Firestore/core/src/firebase/firestore/model/timestamp.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2018 Google - * - * 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 FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_TIMESTAMP_H_ -#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_TIMESTAMP_H_ - -#include - -namespace firebase { -namespace firestore { -namespace model { - -/** - * A Timestamp represents an absolute time from the backend at up to nanosecond - * precision. A Timestamp is always UTC. - */ -class Timestamp { - public: - /** - * Creates a new timestamp with seconds and nanos set to 0. - * - * PORTING NOTE: This does NOT set to current timestamp by default. To get the - * current timestamp, call Timestamp::Now(). - */ - Timestamp(); - - /** - * Creates a new timestamp. - * - * @param seconds the number of seconds since epoch. - * @param nanos the number of nanoseconds after the seconds. - */ - Timestamp(int64_t seconds, int32_t nanos); - - /** Returns a timestamp with the current date / time. */ - static Timestamp Now(); - - int64_t seconds() const { - return seconds_; - } - - int32_t nanos() const { - return nanos_; - } - - private: - int64_t seconds_; - int32_t nanos_; -}; - -/** Compares against another Timestamp. */ -inline bool operator<(const Timestamp& lhs, const Timestamp& rhs) { - return lhs.seconds() < rhs.seconds() || - (lhs.seconds() == rhs.seconds() && lhs.nanos() < rhs.nanos()); -} - -inline bool operator>(const Timestamp& lhs, const Timestamp& rhs) { - return rhs < lhs; -} - -inline bool operator>=(const Timestamp& lhs, const Timestamp& rhs) { - return !(lhs < rhs); -} - -inline bool operator<=(const Timestamp& lhs, const Timestamp& rhs) { - return !(lhs > rhs); -} - -inline bool operator!=(const Timestamp& lhs, const Timestamp& rhs) { - return lhs < rhs || lhs > rhs; -} - -inline bool operator==(const Timestamp& lhs, const Timestamp& rhs) { - return !(lhs != rhs); -} - -} // namespace model -} // namespace firestore -} // namespace firebase - -#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_TIMESTAMP_H_ diff --git a/Firestore/core/src/firebase/firestore/timestamp.cc b/Firestore/core/src/firebase/firestore/timestamp.cc new file mode 100644 index 0000000..7d947c5 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/timestamp.cc @@ -0,0 +1,117 @@ +/* + * Copyright 2018 Google + * + * 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. + */ + +#include "Firestore/core/include/firebase/firestore/timestamp.h" + +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" + +namespace firebase { + +Timestamp::Timestamp() { +} + +Timestamp::Timestamp(const int64_t seconds, const int32_t nanoseconds) + : seconds_(seconds), nanoseconds_(nanoseconds) { + ValidateBounds(); +} + +Timestamp Timestamp::Now() { +#if !defined(_STLPORT_VERSION) + // Use the standard library from C++11 if possible. + return FromTimePoint(std::chrono::system_clock::now()); +#else + // If is unavailable, use clock_gettime from POSIX, which supports up + // to nanosecond resolution. Note that it's a non-standard function contained + // in . + // + // Note: it's possible to check for availability of POSIX clock_gettime using + // macros (see "Availability" at https://linux.die.net/man/3/clock_gettime). + // However, the only platform where isn't available is Android with + // STLPort standard library, where clock_gettime is known to be available. + timespec now; + clock_gettime(CLOCK_REALTIME, &now); + return Timestamp(now.tv_sec, now.tv_nsec); +#endif // !defined(_STLPORT_VERSION) +} + +Timestamp Timestamp::FromTimeT(const time_t seconds_since_unix_epoch) { + return Timestamp(seconds_since_unix_epoch, 0); +} + +#if !defined(_STLPORT_VERSION) +Timestamp Timestamp::FromTimePoint( + const std::chrono::time_point time_point) { + namespace chr = std::chrono; + const auto epoch_time = time_point.time_since_epoch(); + auto seconds = chr::duration_cast>(epoch_time); + auto nanoseconds = chr::duration_cast(epoch_time - seconds); + FIREBASE_DEV_ASSERT(nanoseconds.count() < 1 * 1000 * 1000 * 1000); + + if (nanoseconds.count() < 0) { + // Timestamp format always has a positive number of nanoseconds that is + // counting forward. For negative time, we need to transform chrono + // representation of (negative seconds s1 + negative nanoseconds ns1) to + // (negative seconds s2 + positive nanoseconds ns2). Since nanosecond part + // is always less than 1 second in our representation, instead of starting + // at s1 and going back ns1 nanoseconds, start at (s1 minus one second) and + // go *forward* ns2 = (1 second + ns1, ns1 < 0) nanoseconds. + // + // Note: if nanoseconds are negative, it must mean that seconds are + // non-positive, but the formula would still be valid, so no need to check. + seconds = seconds - chr::seconds(1); + nanoseconds = chr::seconds(1) + nanoseconds; + } + + const Timestamp result{seconds.count(), + static_cast(nanoseconds.count())}; + result.ValidateBounds(); + return result; +} + +#endif // !defined(_STLPORT_VERSION) + +std::string Timestamp::ToString() const { + return std::string("Timestamp(seconds=") + std::to_string(seconds_) + + ", nanoseconds=" + std::to_string(nanoseconds_) + ")"; +} + +void Timestamp::ValidateBounds() const { + FIREBASE_ASSERT_MESSAGE(nanoseconds_ >= 0, + "Timestamp nanoseconds out of range: %d", + nanoseconds_); + FIREBASE_ASSERT_MESSAGE(nanoseconds_ < 1e9, + "Timestamp nanoseconds out of range: %d", + nanoseconds_); + // Midnight at the beginning of 1/1/1 is the earliest timestamp Firestore + // supports. + FIREBASE_ASSERT_MESSAGE(seconds_ >= -62135596800L, + "Timestamp seconds out of range: %lld", seconds_); + // This will break in the year 10,000. + FIREBASE_ASSERT_MESSAGE(seconds_ < 253402300800L, + "Timestamp seconds out of range: %lld", seconds_); +} + +} // namespace firebase + +namespace std { +size_t hash::operator()( + const firebase::Timestamp& timestamp) const { + // Note: if sizeof(size_t) == 4, this discards high-order bits of seconds. + return 37 * static_cast(timestamp.seconds()) + + static_cast(timestamp.nanoseconds()); +} + +} // namespace std diff --git a/Firestore/core/test/firebase/firestore/CMakeLists.txt b/Firestore/core/test/firebase/firestore/CMakeLists.txt index ed5760f..61692de 100644 --- a/Firestore/core/test/firebase/firestore/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/CMakeLists.txt @@ -16,6 +16,7 @@ cc_test( firebase_firestore_types_test SOURCES geo_point_test.cc + timestamp_test.cc DEPENDS firebase_firestore_types ) diff --git a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt index 0d581bc..2c2281f 100644 --- a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt @@ -24,7 +24,6 @@ cc_test( no_document_test.cc resource_path_test.cc snapshot_version_test.cc - timestamp_test.cc DEPENDS firebase_firestore_model ) diff --git a/Firestore/core/test/firebase/firestore/model/timestamp_test.cc b/Firestore/core/test/firebase/firestore/model/timestamp_test.cc deleted file mode 100644 index 55ee378..0000000 --- a/Firestore/core/test/firebase/firestore/model/timestamp_test.cc +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2018 Google - * - * 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. - */ - -#include "Firestore/core/src/firebase/firestore/model/timestamp.h" - -#include - -#include "gtest/gtest.h" - -namespace firebase { -namespace firestore { -namespace model { - -TEST(Timestamp, Getter) { - const Timestamp timestamp_zero; - EXPECT_EQ(0, timestamp_zero.seconds()); - EXPECT_EQ(0, timestamp_zero.nanos()); - - const Timestamp timestamp(100, 200); - EXPECT_EQ(100, timestamp.seconds()); - EXPECT_EQ(200, timestamp.nanos()); - - const Timestamp timestamp_now = Timestamp::Now(); - EXPECT_LT(0, timestamp_now.seconds()); - EXPECT_LE(0, timestamp_now.nanos()); -} - -TEST(Timestamp, Comparison) { - EXPECT_TRUE(Timestamp() < Timestamp(1, 2)); - EXPECT_TRUE(Timestamp(1, 2) < Timestamp(2, 1)); - EXPECT_TRUE(Timestamp(2, 1) < Timestamp(2, 2)); -} - -} // namespace model -} // namespace firestore -} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/timestamp_test.cc b/Firestore/core/test/firebase/firestore/timestamp_test.cc new file mode 100644 index 0000000..a4edcdf --- /dev/null +++ b/Firestore/core/test/firebase/firestore/timestamp_test.cc @@ -0,0 +1,272 @@ +/* + * Copyright 2018 Google + * + * 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. + */ + +#include "Firestore/core/include/firebase/firestore/timestamp.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace firebase { + +namespace { + +using TimePoint = std::chrono::time_point; +using Sec = std::chrono::seconds; +using Ms = std::chrono::milliseconds; + +const auto kUpperBound = 253402300800L - 1; +const auto kLowerBound = -62135596800L; + +} // namespace + +TEST(Timestamp, Constructors) { + const Timestamp zero; + EXPECT_EQ(0, zero.seconds()); + EXPECT_EQ(0, zero.nanoseconds()); + + const Timestamp positive(100, 200); + EXPECT_EQ(100, positive.seconds()); + EXPECT_EQ(200, positive.nanoseconds()); + + const Timestamp negative(-100, 200); + EXPECT_EQ(-100, negative.seconds()); + EXPECT_EQ(200, negative.nanoseconds()); + + const Timestamp now = Timestamp::Now(); + EXPECT_LT(0, now.seconds()); + EXPECT_LE(0, now.nanoseconds()); + + Timestamp copy_now = now; + EXPECT_EQ(now, copy_now); + EXPECT_EQ(now.seconds(), copy_now.seconds()); + EXPECT_EQ(now.nanoseconds(), copy_now.nanoseconds()); + const Timestamp move_now = std::move(copy_now); + EXPECT_EQ(now, move_now); +} + +TEST(Timestamp, Bounds) { + const Timestamp max_timestamp{kUpperBound, 999999999}; + EXPECT_EQ(kUpperBound, max_timestamp.seconds()); + EXPECT_EQ(999999999, max_timestamp.nanoseconds()); + + const Timestamp min_timestamp{kLowerBound, 0}; + EXPECT_EQ(kLowerBound, min_timestamp.seconds()); + EXPECT_EQ(0, min_timestamp.nanoseconds()); +} + +TEST(Timestamp, FromTimeT) { + const Timestamp zero = Timestamp::FromTimeT(std::time_t{}); + EXPECT_EQ(0, zero.seconds()); + EXPECT_EQ(0, zero.nanoseconds()); + + const Timestamp positive = Timestamp::FromTimeT(std::time_t{123456}); + EXPECT_EQ(123456, positive.seconds()); + EXPECT_EQ(0, positive.nanoseconds()); + + const Timestamp negative = Timestamp::FromTimeT(std::time_t{-123456}); + EXPECT_EQ(-123456, negative.seconds()); + EXPECT_EQ(0, negative.nanoseconds()); +} + +TEST(Timestamp, FromChrono) { + const auto zero = Timestamp::FromTimePoint(TimePoint{}); + EXPECT_EQ(0, zero.seconds()); + EXPECT_EQ(0, zero.nanoseconds()); + + const auto sec = Timestamp::FromTimePoint(TimePoint{Sec(123)}); + EXPECT_EQ(123, sec.seconds()); + EXPECT_EQ(0, sec.nanoseconds()); + + const auto ms = Timestamp::FromTimePoint(TimePoint{Sec(123) + Ms(456)}); + EXPECT_EQ(123, ms.seconds()); + EXPECT_EQ(456000000, ms.nanoseconds()); +} + +TEST(Timestamp, FromChronoNegativeTime) { + const auto no_fraction = Timestamp::FromTimePoint(TimePoint{Sec(-123)}); + EXPECT_EQ(-123, no_fraction.seconds()); + EXPECT_EQ(0, no_fraction.nanoseconds()); + + const auto with_positive_fraction = + Timestamp::FromTimePoint(TimePoint{Sec(-123) + Ms(456)}); + EXPECT_EQ(-123, with_positive_fraction.seconds()); + EXPECT_EQ(456000000, with_positive_fraction.nanoseconds()); + + const auto with_negative_fraction = + Timestamp::FromTimePoint(TimePoint{Sec(-122) + Ms(-544)}); + EXPECT_EQ(-123, with_negative_fraction.seconds()); + EXPECT_EQ(456000000, with_negative_fraction.nanoseconds()); + + const auto with_large_negative_fraction = + Timestamp::FromTimePoint(TimePoint{Sec(-122) + Ms(-100544)}); + EXPECT_EQ(-223, with_large_negative_fraction.seconds()); + EXPECT_EQ(456000000, with_large_negative_fraction.nanoseconds()); + + const auto only_negative_fraction = + Timestamp::FromTimePoint(TimePoint{Ms(-544)}); + EXPECT_EQ(-1, only_negative_fraction.seconds()); + EXPECT_EQ(456000000, only_negative_fraction.nanoseconds()); + + const auto positive_time_negative_fraction = + Timestamp::FromTimePoint(TimePoint{Sec(1) + Ms(-544)}); + EXPECT_EQ(0, positive_time_negative_fraction.seconds()); + EXPECT_EQ(456000000, positive_time_negative_fraction.nanoseconds()); + + const auto near_bounds = + Timestamp::FromTimePoint(TimePoint{Sec(kUpperBound + 1) + Ms(-544)}); + EXPECT_EQ(kUpperBound, near_bounds.seconds()); + EXPECT_EQ(456000000, near_bounds.nanoseconds()); +} + +TEST(Timestamp, ToChrono) { + namespace chr = std::chrono; + + // Note: this line is outside the inner block because otherwise clang-format + // gets confused about namespace alias on the line above. + const Timestamp positive{123, 456789000}; + { + const auto micros = positive.ToTimePoint().time_since_epoch(); + EXPECT_EQ(123456789, chr::duration_cast(micros).count()); + + const auto millis = + positive.ToTimePoint() + .time_since_epoch(); + EXPECT_EQ(123456000, chr::duration_cast(millis).count()); + + const auto nanos = + positive.ToTimePoint() + .time_since_epoch(); + EXPECT_EQ(123456789000, + chr::duration_cast(nanos).count()); + } + + { + const Timestamp negative{-123, 456000000}; + + const auto millis = + negative.ToTimePoint() + .time_since_epoch(); + const auto seconds = chr::duration_cast(millis); + EXPECT_EQ(-122, seconds.count()); + EXPECT_EQ(-544, + chr::duration_cast(millis - seconds).count()); + } + + // Bounds + { + const Timestamp max{kUpperBound, 999999999}; + const auto max_micros = max.ToTimePoint().time_since_epoch(); + EXPECT_EQ(kUpperBound * 1000 * 1000 + 999999, + chr::duration_cast(max_micros).count()); + + const Timestamp min{kLowerBound, 0}; + const auto min_micros = min.ToTimePoint().time_since_epoch(); + EXPECT_EQ(kLowerBound * 1000 * 1000, + chr::duration_cast(min_micros).count()); + } + + // Overflow + { + const Timestamp max{kUpperBound, 999999999}; + + const auto max_nanos = + max.ToTimePoint() + .time_since_epoch(); + EXPECT_EQ(std::numeric_limits::max(), + chr::duration_cast(max_nanos).count()); + + const Timestamp min{kLowerBound, 0}; + const auto min_nanos = + min.ToTimePoint() + .time_since_epoch(); + EXPECT_EQ(std::numeric_limits::min(), + chr::duration_cast(min_nanos).count()); + } +} + +TEST(Timestamp, Comparison) { + EXPECT_LT(Timestamp(), Timestamp(1, 2)); + EXPECT_LT(Timestamp(1, 2), Timestamp(2, 1)); + EXPECT_LT(Timestamp(2, 1), Timestamp(2, 2)); + + EXPECT_GT(Timestamp(1, 1), Timestamp()); + EXPECT_GT(Timestamp(2, 1), Timestamp(1, 2)); + EXPECT_GT(Timestamp(2, 2), Timestamp(2, 1)); + + EXPECT_LE(Timestamp(), Timestamp()); + EXPECT_LE(Timestamp(), Timestamp(1, 2)); + EXPECT_LE(Timestamp(1, 2), Timestamp(2, 1)); + EXPECT_LE(Timestamp(2, 1), Timestamp(2, 1)); + EXPECT_LE(Timestamp(2, 1), Timestamp(2, 2)); + + EXPECT_GE(Timestamp(), Timestamp()); + EXPECT_GE(Timestamp(1, 1), Timestamp()); + EXPECT_GE(Timestamp(1, 1), Timestamp(1, 1)); + EXPECT_GE(Timestamp(2, 1), Timestamp(1, 2)); + EXPECT_GE(Timestamp(2, 1), Timestamp(2, 1)); + EXPECT_GE(Timestamp(2, 2), Timestamp(2, 1)); + + EXPECT_EQ(Timestamp(), Timestamp()); + EXPECT_EQ(Timestamp(), Timestamp(0, 0)); + EXPECT_EQ(Timestamp(123, 123456789), Timestamp(123, 123456789)); + + EXPECT_NE(Timestamp(), Timestamp(0, 1)); + EXPECT_NE(Timestamp(), Timestamp(1, 0)); + EXPECT_NE(Timestamp(123, 123456789), Timestamp(123, 123456780)); +} + +TEST(Timestamp, Hash) { + const Timestamp foo1{123, 456000000}; + const Timestamp foo2 = foo1; + const Timestamp foo3 = + Timestamp::FromTimePoint(TimePoint{Sec(123) + Ms(456)}); + EXPECT_EQ(std::hash()(foo1), std::hash()(foo2)); + EXPECT_EQ(std::hash()(foo2), std::hash()(foo3)); + + const Timestamp bar{123, 456}; + EXPECT_NE(std::hash()(foo1), std::hash()(bar)); +} + +TEST(Timestamp, InvalidArguments) { + // Negative nanoseconds. + ASSERT_ANY_THROW(Timestamp(0, -1)); + ASSERT_ANY_THROW(Timestamp(100, -1)); + ASSERT_ANY_THROW(Timestamp(100, -12346789)); + + // Nanoseconds that are more than one second. + ASSERT_ANY_THROW(Timestamp(0, 999999999 + 1)); + + // Seconds beyond supported range. + ASSERT_ANY_THROW(Timestamp(kLowerBound - 1, 0)); + ASSERT_ANY_THROW(Timestamp(kUpperBound + 1, 0)); + + // Using chrono. + ASSERT_ANY_THROW(Timestamp::FromTimePoint(TimePoint{Sec(kLowerBound - 1)})); + ASSERT_ANY_THROW(Timestamp::FromTimePoint(TimePoint{Sec(kUpperBound + 1)})); +} + +TEST(Timestamp, ToString) { + EXPECT_EQ(Timestamp().ToString(), "Timestamp(seconds=0, nanoseconds=0)"); + EXPECT_EQ(Timestamp(123, 123456789).ToString(), + "Timestamp(seconds=123, nanoseconds=123456789)"); + EXPECT_EQ(Timestamp(-123, 123456789).ToString(), + "Timestamp(seconds=-123, nanoseconds=123456789)"); +} + +} // namespace firebase -- cgit v1.2.3