aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/core
diff options
context:
space:
mode:
authorGravatar Konstantin Varlamov <var-const@users.noreply.github.com>2018-03-27 11:50:03 -0400
committerGravatar GitHub <noreply@github.com>2018-03-27 11:50:03 -0400
commit22c226af3f5570514d3d13d82a399577ecd7d280 (patch)
treefc8f3e1ca146297f2993f8391ed3dab9e0f2c153 /Firestore/core
parent13aa61633f5a98c8dd0a508e5a404017bc79370e (diff)
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.
Diffstat (limited to 'Firestore/core')
-rw-r--r--Firestore/core/CMakeLists.txt2
-rw-r--r--Firestore/core/include/firebase/firestore/CMakeLists.txt25
-rw-r--r--Firestore/core/include/firebase/firestore/timestamp.h221
-rw-r--r--Firestore/core/src/firebase/firestore/CMakeLists.txt8
-rw-r--r--Firestore/core/src/firebase/firestore/model/CMakeLists.txt2
-rw-r--r--Firestore/core/src/firebase/firestore/model/field_value.h2
-rw-r--r--Firestore/core/src/firebase/firestore/model/snapshot_version.h2
-rw-r--r--Firestore/core/src/firebase/firestore/model/timestamp.cc54
-rw-r--r--Firestore/core/src/firebase/firestore/model/timestamp.h94
-rw-r--r--Firestore/core/src/firebase/firestore/timestamp.cc117
-rw-r--r--Firestore/core/test/firebase/firestore/CMakeLists.txt1
-rw-r--r--Firestore/core/test/firebase/firestore/model/CMakeLists.txt1
-rw-r--r--Firestore/core/test/firebase/firestore/model/timestamp_test.cc49
-rw-r--r--Firestore/core/test/firebase/firestore/timestamp_test.cc272
14 files changed, 648 insertions, 202 deletions
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 <stdint.h>
+#include <time.h>
+#if !defined(_STLPORT_VERSION)
+#include <chrono> // NOLINT(build/c++11)
+#endif // !defined(_STLPORT_VERSION)
+#include <limits>
+#include <string>
+
+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<std::chrono::system_clock> 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 <typename Clock = std::chrono::system_clock,
+ typename Duration = std::chrono::microseconds>
+ std::chrono::time_point<Clock, Duration> 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 <typename Clock, typename Duration>
+std::chrono::time_point<Clock, Duration> Timestamp::ToTimePoint() const {
+ namespace chr = std::chrono;
+ using TimePoint = chr::time_point<Clock, Duration>;
+
+ // Saturate on overflow
+ const auto max_value = std::numeric_limits<typename Duration::rep>::max();
+ if (seconds_ > 0 && max_value / Duration::period::den <= seconds_) {
+ return TimePoint{Duration(max_value)};
+ }
+ const auto min_value = std::numeric_limits<typename Duration::rep>::min();
+ if (seconds_ < 0 && min_value / Duration::period::den >= seconds_) {
+ return TimePoint{Duration(min_value)};
+ }
+
+ const auto seconds = chr::duration_cast<Duration>(chr::seconds(seconds_));
+ const auto nanoseconds =
+ chr::duration_cast<Duration>(chr::nanoseconds(nanoseconds_));
+ return TimePoint{seconds + nanoseconds};
+}
+#endif // !defined(_STLPORT_VERSION)
+
+} // namespace firebase
+
+namespace std {
+template <>
+struct hash<firebase::Timestamp> {
+ // 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 <vector>
#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 <time.h>
-
-#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 <stdint.h>
-
-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 <chrono> library from C++11 if possible.
+ return FromTimePoint(std::chrono::system_clock::now());
+#else
+ // If <chrono> is unavailable, use clock_gettime from POSIX, which supports up
+ // to nanosecond resolution. Note that it's a non-standard function contained
+ // in <time.h>.
+ //
+ // 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 <chrono> 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<std::chrono::system_clock> time_point) {
+ namespace chr = std::chrono;
+ const auto epoch_time = time_point.time_since_epoch();
+ auto seconds = chr::duration_cast<chr::duration<int64_t>>(epoch_time);
+ auto nanoseconds = chr::duration_cast<chr::nanoseconds>(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<int32_t>(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<firebase::Timestamp>::operator()(
+ const firebase::Timestamp& timestamp) const {
+ // Note: if sizeof(size_t) == 4, this discards high-order bits of seconds.
+ return 37 * static_cast<size_t>(timestamp.seconds()) +
+ static_cast<size_t>(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 <vector>
-
-#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 <limits>
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+
+namespace {
+
+using TimePoint = std::chrono::time_point<std::chrono::system_clock>;
+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<chr::microseconds>(micros).count());
+
+ const auto millis =
+ positive.ToTimePoint<chr::system_clock, chr::milliseconds>()
+ .time_since_epoch();
+ EXPECT_EQ(123456000, chr::duration_cast<chr::microseconds>(millis).count());
+
+ const auto nanos =
+ positive.ToTimePoint<chr::system_clock, chr::nanoseconds>()
+ .time_since_epoch();
+ EXPECT_EQ(123456789000,
+ chr::duration_cast<chr::nanoseconds>(nanos).count());
+ }
+
+ {
+ const Timestamp negative{-123, 456000000};
+
+ const auto millis =
+ negative.ToTimePoint<chr::system_clock, chr::milliseconds>()
+ .time_since_epoch();
+ const auto seconds = chr::duration_cast<chr::seconds>(millis);
+ EXPECT_EQ(-122, seconds.count());
+ EXPECT_EQ(-544,
+ chr::duration_cast<chr::milliseconds>(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<chr::microseconds>(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<chr::microseconds>(min_micros).count());
+ }
+
+ // Overflow
+ {
+ const Timestamp max{kUpperBound, 999999999};
+
+ const auto max_nanos =
+ max.ToTimePoint<chr::system_clock, chr::nanoseconds>()
+ .time_since_epoch();
+ EXPECT_EQ(std::numeric_limits<chr::nanoseconds::rep>::max(),
+ chr::duration_cast<chr::nanoseconds>(max_nanos).count());
+
+ const Timestamp min{kLowerBound, 0};
+ const auto min_nanos =
+ min.ToTimePoint<chr::system_clock, chr::nanoseconds>()
+ .time_since_epoch();
+ EXPECT_EQ(std::numeric_limits<chr::nanoseconds::rep>::min(),
+ chr::duration_cast<chr::nanoseconds>(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<Timestamp>()(foo1), std::hash<Timestamp>()(foo2));
+ EXPECT_EQ(std::hash<Timestamp>()(foo2), std::hash<Timestamp>()(foo3));
+
+ const Timestamp bar{123, 456};
+ EXPECT_NE(std::hash<Timestamp>()(foo1), std::hash<Timestamp>()(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