Skip to content

Commit

Permalink
fix(spanner): avoid absl types in public Interval API
Browse files Browse the repository at this point in the history
An update to the `Interval` API introduced in googleapis#14059.  Use
`std::chrono::nanoseconds` instead of `absl::Duration` to
represent the absolute "offset" portion of the interval.
  • Loading branch information
devbww committed Apr 30, 2024
1 parent c08330a commit a4876c0
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 92 deletions.
98 changes: 64 additions & 34 deletions google/cloud/spanner/interval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN

namespace {

using std::chrono::duration;
using std::chrono::duration_cast;
using std::chrono::hours;
using std::chrono::microseconds;
using std::chrono::milliseconds;
using std::chrono::minutes;
using std::chrono::nanoseconds;
using std::chrono::seconds;

// Interval factories for quantity units.
struct {
absl::string_view name; // de-pluralized forms
Expand All @@ -45,12 +54,12 @@ struct {
{"day", [](auto n) { return Interval(0, 0, n); }},

// "minute" comes before other "m"s so it is chosen for, say, "6m".
{"minute", [](auto n) { return Interval(absl::Minutes(n)); }},
{"minute", [](auto n) { return Interval{minutes(n)}; }},

{"microsecond", [](auto n) { return Interval(absl::Microseconds(n)); }},
{"millisecond", [](auto n) { return Interval(absl::Milliseconds(n)); }},
{"second", [](auto n) { return Interval(absl::Seconds(n)); }},
{"hour", [](auto n) { return Interval(absl::Hours(n)); }},
{"microsecond", [](auto n) { return Interval{microseconds(n)}; }},
{"millisecond", [](auto n) { return Interval{milliseconds(n)}; }},
{"second", [](auto n) { return Interval{seconds(n)}; }},
{"hour", [](auto n) { return Interval{hours(n)}; }},
{"week", [](auto n) { return Interval(0, 0, n * 7); }},
{"month", [](auto n) { return Interval(0, n, 0); }},
{"year", [](auto n) { return Interval(n, 0, 0); }},
Expand All @@ -61,19 +70,33 @@ struct {
{"millennia", [](auto n) { return Interval(n * 1000, 0, 0); }},
};

// Round d to a To{1} boundary (to even in halfway cases).
template <class To, class Rep, class Period>
To Round(duration<Rep, Period> const& d) {
auto below = duration_cast<To>(d);
if (below > d) below -= To{1};
auto above = below + To{1};
auto diff_below = d - below;
auto diff_above = above - d;
if (diff_below < diff_above) return below;
if (diff_below > diff_above) return above;
return (above.count() & 1) ? below : above;
}

// Interval comparison is done by logically combining the fields into a
// single value by assuming that 1 month == 30 days and 1 day == 24 hours,
// and by rounding to a microsecond boundary.
void Normalize(std::int64_t& months, std::int64_t& days,
absl::Duration& offset) {
auto const precision = absl::Microseconds(1);
offset += ((offset < absl::ZeroDuration()) ? -precision : precision) / 2;
offset = absl::Trunc(offset, precision);
days += absl::IDivDuration(offset, absl::Hours(24), &offset);
if (offset < absl::ZeroDuration()) {
offset += absl::Hours(24);
void Normalize(std::int64_t& months, std::int64_t& days, nanoseconds& offset) {
offset = Round<microseconds>(offset);

auto carry_days = offset / hours(24);
days += carry_days;
offset -= hours(carry_days * 24);
if (offset < nanoseconds::zero()) {
offset += hours(24);
days -= 1;
}

months += days / 30;
days %= 30;
if (days < 0) {
Expand All @@ -83,18 +106,18 @@ void Normalize(std::int64_t& months, std::int64_t& days,
}

template <template <typename> class C>
bool Cmp(std::int64_t a_months, std::int64_t a_days, absl::Duration a_offset,
std::int64_t b_months, std::int64_t b_days, absl::Duration b_offset) {
bool Cmp(std::int64_t a_months, std::int64_t a_days, nanoseconds a_offset,
std::int64_t b_months, std::int64_t b_days, nanoseconds b_offset) {
Normalize(a_months, a_days, a_offset);
Normalize(b_months, b_days, b_offset);
if (a_months != b_months) return C<std::int64_t>()(a_months, b_months);
if (a_days != b_days) return C<std::int64_t>()(a_days, b_days);
return C<absl::Duration>()(a_offset, b_offset);
return C<nanoseconds>()(a_offset, b_offset);
}

// "y years m months d days H:M:S.F"
std::string SerializeInterval(std::int32_t months, std::int32_t days,
absl::Duration offset) {
nanoseconds offset) {
std::ostringstream ss;
std::int32_t years = months / 12;
months %= 12;
Expand All @@ -116,18 +139,24 @@ std::string SerializeInterval(std::int32_t months, std::int32_t days,
ss << sep << days << " day" << plural(days);
sep = " ";
}
if (offset == absl::ZeroDuration()) {
if (offset == nanoseconds::zero()) {
if (*sep == '\0') ss << "0 days";
} else {
char const* sign = (offset < absl::ZeroDuration()) ? "-" : "";
offset = absl::AbsDuration(offset);
auto hour = absl::IDivDuration(offset, absl::Hours(1), &offset);
auto min = absl::IDivDuration(offset, absl::Minutes(1), &offset);
auto sec = absl::IDivDuration(offset, absl::Seconds(1), &offset);
ss << sep << sign << std::setfill('0') << std::setw(2) << hour;
ss << ':' << std::setfill('0') << std::setw(2) << min;
ss << ':' << std::setfill('0') << std::setw(2) << sec;
if (auto ns = absl::ToInt64Nanoseconds(offset)) {
ss << sep;
if (offset < nanoseconds::zero()) {
ss << "-";
offset = -offset;
}
auto hour = duration_cast<hours>(offset);
offset -= hour;
auto min = duration_cast<minutes>(offset);
offset -= min;
auto sec = duration_cast<seconds>(offset);
offset -= sec;
ss << std::setfill('0') << std::setw(2) << hour.count();
ss << ':' << std::setfill('0') << std::setw(2) << min.count();
ss << ':' << std::setfill('0') << std::setw(2) << sec.count();
if (auto ns = offset.count()) {
ss << '.' << std::setfill('0');
if (ns % 1000000 == 0) {
ss << std::setw(3) << ns / 1000000;
Expand Down Expand Up @@ -198,23 +227,23 @@ bool ParseYearMonth(absl::string_view& s, Interval& intvl) {

bool ParseHourMinuteSecond(absl::string_view& s, Interval& intvl) {
auto t = s;
auto d = absl::ZeroDuration();
auto d = nanoseconds::zero();
auto sign = ConsumeSign(t);
std::int64_t hour;
if (!ParseInteger(t, hour, false)) return false;
d += absl::Hours(hour);
d += hours(hour);
if (!absl::ConsumePrefix(&t, ":")) return false;
std::int64_t minute;
if (!ParseInteger(t, minute, false)) return false;
d += absl::Minutes(minute);
d += minutes(minute);
if (!absl::ConsumePrefix(&t, ":")) return false;
double second;
if (ParseDouble(t, second, false)) {
d += absl::Seconds(second);
d += duration_cast<nanoseconds>(duration<double>(second));
} else {
std::int64_t second;
if (!ParseInteger(t, second, false)) return false;
d += absl::Seconds(second);
d += seconds(second);
}
intvl = Interval(sign == "-" ? -d : d);
s.remove_prefix(s.size() - t.size());
Expand Down Expand Up @@ -338,8 +367,9 @@ Interval& Interval::operator*=(double d) {
double i_days;
double f_days = std::modf(days_ * d + f_months * 30, &i_days);
days_ = static_cast<std::int32_t>(i_days);
offset_ *= d;
offset_ += absl::Hours(f_days * 24);
auto offset = duration<double, std::nano>(offset_) * d;
offset += duration<double, std::ratio<3600>>(24) * f_days;
offset_ = duration_cast<nanoseconds>(offset);
return *this;
}

Expand Down
14 changes: 7 additions & 7 deletions google/cloud/spanner/interval.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
#include "google/cloud/spanner/version.h"
#include "google/cloud/status_or.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include <cstdint>
#include <iosfwd>
#include <string>
Expand All @@ -40,10 +39,10 @@ class Interval {
Interval() : Interval(0, 0, 0) {}

Interval(std::int32_t years, std::int32_t months, std::int32_t days,
absl::Duration offset = absl::ZeroDuration())
std::chrono::nanoseconds offset = std::chrono::nanoseconds::zero())
: Interval((years * 12) + months, days, offset) {}

explicit Interval(absl::Duration offset) : Interval(0, 0, offset) {}
explicit Interval(std::chrono::nanoseconds offset) : Interval(0, 0, offset) {}

/// @name Regular value type, supporting copy, assign, move.
///@{
Expand All @@ -58,8 +57,8 @@ class Interval {
/// Beware: Interval comparisons assume that 1 month == 30 days, and
/// 1 day == 24*60*60 seconds. This may lead to counterintuitive results.
/// It also means different Interval representations can compare equal,
/// e.g., Interval(0, 0, 1) == Interval(absl::Hours(24)). Also note
/// that offsets are rounded to 1-microsecond boundaries during
/// e.g., Interval(0, 0, 1) == Interval(std::chrono::hours(24)). Also
/// note that offsets are rounded to 1-microsecond boundaries during
/// comparisons (halfway cases rounding away from zero).
///@{
friend bool operator==(Interval const& a, Interval const& b);
Expand Down Expand Up @@ -105,12 +104,13 @@ class Interval {
}

private:
Interval(std::int32_t months, std::int32_t days, absl::Duration offset)
Interval(std::int32_t months, std::int32_t days,
std::chrono::nanoseconds offset)
: months_(months), days_(days), offset_(offset) {}

std::int32_t months_;
std::int32_t days_;
absl::Duration offset_;
std::chrono::nanoseconds offset_;
};

/// @name Binary operators
Expand Down
Loading

0 comments on commit a4876c0

Please sign in to comment.