blob: cb23b0a93d5ea7012eeb2a1355b9d27e753e4535 [file] [log] [blame]
/*
* Copyright 2022 Google LLC
*
* 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 "time/proto.h"
#include <cstdint>
#include <string>
#include "google/protobuf/duration.pb.h"
#include "google/protobuf/timestamp.pb.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/time/time.h"
namespace ecclesia {
// Validation requirements per google::protobuf::Duration.
absl::Status Validate(const google::protobuf::Duration &d) {
const auto sec = d.seconds();
const auto ns = d.nanos();
if (sec < -315576000000 || sec > 315576000000) {
return absl::InvalidArgumentError(absl::StrCat("seconds=", sec));
}
if (ns < -999999999 || ns > 999999999) {
return absl::InvalidArgumentError(absl::StrCat("nanos=", ns));
}
if ((sec < 0 && ns > 0) || (sec > 0 && ns < 0)) {
return absl::InvalidArgumentError("sign mismatch");
}
return absl::OkStatus();
}
absl::Time AbslTimeFromProtoTime(google::protobuf::Timestamp timestamp) {
// Protobuf time is just a combo of seconds and nanoseconds so we can
// construct time by just taking the unix epoch and splicing in those two
// units.
return absl::UnixEpoch() + absl::Seconds(timestamp.seconds()) +
absl::Nanoseconds(timestamp.nanos());
}
absl::StatusOr<google::protobuf::Timestamp> AbslTimeToProtoTime(
absl::Time timestamp) {
google::protobuf::Timestamp proto_timestamp;
// Converting time directly into seconds and nanoseconds it a bit tricky if we
// want to avoid overflow on the nanoseconds. It's a little easier if we
// instead convert to duration and use division and modulus operators. We can
// think of the time as just being a duration since the unix epoch.
//
// Note that this does not handle infinite past (or even anything pre-epoch)
// or infinite future.
if (timestamp < absl::UnixEpoch()) {
return absl::InternalError(
"timestamps earlier than UnixEpoch cannot be converted to "
"protobuf::Timestamp.");
}
if (timestamp == absl::InfiniteFuture()) {
return absl::InternalError(
"InfiniteFuture timestamp cannot be converted to protobuf::Timestamp.");
}
absl::Duration duration = timestamp - absl::UnixEpoch();
proto_timestamp.set_seconds(duration / absl::Seconds(1));
duration %= absl::Seconds(1);
proto_timestamp.set_nanos(duration / absl::Nanoseconds(1));
return proto_timestamp;
}
absl::StatusOr<google::protobuf::Duration> AbslDurationToProtoDuration(
absl::Duration d) {
google::protobuf::Duration proto;
absl::Status status = AbslDurationToProtoDuration(d, &proto);
if (!status.ok()) return status;
return proto;
}
absl::Status AbslDurationToProtoDuration(absl::Duration d,
google::protobuf::Duration *proto) {
// s and n may both be negative, per the Duration proto spec.
const int64_t s = absl::IDivDuration(d, absl::Seconds(1), &d);
const int64_t n = absl::IDivDuration(d, absl::Nanoseconds(1), &d);
proto->set_seconds(s);
proto->set_nanos(n);
return Validate(*proto);
}
absl::StatusOr<absl::Duration> AbslDurationFromProtoDuration(
const google::protobuf::Duration &proto) {
absl::Status status = Validate(proto);
if (!status.ok()) return status;
return absl::Seconds(proto.seconds()) + absl::Nanoseconds(proto.nanos());
}
} // namespace ecclesia