Skip to content

Commit c169c9b

Browse files
feat(bigtable): add support for Timestamp and Date (#15560)
* feat(bigtable): add support for Timestamp and Date * chore: checkers * chore: static cast to int * chore: remove unused commit timestamp * chore: compare timestamp protos directly
1 parent cfc3d78 commit c169c9b

File tree

9 files changed

+1155
-3
lines changed

9 files changed

+1155
-3
lines changed

google/cloud/bigtable/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ add_library(
254254
table_config.h
255255
table_resource.cc
256256
table_resource.h
257+
timestamp.cc
258+
timestamp.h
257259
value.cc
258260
value.h
259261
version.cc
@@ -495,6 +497,7 @@ if (BUILD_TESTING)
495497
table_test.cc
496498
testing/cleanup_stale_resources_test.cc
497499
testing/random_names_test.cc
500+
timestamp_test.cc
498501
value_test.cc
499502
wait_for_consistency_test.cc)
500503

google/cloud/bigtable/bigtable_client_unit_tests.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ bigtable_client_unit_tests = [
9595
"table_test.cc",
9696
"testing/cleanup_stale_resources_test.cc",
9797
"testing/random_names_test.cc",
98+
"timestamp_test.cc",
9899
"value_test.cc",
99100
"wait_for_consistency_test.cc",
100101
]

google/cloud/bigtable/google_cloud_cpp_bigtable.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ google_cloud_cpp_bigtable_hdrs = [
132132
"table_admin.h",
133133
"table_config.h",
134134
"table_resource.h",
135+
"timestamp.h",
135136
"value.h",
136137
"version.h",
137138
"version_info.h",
@@ -226,6 +227,7 @@ google_cloud_cpp_bigtable_srcs = [
226227
"table_admin.cc",
227228
"table_config.cc",
228229
"table_resource.cc",
230+
"timestamp.cc",
229231
"value.cc",
230232
"version.cc",
231233
"wait_for_consistency.cc",

google/cloud/bigtable/timestamp.cc

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/cloud/bigtable/timestamp.h"
16+
#include "google/cloud/internal/make_status.h"
17+
#include "google/cloud/status.h"
18+
#include <google/protobuf/util/time_util.h>
19+
#include <string>
20+
21+
namespace google {
22+
namespace cloud {
23+
namespace bigtable {
24+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
25+
26+
namespace {
27+
28+
Status OutOfRange(std::string message, internal::ErrorInfoBuilder info) {
29+
return internal::OutOfRangeError(std::move(message), std::move(info));
30+
}
31+
32+
Status PositiveOverflow(std::string const& type) {
33+
return OutOfRange(type + " positive overflow", GCP_ERROR_INFO());
34+
}
35+
36+
Status NegativeOverflow(std::string const& type) {
37+
return OutOfRange(type + " negative overflow", GCP_ERROR_INFO());
38+
}
39+
40+
} // namespace
41+
42+
StatusOr<protobuf::Timestamp> Timestamp::ConvertTo(
43+
protobuf::Timestamp const&) const {
44+
auto constexpr kDestType = "google::protobuf::Timestamp";
45+
auto const s = absl::ToUnixSeconds(t_);
46+
if (s > google::protobuf::util::TimeUtil::kTimestampMaxSeconds)
47+
return PositiveOverflow(kDestType);
48+
if (s < google::protobuf::util::TimeUtil::kTimestampMinSeconds)
49+
return NegativeOverflow(kDestType);
50+
auto const ns = absl::ToInt64Nanoseconds(t_ - absl::FromUnixSeconds(s));
51+
google::protobuf::Timestamp proto;
52+
proto.set_seconds(s);
53+
proto.set_nanos(static_cast<std::int32_t>(ns));
54+
return proto;
55+
}
56+
57+
StatusOr<std::int64_t> Timestamp::ToRatio(std::int64_t min, std::int64_t max,
58+
std::int64_t num,
59+
std::int64_t den) const {
60+
auto constexpr kDestType = "std::chrono::time_point";
61+
auto const period = absl::Seconds(num) / den;
62+
auto const duration = absl::Floor(t_ - absl::UnixEpoch(), period);
63+
if (duration > max * period) return PositiveOverflow(kDestType);
64+
if (duration < min * period) return NegativeOverflow(kDestType);
65+
return duration / period;
66+
}
67+
68+
StatusOr<Timestamp> MakeTimestamp(absl::Time t) {
69+
auto constexpr kDestType = "google::cloud::bigtable::Timestamp";
70+
auto constexpr kMinTime = absl::FromUnixSeconds(
71+
google::protobuf::util::TimeUtil::kTimestampMinSeconds);
72+
auto constexpr kMaxTime = absl::FromUnixSeconds(
73+
google::protobuf::util::TimeUtil::kTimestampMaxSeconds + 1);
74+
if (t >= kMaxTime) return PositiveOverflow(kDestType);
75+
if (t < kMinTime) return NegativeOverflow(kDestType);
76+
return Timestamp(t);
77+
}
78+
79+
StatusOr<Timestamp> MakeTimestamp(protobuf::Timestamp const& proto) {
80+
return MakeTimestamp(absl::FromUnixSeconds(proto.seconds()) +
81+
absl::Nanoseconds(proto.nanos()));
82+
}
83+
84+
std::ostream& operator<<(std::ostream& os, Timestamp ts) {
85+
return os << bigtable_internal::TimestampToRFC3339(ts);
86+
}
87+
88+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
89+
} // namespace bigtable
90+
91+
namespace bigtable_internal {
92+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
93+
94+
namespace {
95+
96+
// Timestamp objects are always formatted in UTC, and we always format them
97+
// with a trailing 'Z'. However, we're a bit more liberal in the UTC offsets we
98+
// accept, thus the use of '%Ez' in kParseSpec.
99+
auto constexpr kFormatSpec = "%E4Y-%m-%d%ET%H:%M:%E*SZ";
100+
auto constexpr kParseSpec = "%Y-%m-%d%ET%H:%M:%E*S%Ez";
101+
102+
} // namespace
103+
104+
StatusOr<bigtable::Timestamp> TimestampFromRFC3339(std::string const& s) {
105+
absl::Time t;
106+
std::string err;
107+
if (absl::ParseTime(kParseSpec, s, &t, &err)) {
108+
return bigtable::MakeTimestamp(t);
109+
}
110+
return internal::InvalidArgumentError(s + ": " + err, GCP_ERROR_INFO());
111+
}
112+
113+
std::string TimestampToRFC3339(bigtable::Timestamp ts) {
114+
auto const t = ts.get<absl::Time>().value(); // Cannot fail.
115+
return absl::FormatTime(kFormatSpec, t, absl::UTCTimeZone());
116+
}
117+
118+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
119+
} // namespace bigtable_internal
120+
} // namespace cloud
121+
} // namespace google

google/cloud/bigtable/timestamp.h

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_TIMESTAMP_H
16+
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_TIMESTAMP_H
17+
18+
#include "google/cloud/bigtable/version.h"
19+
#include "google/cloud/status_or.h"
20+
#include "absl/time/time.h"
21+
#include <google/protobuf/timestamp.pb.h>
22+
#include <chrono>
23+
#include <cstdint>
24+
#include <limits>
25+
#include <ostream>
26+
#include <string>
27+
28+
namespace google {
29+
namespace cloud {
30+
namespace bigtable {
31+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
32+
33+
/**
34+
* Convenience alias. `std::chrono::sys_time` since C++20.
35+
*/
36+
template <typename Duration>
37+
using sys_time = std::chrono::time_point<std::chrono::system_clock, Duration>;
38+
39+
/**
40+
* A representation of the Bigtable TIMESTAMP type: An instant in time.
41+
*
42+
* A `Timestamp` represents an absolute point in time (i.e., is independent of
43+
* any time zone), with at least nanosecond precision, and with a range of
44+
* 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z, inclusive.
45+
*
46+
* The `MakeTimestamp(src)` factory function(s) should be used to construct
47+
* `Timestamp` values from standard representations of absolute time.
48+
*
49+
* A `Timestamp` can be converted back to a standard representation using
50+
* `ts.get<T>()`.
51+
*
52+
* @see https://cloud.google.com/bigtable/docs/data-types#timestamp_type
53+
*/
54+
class Timestamp {
55+
public:
56+
/// Default construction yields 1970-01-01T00:00:00Z.
57+
Timestamp() : Timestamp(absl::UnixEpoch()) {}
58+
59+
/// @name Regular value type, supporting copy, assign, move.
60+
///@{
61+
Timestamp(Timestamp&&) = default;
62+
Timestamp& operator=(Timestamp&&) = default;
63+
Timestamp(Timestamp const&) = default;
64+
Timestamp& operator=(Timestamp const&) = default;
65+
///@}
66+
67+
/// @name Relational operators
68+
///@{
69+
friend bool operator==(Timestamp const& a, Timestamp const& b) {
70+
return a.t_ == b.t_;
71+
}
72+
friend bool operator!=(Timestamp const& a, Timestamp const& b) {
73+
return !(a == b);
74+
}
75+
friend bool operator<(Timestamp const& a, Timestamp const& b) {
76+
return a.t_ < b.t_;
77+
}
78+
friend bool operator<=(Timestamp const& a, Timestamp const& b) {
79+
return !(b < a);
80+
}
81+
friend bool operator>=(Timestamp const& a, Timestamp const& b) {
82+
return !(a < b);
83+
}
84+
friend bool operator>(Timestamp const& a, Timestamp const& b) {
85+
return b < a;
86+
}
87+
///@}
88+
89+
/// @name Output streaming
90+
friend std::ostream& operator<<(std::ostream& os, Timestamp ts);
91+
92+
/**
93+
* Convert the `Timestamp` to the user-specified template type. Fails if
94+
* `*this` cannot be represented as a `T`.
95+
*
96+
* Supported destination types are:
97+
* - `absl::Time` - Since `absl::Time` can represent all possible
98+
* `Timestamp` values, `get<absl::Time>()` never returns an error.
99+
* - `google::protobuf::Timestamp` - Never returns an error, but any
100+
* sub-nanosecond precision will be lost.
101+
* - `google::cloud::bigtable::sys_time<Duration>` - `Duration::rep` may
102+
* not be wider than `std::int64_t`, and `Duration::period` may be no
103+
* more precise than `std::nano`.
104+
*
105+
* @par Example
106+
*
107+
* @code
108+
* sys_time<std::chrono::nanoseconds> tp = ...;
109+
* Timestamp ts = MakeTimestamp(tp).value();
110+
* assert(tp == ts.get<sys_time<std::chrono::nanoseconds>>().value());
111+
* @endcode
112+
*/
113+
template <typename T>
114+
StatusOr<T> get() const {
115+
// All `ConvertTo()` overloads return a `StatusOr<T>` even when they
116+
// cannot actually fail (e.g., when the destination type can represent
117+
// all `Timestamp` values). See individual comments below.
118+
return ConvertTo(T{});
119+
}
120+
121+
private:
122+
friend StatusOr<Timestamp> MakeTimestamp(absl::Time);
123+
124+
StatusOr<std::int64_t> ToRatio(std::int64_t min, std::int64_t max,
125+
std::int64_t num, std::int64_t den) const;
126+
127+
// Conversion to a `std::chrono::time_point` on the system clock. May
128+
// produce out-of-range errors, depending on the properties of `Duration`
129+
// and the `std::chrono::system_clock` epoch.
130+
template <typename Duration>
131+
StatusOr<sys_time<Duration>> ConvertTo(sys_time<Duration> const&) const {
132+
using Rep = typename Duration::rep;
133+
using Period = typename Duration::period;
134+
static_assert(std::ratio_greater_equal<Period, std::nano>::value,
135+
"Duration must be no more precise than std::nano");
136+
auto count =
137+
ToRatio(std::numeric_limits<Rep>::min(),
138+
std::numeric_limits<Rep>::max(), Period::num, Period::den);
139+
if (!count) return std::move(count).status();
140+
auto const unix_epoch = std::chrono::time_point_cast<Duration>(
141+
sys_time<Duration>::clock::from_time_t(0));
142+
return unix_epoch + Duration(static_cast<Rep>(*count));
143+
}
144+
145+
// Conversion to an `absl::Time`. Can never fail.
146+
StatusOr<absl::Time> ConvertTo(absl::Time) const { return t_; }
147+
148+
// Conversion to a `google::protobuf::Timestamp`. Can never fail, but
149+
// any sub-nanosecond precision will be lost.
150+
StatusOr<protobuf::Timestamp> ConvertTo(protobuf::Timestamp const&) const;
151+
152+
explicit Timestamp(absl::Time t) : t_(t) {}
153+
154+
absl::Time t_;
155+
};
156+
157+
/**
158+
* Construct a `Timestamp` from an `absl::Time`. May produce out-of-range
159+
* errors if the given time is beyond the range supported by `Timestamp` (see
160+
* class comments above).
161+
*/
162+
StatusOr<Timestamp> MakeTimestamp(absl::Time);
163+
164+
/**
165+
* Construct a `Timestamp` from a `google::protobuf::Timestamp`. May produce
166+
* out-of-range errors if the given protobuf is beyond the range supported by
167+
* `Timestamp` (which a valid protobuf never will).
168+
*/
169+
StatusOr<Timestamp> MakeTimestamp(protobuf::Timestamp const&);
170+
171+
/**
172+
* Construct a `Timestamp` from a `std::chrono::time_point` on the system
173+
* clock. May produce out-of-range errors, depending on the properties of
174+
* `Duration` and the `std::chrono::system_clock` epoch. `Duration::rep` may
175+
* not be wider than `std::int64_t`. Requires that `Duration::period` is no
176+
* more precise than `std::nano`.
177+
*/
178+
template <typename Duration>
179+
StatusOr<Timestamp> MakeTimestamp(sys_time<Duration> const& tp) {
180+
using Period = typename Duration::period;
181+
static_assert(std::ratio_greater_equal<Period, std::nano>::value,
182+
"Duration must be no more precise than std::nano");
183+
auto const unix_epoch = std::chrono::time_point_cast<Duration>(
184+
sys_time<Duration>::clock::from_time_t(0));
185+
auto const period = absl::Seconds(Period::num) / Period::den;
186+
auto const count = (tp - unix_epoch).count();
187+
return MakeTimestamp(absl::UnixEpoch() + count * period);
188+
}
189+
190+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
191+
} // namespace bigtable
192+
193+
namespace bigtable_internal {
194+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
195+
StatusOr<bigtable::Timestamp> TimestampFromRFC3339(std::string const&);
196+
std::string TimestampToRFC3339(bigtable::Timestamp);
197+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
198+
} // namespace bigtable_internal
199+
200+
} // namespace cloud
201+
} // namespace google
202+
203+
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_TIMESTAMP_H

0 commit comments

Comments
 (0)