diff options
Diffstat (limited to 'src/google/protobuf/util')
-rw-r--r-- | src/google/protobuf/util/field_mask_util.cc | 418 | ||||
-rw-r--r-- | src/google/protobuf/util/field_mask_util.h | 146 | ||||
-rw-r--r-- | src/google/protobuf/util/field_mask_util_test.cc | 395 | ||||
-rw-r--r-- | src/google/protobuf/util/internal/testdata/oneofs.proto | 68 | ||||
-rw-r--r-- | src/google/protobuf/util/time_util.cc | 525 | ||||
-rw-r--r-- | src/google/protobuf/util/time_util.h | 287 | ||||
-rw-r--r-- | src/google/protobuf/util/time_util_test.cc | 380 |
7 files changed, 2219 insertions, 0 deletions
diff --git a/src/google/protobuf/util/field_mask_util.cc b/src/google/protobuf/util/field_mask_util.cc new file mode 100644 index 00000000..82034bd4 --- /dev/null +++ b/src/google/protobuf/util/field_mask_util.cc @@ -0,0 +1,418 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <google/protobuf/util/field_mask_util.h> + +#include <google/protobuf/stubs/strutil.h> +#include <google/protobuf/stubs/map_util.h> + +namespace google { +namespace protobuf { +namespace util { + +using google::protobuf::FieldMask; + +string FieldMaskUtil::ToString(const FieldMask& mask) { + return Join(mask.paths(), ","); +} + +void FieldMaskUtil::FromString(const string& str, FieldMask* out) { + out->Clear(); + vector<string> paths = Split(str, ","); + for (int i = 0; i < paths.size(); ++i) { + if (paths[i].empty()) continue; + out->add_paths(paths[i]); + } +} + +bool FieldMaskUtil::InternalIsValidPath(const Descriptor* descriptor, + const string& path) { + vector<string> parts = Split(path, "."); + for (int i = 0; i < parts.size(); ++i) { + const string& field_name = parts[i]; + if (descriptor == NULL) { + return false; + } + const FieldDescriptor* field = descriptor->FindFieldByName(field_name); + if (field == NULL) { + return false; + } + if (!field->is_repeated() && + field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { + descriptor = field->message_type(); + } else { + descriptor = NULL; + } + } + return true; +} + +void FieldMaskUtil::InternalGetFieldMaskForAllFields( + const Descriptor* descriptor, FieldMask* out) { + for (int i = 0; i < descriptor->field_count(); ++i) { + out->add_paths(descriptor->field(i)->name()); + } +} + +namespace { +// A FieldMaskTree represents a FieldMask in a tree structure. For example, +// given a FieldMask "foo.bar,foo.baz,bar.baz", the FieldMaskTree will be: +// +// [root] -+- foo -+- bar +// | | +// | +- baz +// | +// +- bar --- baz +// +// In the tree, each leaf node represents a field path. +class FieldMaskTree { + public: + FieldMaskTree(); + ~FieldMaskTree(); + + void MergeFromFieldMask(const FieldMask& mask); + void MergeToFieldMask(FieldMask* mask); + + // Add a field path into the tree. In a FieldMask, each field path matches + // the specified field and also all its sub-fields. If the field path to + // add is a sub-path of an existing field path in the tree (i.e., a leaf + // node), it means the tree already matchesthe the given path so nothing will + // be added to the tree. If the path matches an existing non-leaf node in the + // tree, that non-leaf node will be turned into a leaf node with all its + // children removed because the path matches all the node's children. + void AddPath(const string& path); + + // Calculate the intersection part of a field path with this tree and add + // the intersection field path into out. + void IntersectPath(const string& path, FieldMaskTree* out); + + // Merge all fields specified by this tree from one message to another. + void MergeMessage(const Message& source, + const FieldMaskUtil::MergeOptions& options, + Message* destination) { + // Do nothing if the tree is empty. + if (root_.children.empty()) { + return; + } + MergeMessage(&root_, source, options, destination); + } + + private: + struct Node { + Node() {} + + ~Node() { ClearChildren(); } + + void ClearChildren() { + for (map<string, Node*>::iterator it = children.begin(); + it != children.end(); ++it) { + delete it->second; + } + children.clear(); + } + + map<string, Node*> children; + + private: + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Node); + }; + + // Merge a sub-tree to mask. This method adds the field paths represented + // by all leaf nodes descended from "node" to mask. + void MergeToFieldMask(const string& prefix, const Node* node, FieldMask* out); + + // Merge all leaf nodes of a sub-tree to another tree. + void MergeLeafNodesToTree(const string& prefix, const Node* node, + FieldMaskTree* out); + + // Merge all fields specified by a sub-tree from one message to another. + void MergeMessage(const Node* node, const Message& source, + const FieldMaskUtil::MergeOptions& options, + Message* destination); + + Node root_; + + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(FieldMaskTree); +}; + +FieldMaskTree::FieldMaskTree() {} + +FieldMaskTree::~FieldMaskTree() {} + +void FieldMaskTree::MergeFromFieldMask(const FieldMask& mask) { + for (int i = 0; i < mask.paths_size(); ++i) { + AddPath(mask.paths(i)); + } +} + +void FieldMaskTree::MergeToFieldMask(FieldMask* mask) { + MergeToFieldMask("", &root_, mask); +} + +void FieldMaskTree::MergeToFieldMask(const string& prefix, const Node* node, + FieldMask* out) { + if (node->children.empty()) { + if (prefix.empty()) { + // This is the root node. + return; + } + out->add_paths(prefix); + return; + } + for (map<string, Node*>::const_iterator it = node->children.begin(); + it != node->children.end(); ++it) { + string current_path = prefix.empty() ? it->first : prefix + "." + it->first; + MergeToFieldMask(current_path, it->second, out); + } +} + +void FieldMaskTree::AddPath(const string& path) { + vector<string> parts = Split(path, "."); + if (parts.empty()) { + return; + } + bool new_branch = false; + Node* node = &root_; + for (int i = 0; i < parts.size(); ++i) { + if (!new_branch && node != &root_ && node->children.empty()) { + // Path matches an existing leaf node. This means the path is already + // coverred by this tree (for example, adding "foo.bar.baz" to a tree + // which already contains "foo.bar"). + return; + } + const string& node_name = parts[i]; + Node*& child = node->children[node_name]; + if (child == NULL) { + new_branch = true; + child = new Node(); + } + node = child; + } + if (!node->children.empty()) { + node->ClearChildren(); + } +} + +void FieldMaskTree::IntersectPath(const string& path, FieldMaskTree* out) { + vector<string> parts = Split(path, "."); + if (parts.empty()) { + return; + } + const Node* node = &root_; + for (int i = 0; i < parts.size(); ++i) { + if (node->children.empty()) { + if (node != &root_) { + out->AddPath(path); + } + return; + } + const string& node_name = parts[i]; + const Node* result = FindPtrOrNull(node->children, node_name); + if (result == NULL) { + // No intersection found. + return; + } + node = result; + } + // Now we found a matching node with the given path. Add all leaf nodes + // to out. + MergeLeafNodesToTree(path, node, out); +} + +void FieldMaskTree::MergeLeafNodesToTree(const string& prefix, const Node* node, + FieldMaskTree* out) { + if (node->children.empty()) { + out->AddPath(prefix); + } + for (map<string, Node*>::const_iterator it = node->children.begin(); + it != node->children.end(); ++it) { + string current_path = prefix.empty() ? it->first : prefix + "." + it->first; + MergeLeafNodesToTree(current_path, it->second, out); + } +} + +void FieldMaskTree::MergeMessage(const Node* node, const Message& source, + const FieldMaskUtil::MergeOptions& options, + Message* destination) { + GOOGLE_DCHECK(!node->children.empty()); + const Reflection* source_reflection = source.GetReflection(); + const Reflection* destination_reflection = destination->GetReflection(); + const Descriptor* descriptor = source.GetDescriptor(); + for (map<string, Node*>::const_iterator it = node->children.begin(); + it != node->children.end(); ++it) { + const string& field_name = it->first; + const Node* child = it->second; + const FieldDescriptor* field = descriptor->FindFieldByName(field_name); + if (field == NULL) { + GOOGLE_LOG(ERROR) << "Cannot find field \"" << field_name << "\" in message " + << descriptor->full_name(); + continue; + } + if (!child->children.empty()) { + // Sub-paths are only allowed for singular message fields. + if (field->is_repeated() || + field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) { + GOOGLE_LOG(ERROR) << "Field \"" << field_name << "\" in message " + << descriptor->full_name() + << " is not a singular message field and cannot " + << "have sub-fields."; + continue; + } + MergeMessage(child, source_reflection->GetMessage(source, field), options, + destination_reflection->MutableMessage(destination, field)); + continue; + } + if (!field->is_repeated()) { + switch (field->cpp_type()) { +#define COPY_VALUE(TYPE, Name) \ + case FieldDescriptor::CPPTYPE_##TYPE: { \ + destination_reflection->Set##Name( \ + destination, field, source_reflection->Get##Name(source, field)); \ + break; \ + } + COPY_VALUE(BOOL, Bool) + COPY_VALUE(INT32, Int32) + COPY_VALUE(INT64, Int64) + COPY_VALUE(UINT32, UInt32) + COPY_VALUE(UINT64, UInt64) + COPY_VALUE(FLOAT, Float) + COPY_VALUE(DOUBLE, Double) + COPY_VALUE(ENUM, Enum) + COPY_VALUE(STRING, String) +#undef COPY_VALUE + case FieldDescriptor::CPPTYPE_MESSAGE: { + if (options.replace_message_fields()) { + destination_reflection->ClearField(destination, field); + } + if (source_reflection->HasField(source, field)) { + destination_reflection->MutableMessage(destination, field) + ->MergeFrom(source_reflection->GetMessage(source, field)); + } + break; + } + } + } else { + if (options.replace_repeated_fields()) { + destination_reflection->ClearField(destination, field); + } + switch (field->cpp_type()) { +#define COPY_REPEATED_VALUE(TYPE, Name) \ + case FieldDescriptor::CPPTYPE_##TYPE: { \ + int size = source_reflection->FieldSize(source, field); \ + for (int i = 0; i < size; ++i) { \ + destination_reflection->Add##Name( \ + destination, field, \ + source_reflection->GetRepeated##Name(source, field, i)); \ + } \ + break; \ + } + COPY_REPEATED_VALUE(BOOL, Bool) + COPY_REPEATED_VALUE(INT32, Int32) + COPY_REPEATED_VALUE(INT64, Int64) + COPY_REPEATED_VALUE(UINT32, UInt32) + COPY_REPEATED_VALUE(UINT64, UInt64) + COPY_REPEATED_VALUE(FLOAT, Float) + COPY_REPEATED_VALUE(DOUBLE, Double) + COPY_REPEATED_VALUE(ENUM, Enum) + COPY_REPEATED_VALUE(STRING, String) +#undef COPY_REPEATED_VALUE + case FieldDescriptor::CPPTYPE_MESSAGE: { + int size = source_reflection->FieldSize(source, field); + for (int i = 0; i < size; ++i) { + destination_reflection->AddMessage(destination, field) + ->MergeFrom( + source_reflection->GetRepeatedMessage(source, field, i)); + } + break; + } + } + } + } +} + +} // namespace + +void FieldMaskUtil::ToCanonicalForm(const FieldMask& mask, FieldMask* out) { + FieldMaskTree tree; + tree.MergeFromFieldMask(mask); + out->Clear(); + tree.MergeToFieldMask(out); +} + +void FieldMaskUtil::Union(const FieldMask& mask1, const FieldMask& mask2, + FieldMask* out) { + FieldMaskTree tree; + tree.MergeFromFieldMask(mask1); + tree.MergeFromFieldMask(mask2); + out->Clear(); + tree.MergeToFieldMask(out); +} + +void FieldMaskUtil::Intersect(const FieldMask& mask1, const FieldMask& mask2, + FieldMask* out) { + FieldMaskTree tree, intersection; + tree.MergeFromFieldMask(mask1); + for (int i = 0; i < mask2.paths_size(); ++i) { + tree.IntersectPath(mask2.paths(i), &intersection); + } + out->Clear(); + intersection.MergeToFieldMask(out); +} + +bool FieldMaskUtil::IsPathInFieldMask(const string& path, + const FieldMask& mask) { + for (int i = 0; i < mask.paths_size(); ++i) { + const string& mask_path = mask.paths(i); + if (path == mask_path) { + return true; + } else if (mask_path.length() < path.length()) { + // Also check whether mask.paths(i) is a prefix of path. + if (path.compare(0, mask_path.length() + 1, mask_path + ".") == 0) { + return true; + } + } + } + return false; +} + +void FieldMaskUtil::MergeMessageTo(const Message& source, const FieldMask& mask, + const MergeOptions& options, + Message* destination) { + GOOGLE_CHECK(source.GetDescriptor() == destination->GetDescriptor()); + // Build a FieldMaskTree and walk through the tree to merge all specified + // fields. + FieldMaskTree tree; + tree.MergeFromFieldMask(mask); + tree.MergeMessage(source, options, destination); +} + +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/field_mask_util.h b/src/google/protobuf/util/field_mask_util.h new file mode 100644 index 00000000..c99c34f8 --- /dev/null +++ b/src/google/protobuf/util/field_mask_util.h @@ -0,0 +1,146 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_FIELD_MASK_UTIL_H__ +#define GOOGLE_PROTOBUF_UTIL_FIELD_MASK_UTIL_H__ + +#include <string> + +#include <google/protobuf/descriptor.h> +#include <google/protobuf/field_mask.pb.h> + +namespace google { +namespace protobuf { +namespace util { + +class LIBPROTOBUF_EXPORT FieldMaskUtil { + typedef google::protobuf::FieldMask FieldMask; + + public: + // Converts FieldMask to/from string, formatted according to proto3 JSON + // spec for FieldMask (e.g., "foo,bar,baz.quz"). + static string ToString(const FieldMask& mask); + static void FromString(const string& str, FieldMask* out); + + // Checks whether the given path is valid for type T. + template <typename T> + static bool IsValidPath(const string& path) { + return InternalIsValidPath(T::descriptor(), path); + } + + // Checks whether the given FieldMask is valid for type T. + template <typename T> + static bool IsValidFieldMask(const FieldMask& mask) { + for (int i = 0; i < mask.paths_size(); ++i) { + if (!InternalIsValidPath(T::descriptor(), mask.paths(i))) return false; + } + return true; + } + + // Adds a path to FieldMask after checking whether the given path is valid. + // This method check-fails if the path is not a valid path for type T. + template <typename T> + static void AddPathToFieldMask(const string& path, FieldMask* mask) { + GOOGLE_CHECK(IsValidPath<T>(path)); + mask->add_paths(path); + } + + // Creates a FieldMask with all fields of type T. This FieldMask only + // contains fields of T but not any sub-message fields. + template <typename T> + static void GetFieldMaskForAllFields(FieldMask* out) { + InternalGetFieldMaskForAllFields(T::descriptor(), out); + } + + // Converts a FieldMask to the canonical form. It will: + // 1. Remove paths that are covered by another path. For example, + // "foo.bar" is covered by "foo" and will be removed if "foo" + // is also in the FieldMask. + // 2. Sort all paths in alphabetical order. + static void ToCanonicalForm(const FieldMask& mask, FieldMask* out); + + // Creates an union of two FieldMasks. + static void Union(const FieldMask& mask1, const FieldMask& mask2, + FieldMask* out); + + // Creates an intersection of two FieldMasks. + static void Intersect(const FieldMask& mask1, const FieldMask& mask2, + FieldMask* out); + + // Returns true if path is covered by the given FieldMask. Note that path + // "foo.bar" covers all paths like "foo.bar.baz", "foo.bar.quz.x", etc. + static bool IsPathInFieldMask(const string& path, const FieldMask& mask); + + class MergeOptions; + // Merges fields specified in a FieldMask into another message. + static void MergeMessageTo(const Message& source, const FieldMask& mask, + const MergeOptions& options, Message* destination); + + private: + static bool InternalIsValidPath(const Descriptor* descriptor, + const string& path); + + static void InternalGetFieldMaskForAllFields(const Descriptor* descriptor, + FieldMask* out); +}; + +class LIBPROTOBUF_EXPORT FieldMaskUtil::MergeOptions { + public: + MergeOptions() + : replace_message_fields_(false), replace_repeated_fields_(false) {} + // When merging message fields, the default behavior is to merge the + // content of two message fields together. If you instead want to use + // the field from the source message to replace the corresponding field + // in the destination message, set this flag to true. When this flag is set, + // specified submessage fields that are missing in source will be cleared in + // destination. + void set_replace_message_fields(bool value) { + replace_message_fields_ = value; + } + bool replace_message_fields() const { return replace_message_fields_; } + // The default merging behavior will append entries from the source + // repeated field to the destination repeated field. If you only want + // to keep the entries from the source repeated field, set this flag + // to true. + void set_replace_repeated_fields(bool value) { + replace_repeated_fields_ = value; + } + bool replace_repeated_fields() const { return replace_repeated_fields_; } + + private: + bool replace_message_fields_; + bool replace_repeated_fields_; +}; + +} // namespace util +} // namespace protobuf + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_FIELD_MASK_UTIL_H__ diff --git a/src/google/protobuf/util/field_mask_util_test.cc b/src/google/protobuf/util/field_mask_util_test.cc new file mode 100644 index 00000000..a9523250 --- /dev/null +++ b/src/google/protobuf/util/field_mask_util_test.cc @@ -0,0 +1,395 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <google/protobuf/util/field_mask_util.h> + +#include <google/protobuf/field_mask.pb.h> +#include <google/protobuf/unittest.pb.h> +#include <google/protobuf/test_util.h> +#include <gtest/gtest.h> + +namespace google { +namespace protobuf { +namespace util { +namespace { + +using protobuf_unittest::TestAllTypes; +using protobuf_unittest::NestedTestAllTypes; +using google::protobuf::FieldMask; + +TEST(FieldMaskUtilTest, StringFormat) { + FieldMask mask; + EXPECT_EQ("", FieldMaskUtil::ToString(mask)); + mask.add_paths("foo"); + EXPECT_EQ("foo", FieldMaskUtil::ToString(mask)); + mask.add_paths("bar"); + EXPECT_EQ("foo,bar", FieldMaskUtil::ToString(mask)); + + FieldMaskUtil::FromString("", &mask); + EXPECT_EQ(0, mask.paths_size()); + FieldMaskUtil::FromString("foo", &mask); + EXPECT_EQ(1, mask.paths_size()); + EXPECT_EQ("foo", mask.paths(0)); + FieldMaskUtil::FromString("foo,bar", &mask); + EXPECT_EQ(2, mask.paths_size()); + EXPECT_EQ("foo", mask.paths(0)); + EXPECT_EQ("bar", mask.paths(1)); +} + +TEST(FieldMaskUtilTest, TestIsVaildPath) { + EXPECT_TRUE(FieldMaskUtil::IsValidPath<TestAllTypes>("optional_int32")); + EXPECT_FALSE(FieldMaskUtil::IsValidPath<TestAllTypes>("optional_nonexist")); + EXPECT_TRUE( + FieldMaskUtil::IsValidPath<TestAllTypes>("optional_nested_message.bb")); + EXPECT_FALSE(FieldMaskUtil::IsValidPath<TestAllTypes>( + "optional_nested_message.nonexist")); + // FieldMask cannot be used to specify sub-fields of a repeated message. + EXPECT_FALSE( + FieldMaskUtil::IsValidPath<TestAllTypes>("repeated_nested_message.bb")); +} + +TEST(FieldMaskUtilTest, TestIsValidFieldMask) { + FieldMask mask; + FieldMaskUtil::FromString("optional_int32,optional_nested_message.bb", &mask); + EXPECT_TRUE(FieldMaskUtil::IsValidFieldMask<TestAllTypes>(mask)); + + FieldMaskUtil::FromString( + "optional_int32,optional_nested_message.bb,optional_nonexist", &mask); + EXPECT_FALSE(FieldMaskUtil::IsValidFieldMask<TestAllTypes>(mask)); +} + +TEST(FieldMaskUtilTest, TestGetFieldMaskForAllFields) { + FieldMask mask; + FieldMaskUtil::GetFieldMaskForAllFields<TestAllTypes::NestedMessage>(&mask); + EXPECT_EQ(1, mask.paths_size()); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("bb", mask)); + + FieldMaskUtil::GetFieldMaskForAllFields<TestAllTypes>(&mask); + EXPECT_EQ(76, mask.paths_size()); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_int32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_int64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_uint32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_uint64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_sint32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_sint64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_fixed32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_fixed64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_sfixed32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_sfixed64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_float", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_double", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_bool", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_string", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_bytes", mask)); + EXPECT_TRUE( + FieldMaskUtil::IsPathInFieldMask("optional_nested_message", mask)); + EXPECT_TRUE( + FieldMaskUtil::IsPathInFieldMask("optional_foreign_message", mask)); + EXPECT_TRUE( + FieldMaskUtil::IsPathInFieldMask("optional_import_message", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_nested_enum", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_foreign_enum", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("optional_import_enum", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_int32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_int64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_uint32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_uint64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_sint32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_sint64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_fixed32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_fixed64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_sfixed32", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_sfixed64", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_float", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_double", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_bool", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_string", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_bytes", mask)); + EXPECT_TRUE( + FieldMaskUtil::IsPathInFieldMask("repeated_nested_message", mask)); + EXPECT_TRUE( + FieldMaskUtil::IsPathInFieldMask("repeated_foreign_message", mask)); + EXPECT_TRUE( + FieldMaskUtil::IsPathInFieldMask("repeated_import_message", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_nested_enum", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_foreign_enum", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("repeated_import_enum", mask)); +} + +TEST(FieldMaskUtilTest, TestToCanonicalForm) { + FieldMask in, out; + // Paths will be sorted. + FieldMaskUtil::FromString("baz.quz,bar,foo", &in); + FieldMaskUtil::ToCanonicalForm(in, &out); + EXPECT_EQ("bar,baz.quz,foo", FieldMaskUtil::ToString(out)); + // Duplicated paths will be removed. + FieldMaskUtil::FromString("foo,bar,foo", &in); + FieldMaskUtil::ToCanonicalForm(in, &out); + EXPECT_EQ("bar,foo", FieldMaskUtil::ToString(out)); + // Sub-paths of other paths will be removed. + FieldMaskUtil::FromString("foo.b1,bar.b1,foo.b2,bar", &in); + FieldMaskUtil::ToCanonicalForm(in, &out); + EXPECT_EQ("bar,foo.b1,foo.b2", FieldMaskUtil::ToString(out)); + + // Test more deeply nested cases. + FieldMaskUtil::FromString( + "foo.bar.baz1," + "foo.bar.baz2.quz," + "foo.bar.baz2", + &in); + FieldMaskUtil::ToCanonicalForm(in, &out); + EXPECT_EQ("foo.bar.baz1,foo.bar.baz2", FieldMaskUtil::ToString(out)); + FieldMaskUtil::FromString( + "foo.bar.baz1," + "foo.bar.baz2," + "foo.bar.baz2.quz", + &in); + FieldMaskUtil::ToCanonicalForm(in, &out); + EXPECT_EQ("foo.bar.baz1,foo.bar.baz2", FieldMaskUtil::ToString(out)); + FieldMaskUtil::FromString( + "foo.bar.baz1," + "foo.bar.baz2," + "foo.bar.baz2.quz," + "foo.bar", + &in); + FieldMaskUtil::ToCanonicalForm(in, &out); + EXPECT_EQ("foo.bar", FieldMaskUtil::ToString(out)); + FieldMaskUtil::FromString( + "foo.bar.baz1," + "foo.bar.baz2," + "foo.bar.baz2.quz," + "foo", + &in); + FieldMaskUtil::ToCanonicalForm(in, &out); + EXPECT_EQ("foo", FieldMaskUtil::ToString(out)); +} + +TEST(FieldMaskUtilTest, TestUnion) { + FieldMask mask1, mask2, out; + // Test cases without overlapping. + FieldMaskUtil::FromString("foo,baz", &mask1); + FieldMaskUtil::FromString("bar,quz", &mask2); + FieldMaskUtil::Union(mask1, mask2, &out); + EXPECT_EQ("bar,baz,foo,quz", FieldMaskUtil::ToString(out)); + // Overlap with duplicated paths. + FieldMaskUtil::FromString("foo,baz.bb", &mask1); + FieldMaskUtil::FromString("baz.bb,quz", &mask2); + FieldMaskUtil::Union(mask1, mask2, &out); + EXPECT_EQ("baz.bb,foo,quz", FieldMaskUtil::ToString(out)); + // Overlap with paths covering some other paths. + FieldMaskUtil::FromString("foo.bar.baz,quz", &mask1); + FieldMaskUtil::FromString("foo.bar,bar", &mask2); + FieldMaskUtil::Union(mask1, mask2, &out); + EXPECT_EQ("bar,foo.bar,quz", FieldMaskUtil::ToString(out)); +} + +TEST(FieldMaskUtilTest, TestIntersect) { + FieldMask mask1, mask2, out; + // Test cases without overlapping. + FieldMaskUtil::FromString("foo,baz", &mask1); + FieldMaskUtil::FromString("bar,quz", &mask2); + FieldMaskUtil::Intersect(mask1, mask2, &out); + EXPECT_EQ("", FieldMaskUtil::ToString(out)); + // Overlap with duplicated paths. + FieldMaskUtil::FromString("foo,baz.bb", &mask1); + FieldMaskUtil::FromString("baz.bb,quz", &mask2); + FieldMaskUtil::Intersect(mask1, mask2, &out); + EXPECT_EQ("baz.bb", FieldMaskUtil::ToString(out)); + // Overlap with paths covering some other paths. + FieldMaskUtil::FromString("foo.bar.baz,quz", &mask1); + FieldMaskUtil::FromString("foo.bar,bar", &mask2); + FieldMaskUtil::Intersect(mask1, mask2, &out); + EXPECT_EQ("foo.bar.baz", FieldMaskUtil::ToString(out)); +} + +TEST(FieldMaskUtilTest, TestIspathInFieldMask) { + FieldMask mask; + FieldMaskUtil::FromString("foo.bar", &mask); + EXPECT_FALSE(FieldMaskUtil::IsPathInFieldMask("", mask)); + EXPECT_FALSE(FieldMaskUtil::IsPathInFieldMask("foo", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("foo.bar", mask)); + EXPECT_TRUE(FieldMaskUtil::IsPathInFieldMask("foo.bar.baz", mask)); + EXPECT_FALSE(FieldMaskUtil::IsPathInFieldMask("foo.bar0.baz", mask)); +} + +TEST(FieldMaskUtilTest, MergeMessage) { + TestAllTypes src, dst; + TestUtil::SetAllFields(&src); + FieldMaskUtil::MergeOptions options; + +#define TEST_MERGE_ONE_PRIMITIVE_FIELD(field_name) \ + { \ + TestAllTypes tmp; \ + tmp.set_##field_name(src.field_name()); \ + FieldMask mask; \ + mask.add_paths(#field_name); \ + dst.Clear(); \ + FieldMaskUtil::MergeMessageTo(src, mask, options, &dst); \ + EXPECT_EQ(tmp.DebugString(), dst.DebugString()); \ + } + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_int32) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_int64) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_uint32) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_uint64) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_sint32) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_sint64) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_fixed32) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_fixed64) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_sfixed32) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_sfixed64) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_float) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_double) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_bool) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_string) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_bytes) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_nested_enum) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_foreign_enum) + TEST_MERGE_ONE_PRIMITIVE_FIELD(optional_import_enum) +#undef TEST_MERGE_ONE_PRIMITIVE_FIELD + +#define TEST_MERGE_ONE_FIELD(field_name) \ + { \ + TestAllTypes tmp; \ + *tmp.mutable_##field_name() = src.field_name(); \ + FieldMask mask; \ + mask.add_paths(#field_name); \ + dst.Clear(); \ + FieldMaskUtil::MergeMessageTo(src, mask, options, &dst); \ + EXPECT_EQ(tmp.DebugString(), dst.DebugString()); \ + } + TEST_MERGE_ONE_FIELD(optional_nested_message) + TEST_MERGE_ONE_FIELD(optional_foreign_message) + TEST_MERGE_ONE_FIELD(optional_import_message) + + TEST_MERGE_ONE_FIELD(repeated_int32) + TEST_MERGE_ONE_FIELD(repeated_int64) + TEST_MERGE_ONE_FIELD(repeated_uint32) + TEST_MERGE_ONE_FIELD(repeated_uint64) + TEST_MERGE_ONE_FIELD(repeated_sint32) + TEST_MERGE_ONE_FIELD(repeated_sint64) + TEST_MERGE_ONE_FIELD(repeated_fixed32) + TEST_MERGE_ONE_FIELD(repeated_fixed64) + TEST_MERGE_ONE_FIELD(repeated_sfixed32) + TEST_MERGE_ONE_FIELD(repeated_sfixed64) + TEST_MERGE_ONE_FIELD(repeated_float) + TEST_MERGE_ONE_FIELD(repeated_double) + TEST_MERGE_ONE_FIELD(repeated_bool) + TEST_MERGE_ONE_FIELD(repeated_string) + TEST_MERGE_ONE_FIELD(repeated_bytes) + TEST_MERGE_ONE_FIELD(repeated_nested_message) + TEST_MERGE_ONE_FIELD(repeated_foreign_message) + TEST_MERGE_ONE_FIELD(repeated_import_message) + TEST_MERGE_ONE_FIELD(repeated_nested_enum) + TEST_MERGE_ONE_FIELD(repeated_foreign_enum) + TEST_MERGE_ONE_FIELD(repeated_import_enum) +#undef TEST_MERGE_ONE_FIELD + + // Test merge nested fields. + NestedTestAllTypes nested_src, nested_dst; + nested_src.mutable_child()->mutable_payload()->set_optional_int32(1234); + nested_src.mutable_child() + ->mutable_child() + ->mutable_payload() + ->set_optional_int32(5678); + FieldMask mask; + FieldMaskUtil::FromString("child.payload", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_EQ(1234, nested_dst.child().payload().optional_int32()); + EXPECT_EQ(0, nested_dst.child().child().payload().optional_int32()); + + FieldMaskUtil::FromString("child.child.payload", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_EQ(1234, nested_dst.child().payload().optional_int32()); + EXPECT_EQ(5678, nested_dst.child().child().payload().optional_int32()); + + nested_dst.Clear(); + FieldMaskUtil::FromString("child.child.payload", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_EQ(0, nested_dst.child().payload().optional_int32()); + EXPECT_EQ(5678, nested_dst.child().child().payload().optional_int32()); + + nested_dst.Clear(); + FieldMaskUtil::FromString("child", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_EQ(1234, nested_dst.child().payload().optional_int32()); + EXPECT_EQ(5678, nested_dst.child().child().payload().optional_int32()); + + // Test MergeOptions. + + nested_dst.Clear(); + nested_dst.mutable_child()->mutable_payload()->set_optional_int64(4321); + // Message fields will be merged by default. + FieldMaskUtil::FromString("child.payload", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_EQ(1234, nested_dst.child().payload().optional_int32()); + EXPECT_EQ(4321, nested_dst.child().payload().optional_int64()); + // Change the behavior to replace message fields. + options.set_replace_message_fields(true); + FieldMaskUtil::FromString("child.payload", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_EQ(1234, nested_dst.child().payload().optional_int32()); + EXPECT_EQ(0, nested_dst.child().payload().optional_int64()); + + // By default, fields missing in source are not cleared in destination. + options.set_replace_message_fields(false); + nested_dst.mutable_payload(); + EXPECT_TRUE(nested_dst.has_payload()); + FieldMaskUtil::FromString("payload", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_TRUE(nested_dst.has_payload()); + // But they are cleared when replacing message fields. + options.set_replace_message_fields(true); + nested_dst.Clear(); + nested_dst.mutable_payload(); + FieldMaskUtil::FromString("payload", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + EXPECT_FALSE(nested_dst.has_payload()); + + nested_src.mutable_payload()->add_repeated_int32(1234); + nested_dst.mutable_payload()->add_repeated_int32(5678); + // Repeated fields will be appended by default. + FieldMaskUtil::FromString("payload.repeated_int32", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + ASSERT_EQ(2, nested_dst.payload().repeated_int32_size()); + EXPECT_EQ(5678, nested_dst.payload().repeated_int32(0)); + EXPECT_EQ(1234, nested_dst.payload().repeated_int32(1)); + // Change the behavior to replace repeated fields. + options.set_replace_repeated_fields(true); + FieldMaskUtil::FromString("payload.repeated_int32", &mask); + FieldMaskUtil::MergeMessageTo(nested_src, mask, options, &nested_dst); + ASSERT_EQ(1, nested_dst.payload().repeated_int32_size()); + EXPECT_EQ(1234, nested_dst.payload().repeated_int32(0)); +} + + +} // namespace +} // namespace util +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/util/internal/testdata/oneofs.proto b/src/google/protobuf/util/internal/testdata/oneofs.proto new file mode 100644 index 00000000..5bc9fa08 --- /dev/null +++ b/src/google/protobuf/util/internal/testdata/oneofs.proto @@ -0,0 +1,68 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Proto to test oneofs. +syntax = "proto3"; + +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; + +package google.protobuf.testing.oneofs; +option java_package = "com.google.protobuf.testing.oneofs"; + +message OneOfsRequest { + string value = 1; + oneof data { + string str_data = 2; + int32 int_data = 3; + // Simple message + Data message_data = 4; + // Well known types + google.protobuf.Struct struct_data = 5; + google.protobuf.Value value_data = 6; + google.protobuf.ListValue list_value_data = 7; + google.protobuf.Timestamp ts_data = 8; + } + google.protobuf.Any any_data = 19; +} + +message Data { + int32 data_value = 1; +} + +message Response { + string value = 1; +} + +service TestService { + // Test call. + rpc Call(OneOfsRequest) returns (Response); +} diff --git a/src/google/protobuf/util/time_util.cc b/src/google/protobuf/util/time_util.cc new file mode 100644 index 00000000..c782d691 --- /dev/null +++ b/src/google/protobuf/util/time_util.cc @@ -0,0 +1,525 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <google/protobuf/util/time_util.h> + +#include <google/protobuf/stubs/time.h> +#include <google/protobuf/stubs/int128.h> +#include <google/protobuf/stubs/strutil.h> +#include <google/protobuf/stubs/stringprintf.h> +#include <google/protobuf/duration.pb.h> +#include <google/protobuf/timestamp.pb.h> + +namespace google { +namespace protobuf { +namespace util { + +using google::protobuf::Timestamp; +using google::protobuf::Duration; + +namespace { +static const int kNanosPerSecond = 1000000000; +static const int kMicrosPerSecond = 1000000; +static const int kMillisPerSecond = 1000; +static const int kNanosPerMillisecond = 1000000; +static const int kMicrosPerMillisecond = 1000; +static const int kNanosPerMicrosecond = 1000; +static const int kSecondsPerMinute = 60; // Note that we ignore leap seconds. +static const int kSecondsPerHour = 3600; +static const char kTimestampFormat[] = "%E4Y-%m-%dT%H:%M:%S"; + +template <typename T> +T CreateNormalized(int64 seconds, int64 nanos); + +template <> +Timestamp CreateNormalized(int64 seconds, int64 nanos) { + // Make sure nanos is in the range. + if (nanos <= -kNanosPerSecond || nanos >= kNanosPerSecond) { + seconds += nanos / kNanosPerSecond; + nanos = nanos % kNanosPerSecond; + } + // For Timestamp nanos should be in the range [0, 999999999] + if (nanos < 0) { + seconds -= 1; + nanos += kNanosPerSecond; + } + GOOGLE_DCHECK(seconds >= TimeUtil::kTimestampMinSeconds && + seconds <= TimeUtil::kTimestampMaxSeconds); + Timestamp result; + result.set_seconds(seconds); + result.set_nanos(static_cast<int32>(nanos)); + return result; +} + +template <> +Duration CreateNormalized(int64 seconds, int64 nanos) { + // Make sure nanos is in the range. + if (nanos <= -kNanosPerSecond || nanos >= kNanosPerSecond) { + seconds += nanos / kNanosPerSecond; + nanos = nanos % kNanosPerSecond; + } + // nanos should have the same sign as seconds. + if (seconds < 0 && nanos > 0) { + seconds += 1; + nanos -= kNanosPerSecond; + } else if (seconds > 0 && nanos < 0) { + seconds -= 1; + nanos += kNanosPerSecond; + } + GOOGLE_DCHECK(seconds >= TimeUtil::kDurationMinSeconds && + seconds <= TimeUtil::kDurationMaxSeconds); + Duration result; + result.set_seconds(seconds); + result.set_nanos(static_cast<int32>(nanos)); + return result; +} + +// Format nanoseconds with either 3, 6, or 9 digits depending on the required +// precision to represent the exact value. +string FormatNanos(int32 nanos) { + if (nanos % kNanosPerMillisecond == 0) { + return StringPrintf("%03d", nanos / kNanosPerMillisecond); + } else if (nanos % kNanosPerMicrosecond == 0) { + return StringPrintf("%06d", nanos / kNanosPerMicrosecond); + } else { + return StringPrintf("%09d", nanos); + } +} + +string FormatTime(int64 seconds, int32 nanos) { + return ::google::protobuf::internal::FormatTime(seconds, nanos); +} + +bool ParseTime(const string& value, int64* seconds, int32* nanos) { + return ::google::protobuf::internal::ParseTime(value, seconds, nanos); +} + +void CurrentTime(int64* seconds, int32* nanos) { + return ::google::protobuf::internal::GetCurrentTime(seconds, nanos); +} + +// Truncates the remainder part after division. +int64 RoundTowardZero(int64 value, int64 divider) { + int64 result = value / divider; + int64 remainder = value % divider; + // Before C++11, the sign of the remainder is implementation dependent if + // any of the operands is negative. Here we try to enforce C++11's "rounded + // toward zero" semantics. For example, for (-5) / 2 an implementation may + // give -3 as the result with the remainder being 1. This function ensures + // we always return -2 (closer to zero) regardless of the implementation. + if (result < 0 && remainder > 0) { + return result + 1; + } else { + return result; + } +} +} // namespace + +string TimeUtil::ToString(const Timestamp& timestamp) { + return FormatTime(timestamp.seconds(), timestamp.nanos()); +} + +bool TimeUtil::FromString(const string& value, Timestamp* timestamp) { + int64 seconds; + int32 nanos; + if (!ParseTime(value, &seconds, &nanos)) { + return false; + } + *timestamp = CreateNormalized<Timestamp>(seconds, nanos); + return true; +} + +Timestamp TimeUtil::GetCurrentTime() { + int64 seconds; + int32 nanos; + CurrentTime(&seconds, &nanos); + return CreateNormalized<Timestamp>(seconds, nanos); +} + +Timestamp TimeUtil::GetEpoch() { return Timestamp(); } + +string TimeUtil::ToString(const Duration& duration) { + string result; + int64 seconds = duration.seconds(); + int32 nanos = duration.nanos(); + if (seconds < 0 || nanos < 0) { + result += "-"; + seconds = -seconds; + nanos = -nanos; + } + result += StringPrintf("%" GOOGLE_LL_FORMAT "d", seconds); + if (nanos != 0) { + result += "." + FormatNanos(nanos); + } + result += "s"; + return result; +} + +static int64 Pow(int64 x, int y) { + int64 result = 1; + for (int i = 0; i < y; ++i) { + result *= x; + } + return result; +} + +bool TimeUtil::FromString(const string& value, Duration* duration) { + if (value.length() <= 1 || value[value.length() - 1] != 's') { + return false; + } + bool negative = (value[0] == '-'); + int sign_length = (negative ? 1 : 0); + // Parse the duration value as two integers rather than a float value + // to avoid precision loss. + string seconds_part, nanos_part; + size_t pos = value.find_last_of("."); + if (pos == string::npos) { + seconds_part = value.substr(sign_length, value.length() - 1 - sign_length); + nanos_part = "0"; + } else { + seconds_part = value.substr(sign_length, pos - sign_length); + nanos_part = value.substr(pos + 1, value.length() - pos - 2); + } + char* end; + int64 seconds = strto64(seconds_part.c_str(), &end, 10); + if (end != seconds_part.c_str() + seconds_part.length()) { + return false; + } + int64 nanos = strto64(nanos_part.c_str(), &end, 10); + if (end != nanos_part.c_str() + nanos_part.length()) { + return false; + } + nanos = nanos * Pow(10, 9 - nanos_part.length()); + if (negative) { + // If a Duration is negative, both seconds and nanos should be negative. + seconds = -seconds; + nanos = -nanos; + } + duration->set_seconds(seconds); + duration->set_nanos(static_cast<int32>(nanos)); + return true; +} + +Duration TimeUtil::NanosecondsToDuration(int64 nanos) { + return CreateNormalized<Duration>(nanos / kNanosPerSecond, + nanos % kNanosPerSecond); +} + +Duration TimeUtil::MicrosecondsToDuration(int64 micros) { + return CreateNormalized<Duration>( + micros / kMicrosPerSecond, + (micros % kMicrosPerSecond) * kNanosPerMicrosecond); +} + +Duration TimeUtil::MillisecondsToDuration(int64 millis) { + return CreateNormalized<Duration>( + millis / kMillisPerSecond, + (millis % kMillisPerSecond) * kNanosPerMillisecond); +} + +Duration TimeUtil::SecondsToDuration(int64 seconds) { + return CreateNormalized<Duration>(seconds, 0); +} + +Duration TimeUtil::MinutesToDuration(int64 minutes) { + return CreateNormalized<Duration>(minutes * kSecondsPerMinute, 0); +} + +Duration TimeUtil::HoursToDuration(int64 hours) { + return CreateNormalized<Duration>(hours * kSecondsPerHour, 0); +} + +int64 TimeUtil::DurationToNanoseconds(const Duration& duration) { + return duration.seconds() * kNanosPerSecond + duration.nanos(); +} + +int64 TimeUtil::DurationToMicroseconds(const Duration& duration) { + return duration.seconds() * kMicrosPerSecond + + RoundTowardZero(duration.nanos(), kNanosPerMicrosecond); +} + +int64 TimeUtil::DurationToMilliseconds(const Duration& duration) { + return duration.seconds() * kMillisPerSecond + + RoundTowardZero(duration.nanos(), kNanosPerMillisecond); +} + +int64 TimeUtil::DurationToSeconds(const Duration& duration) { + return duration.seconds(); +} + +int64 TimeUtil::DurationToMinutes(const Duration& duration) { + return RoundTowardZero(duration.seconds(), kSecondsPerMinute); +} + +int64 TimeUtil::DurationToHours(const Duration& duration) { + return RoundTowardZero(duration.seconds(), kSecondsPerHour); +} + +Timestamp TimeUtil::NanosecondsToTimestamp(int64 nanos) { + return CreateNormalized<Timestamp>(nanos / kNanosPerSecond, + nanos % kNanosPerSecond); +} + +Timestamp TimeUtil::MicrosecondsToTimestamp(int64 micros) { + return CreateNormalized<Timestamp>( + micros / kMicrosPerSecond, + micros % kMicrosPerSecond * kNanosPerMicrosecond); +} + +Timestamp TimeUtil::MillisecondsToTimestamp(int64 millis) { + return CreateNormalized<Timestamp>( + millis / kMillisPerSecond, + millis % kMillisPerSecond * kNanosPerMillisecond); +} + +Timestamp TimeUtil::SecondsToTimestamp(int64 seconds) { + return CreateNormalized<Timestamp>(seconds, 0); +} + +int64 TimeUtil::TimestampToNanoseconds(const Timestamp& timestamp) { + return timestamp.seconds() * kNanosPerSecond + timestamp.nanos(); +} + +int64 TimeUtil::TimestampToMicroseconds(const Timestamp& timestamp) { + return timestamp.seconds() * kMicrosPerSecond + + RoundTowardZero(timestamp.nanos(), kNanosPerMicrosecond); +} + +int64 TimeUtil::TimestampToMilliseconds(const Timestamp& timestamp) { + return timestamp.seconds() * kMillisPerSecond + + RoundTowardZero(timestamp.nanos(), kNanosPerMillisecond); +} + +int64 TimeUtil::TimestampToSeconds(const Timestamp& timestamp) { + return timestamp.seconds(); +} + +Timestamp TimeUtil::TimeTToTimestamp(time_t value) { + return CreateNormalized<Timestamp>(static_cast<int64>(value), 0); +} + +time_t TimeUtil::TimestampToTimeT(const Timestamp& value) { + return static_cast<time_t>(value.seconds()); +} + +Timestamp TimeUtil::TimevalToTimestamp(const timeval& value) { + return CreateNormalized<Timestamp>(value.tv_sec, + value.tv_usec * kNanosPerMicrosecond); +} + +timeval TimeUtil::TimestampToTimeval(const Timestamp& value) { + timeval result; + result.tv_sec = value.seconds(); + result.tv_usec = RoundTowardZero(value.nanos(), kNanosPerMicrosecond); + return result; +} + +Duration TimeUtil::TimevalToDuration(const timeval& value) { + return CreateNormalized<Duration>(value.tv_sec, + value.tv_usec * kNanosPerMicrosecond); +} + +timeval TimeUtil::DurationToTimeval(const Duration& value) { + timeval result; + result.tv_sec = value.seconds(); + result.tv_usec = RoundTowardZero(value.nanos(), kNanosPerMicrosecond); + // timeval.tv_usec's range is [0, 1000000) + if (result.tv_usec < 0) { + result.tv_sec -= 1; + result.tv_usec += kMicrosPerSecond; + } + return result; +} + +} // namespace util +} // namespace protobuf + + +namespace protobuf { +namespace { +using google::protobuf::util::kNanosPerSecond; +using google::protobuf::util::CreateNormalized; + +// Convert a Timestamp to uint128. +void ToUint128(const Timestamp& value, uint128* result, bool* negative) { + if (value.seconds() < 0) { + *negative = true; + *result = static_cast<uint64>(-value.seconds()); + *result = *result * kNanosPerSecond - static_cast<uint32>(value.nanos()); + } else { + *negative = false; + *result = static_cast<uint64>(value.seconds()); + *result = *result * kNanosPerSecond + static_cast<uint32>(value.nanos()); + } +} + +// Convert a Duration to uint128. +void ToUint128(const Duration& value, uint128* result, bool* negative) { + if (value.seconds() < 0 || value.nanos() < 0) { + *negative = true; + *result = static_cast<uint64>(-value.seconds()); + *result = *result * kNanosPerSecond + static_cast<uint32>(-value.nanos()); + } else { + *negative = false; + *result = static_cast<uint64>(value.seconds()); + *result = *result * kNanosPerSecond + static_cast<uint32>(value.nanos()); + } +} + +void ToTimestamp(const uint128& value, bool negative, Timestamp* timestamp) { + int64 seconds = static_cast<int64>(Uint128Low64(value / kNanosPerSecond)); + int32 nanos = static_cast<int32>(Uint128Low64(value % kNanosPerSecond)); + if (negative) { + seconds = -seconds; + nanos = -nanos; + if (nanos < 0) { + nanos += kNanosPerSecond; + seconds -= 1; + } + } + timestamp->set_seconds(seconds); + timestamp->set_nanos(nanos); +} + +void ToDuration(const uint128& value, bool negative, Duration* duration) { + int64 seconds = static_cast<int64>(Uint128Low64(value / kNanosPerSecond)); + int32 nanos = static_cast<int32>(Uint128Low64(value % kNanosPerSecond)); + if (negative) { + seconds = -seconds; + nanos = -nanos; + } + duration->set_seconds(seconds); + duration->set_nanos(nanos); +} +} // namespace + +Duration& operator+=(Duration& d1, const Duration& d2) { + d1 = CreateNormalized<Duration>(d1.seconds() + d2.seconds(), + d1.nanos() + d2.nanos()); + return d1; +} + +Duration& operator-=(Duration& d1, const Duration& d2) { // NOLINT + d1 = CreateNormalized<Duration>(d1.seconds() - d2.seconds(), + d1.nanos() - d2.nanos()); + return d1; +} + +Duration& operator*=(Duration& d, int64 r) { // NOLINT + bool negative; + uint128 value; + ToUint128(d, &value, &negative); + if (r > 0) { + value *= static_cast<uint64>(r); + } else { + negative = !negative; + value *= static_cast<uint64>(-r); + } + ToDuration(value, negative, &d); + return d; +} + +Duration& operator*=(Duration& d, double r) { // NOLINT + double result = (d.seconds() * 1.0 + 1.0 * d.nanos() / kNanosPerSecond) * r; + int64 seconds = static_cast<int64>(result); + int32 nanos = static_cast<int32>((result - seconds) * kNanosPerSecond); + // Note that we normalize here not just because nanos can have a different + // sign from seconds but also that nanos can be any arbitrary value when + // overflow happens (i.e., the result is a much larger value than what + // int64 can represent). + d = CreateNormalized<Duration>(seconds, nanos); + return d; +} + +Duration& operator/=(Duration& d, int64 r) { // NOLINT + bool negative; + uint128 value; + ToUint128(d, &value, &negative); + if (r > 0) { + value /= static_cast<uint64>(r); + } else { + negative = !negative; + value /= static_cast<uint64>(-r); + } + ToDuration(value, negative, &d); + return d; +} + +Duration& operator/=(Duration& d, double r) { // NOLINT + return d *= 1.0 / r; +} + +Duration& operator%=(Duration& d1, const Duration& d2) { // NOLINT + bool negative1, negative2; + uint128 value1, value2; + ToUint128(d1, &value1, &negative1); + ToUint128(d2, &value2, &negative2); + uint128 result = value1 % value2; + // When negative values are involved in division, we round the division + // result towards zero. With this semantics, sign of the remainder is the + // same as the dividend. For example: + // -5 / 10 = 0, -5 % 10 = -5 + // -5 / (-10) = 0, -5 % (-10) = -5 + // 5 / (-10) = 0, 5 % (-10) = 5 + ToDuration(result, negative1, &d1); + return d1; +} + +int64 operator/(const Duration& d1, const Duration& d2) { + bool negative1, negative2; + uint128 value1, value2; + ToUint128(d1, &value1, &negative1); + ToUint128(d2, &value2, &negative2); + int64 result = Uint128Low64(value1 / value2); + if (negative1 != negative2) { + result = -result; + } + return result; +} + +Timestamp& operator+=(Timestamp& t, const Duration& d) { // NOLINT + t = CreateNormalized<Timestamp>(t.seconds() + d.seconds(), + t.nanos() + d.nanos()); + return t; +} + +Timestamp& operator-=(Timestamp& t, const Duration& d) { // NOLINT + t = CreateNormalized<Timestamp>(t.seconds() - d.seconds(), + t.nanos() - d.nanos()); + return t; +} + +Duration operator-(const Timestamp& t1, const Timestamp& t2) { + return CreateNormalized<Duration>(t1.seconds() - t2.seconds(), + t1.nanos() - t2.nanos()); +} +} // namespace protobuf + +} // namespace google diff --git a/src/google/protobuf/util/time_util.h b/src/google/protobuf/util/time_util.h new file mode 100644 index 00000000..11268157 --- /dev/null +++ b/src/google/protobuf/util/time_util.h @@ -0,0 +1,287 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_UTIL_TIME_UTIL_H__ +#define GOOGLE_PROTOBUF_UTIL_TIME_UTIL_H__ + +#include <sys/time.h> + +#include <ctime> +#include <ostream> +#include <string> + +#include <google/protobuf/duration.pb.h> +#include <google/protobuf/timestamp.pb.h> + +namespace google { +namespace protobuf { +namespace util { + +class LIBPROTOBUF_EXPORT TimeUtil { + typedef google::protobuf::Timestamp Timestamp; + typedef google::protobuf::Duration Duration; + + public: + // The min/max Timestamp/Duration values we support. + // + // For "0001-01-01T00:00:00Z". + static const int64 kTimestampMinSeconds = -62135596800LL; + // For "9999-12-31T23:59:59.999999999Z". + static const int64 kTimestampMaxSeconds = 253402300799LL; + static const int64 kDurationMinSeconds = -315576000000LL; + static const int64 kDurationMaxSeconds = 315576000000LL; + + // Converts Timestamp to/from RFC 3339 date string format. + // Generated output will always be Z-normalized and uses 3, 6 or 9 + // fractional digits as required to represent the exact time. When + // parsing, any fractional digits (or none) and any offset are + // accepted as long as they fit into nano-seconds precision. + // Note that Timestamp can only represent time from + // 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. Converting + // a Timestamp outside of this range is undefined behavior. + // See https://www.ietf.org/rfc/rfc3339.txt + // + // Example of generated format: + // "1972-01-01T10:00:20.021Z" + // + // Example of accepted format: + // "1972-01-01T10:00:20.021-05:00" + static string ToString(const Timestamp& timestamp); + static bool FromString(const string& value, Timestamp* timestamp); + + // Converts Duration to/from string format. The string format will contains + // 3, 6, or 9 fractional digits depending on the precision required to + // represent the exact Duration value. For example: + // "1s", "1.010s", "1.000000100s", "-3.100s" + // The range that can be represented by Duration is from -315,576,000,000 + // to +315,576,000,000 inclusive (in seconds). + static string ToString(const Duration& duration); + static bool FromString(const string& value, Duration* timestamp); + + // Gets the current UTC time. + static Timestamp GetCurrentTime(); + // Returns the Time representing "1970-01-01 00:00:00". + static Timestamp GetEpoch(); + + // Converts between Duration and integer types. The behavior is undefined if + // the input value is not in the valid range of Duration. + static Duration NanosecondsToDuration(int64 nanos); + static Duration MicrosecondsToDuration(int64 micros); + static Duration MillisecondsToDuration(int64 millis); + static Duration SecondsToDuration(int64 seconds); + static Duration MinutesToDuration(int64 minutes); + static Duration HoursToDuration(int64 hours); + // Result will be truncated towards zero. For example, "-1.5s" will be + // truncated to "-1s", and "1.5s" to "1s" when converting to seconds. + // It's undefined behavior if the input duration is not valid or the result + // exceeds the range of int64. A duration is not valid if it's not in the + // valid range of Duration, or have an invalid nanos value (i.e., larger + // than 999999999, less than -999999999, or have a different sign from the + // seconds part). + static int64 DurationToNanoseconds(const Duration& duration); + static int64 DurationToMicroseconds(const Duration& duration); + static int64 DurationToMilliseconds(const Duration& duration); + static int64 DurationToSeconds(const Duration& duration); + static int64 DurationToMinutes(const Duration& duration); + static int64 DurationToHours(const Duration& duration); + // Creates Timestamp from integer types. The integer value indicates the + // time elapsed from Epoch time. The behavior is undefined if the input + // value is not in the valid range of Timestamp. + static Timestamp NanosecondsToTimestamp(int64 nanos); + static Timestamp MicrosecondsToTimestamp(int64 micros); + static Timestamp MillisecondsToTimestamp(int64 millis); + static Timestamp SecondsToTimestamp(int64 seconds); + // Result will be truncated down to the nearest integer value. For example, + // with "1969-12-31T23:59:59.9Z", TimestampToMilliseconds() returns -100 + // and TimestampToSeconds() returns -1. It's undefined behavior if the input + // Timestamp is not valid (i.e., its seconds part or nanos part does not fall + // in the valid range) or the return value doesn't fit into int64. + static int64 TimestampToNanoseconds(const Timestamp& timestamp); + static int64 TimestampToMicroseconds(const Timestamp& timestamp); + static int64 TimestampToMilliseconds(const Timestamp& timestamp); + static int64 TimestampToSeconds(const Timestamp& timestamp); + + // Conversion to/from other time/date types. Note that these types may + // have a different precision and time range from Timestamp/Duration. + // When converting to a lower precision type, the value will be truncated + // to the nearest value that can be represented. If the value is + // out of the range of the result type, the return value is undefined. + // + // Conversion to/from time_t + static Timestamp TimeTToTimestamp(time_t value); + static time_t TimestampToTimeT(const Timestamp& value); + + // Conversion to/from timeval + static Timestamp TimevalToTimestamp(const timeval& value); + static timeval TimestampToTimeval(const Timestamp& value); + static Duration TimevalToDuration(const timeval& value); + static timeval DurationToTimeval(const Duration& value); +}; + +} // namespace util +} // namespace protobuf + + +namespace protobuf { +// Overloaded operators for Duration. +// +// Assignment operators. +Duration& operator+=(Duration& d1, const Duration& d2); // NOLINT +Duration& operator-=(Duration& d1, const Duration& d2); // NOLINT +Duration& operator*=(Duration& d, int64 r); // NOLINT +Duration& operator*=(Duration& d, double r); // NOLINT +Duration& operator/=(Duration& d, int64 r); // NOLINT +Duration& operator/=(Duration& d, double r); // NOLINT +// Overload for other integer types. +template <typename T> +Duration& operator*=(Duration& d, T r) { // NOLINT + int64 x = r; + return d *= x; +} +template <typename T> +Duration& operator/=(Duration& d, T r) { // NOLINT + int64 x = r; + return d /= x; +} +Duration& operator%=(Duration& d1, const Duration& d2); // NOLINT +// Relational operators. +inline bool operator<(const Duration& d1, const Duration& d2) { + if (d1.seconds() == d2.seconds()) { + return d1.nanos() < d2.nanos(); + } + return d1.seconds() < d2.seconds(); +} +inline bool operator>(const Duration& d1, const Duration& d2) { + return d2 < d1; +} +inline bool operator>=(const Duration& d1, const Duration& d2) { + return !(d1 < d2); +} +inline bool operator<=(const Duration& d1, const Duration& d2) { + return !(d2 < d1); +} +inline bool operator==(const Duration& d1, const Duration& d2) { + return d1.seconds() == d2.seconds() && d1.nanos() == d2.nanos(); +} +inline bool operator!=(const Duration& d1, const Duration& d2) { + return !(d1 == d2); +} +// Additive operators +inline Duration operator-(const Duration& d) { + Duration result; + result.set_seconds(-d.seconds()); + result.set_nanos(-d.nanos()); + return result; +} +inline Duration operator+(const Duration& d1, const Duration& d2) { + Duration result = d1; + return result += d2; +} +inline Duration operator-(const Duration& d1, const Duration& d2) { + Duration result = d1; + return result -= d2; +} +// Multiplicative operators +template<typename T> +inline Duration operator*(Duration d, T r) { + return d *= r; +} +template<typename T> +inline Duration operator*(T r, Duration d) { + return d *= r; +} +template<typename T> +inline Duration operator/(Duration d, T r) { + return d /= r; +} +int64 operator/(const Duration& d1, const Duration& d2); + +inline Duration operator%(const Duration& d1, const Duration& d2) { + Duration result = d1; + return result %= d2; +} + +inline ostream& operator<<(ostream& out, const Duration& d) { + out << google::protobuf::util::TimeUtil::ToString(d); + return out; +} + +// Overloaded operators for Timestamp +// +// Assignement operators. +Timestamp& operator+=(Timestamp& t, const Duration& d); // NOLINT +Timestamp& operator-=(Timestamp& t, const Duration& d); // NOLINT +// Relational operators. +inline bool operator<(const Timestamp& t1, const Timestamp& t2) { + if (t1.seconds() == t2.seconds()) { + return t1.nanos() < t2.nanos(); + } + return t1.seconds() < t2.seconds(); +} +inline bool operator>(const Timestamp& t1, const Timestamp& t2) { + return t2 < t1; +} +inline bool operator>=(const Timestamp& t1, const Timestamp& t2) { + return !(t1 < t2); +} +inline bool operator<=(const Timestamp& t1, const Timestamp& t2) { + return !(t2 < t1); +} +inline bool operator==(const Timestamp& t1, const Timestamp& t2) { + return t1.seconds() == t2.seconds() && t1.nanos() == t2.nanos(); +} +inline bool operator!=(const Timestamp& t1, const Timestamp& t2) { + return !(t1 == t2); +} +// Additive operators. +inline Timestamp operator+(const Timestamp& t, const Duration& d) { + Timestamp result = t; + return result += d; +} +inline Timestamp operator+(const Duration& d, const Timestamp& t) { + Timestamp result = t; + return result += d; +} +inline Timestamp operator-(const Timestamp& t, const Duration& d) { + Timestamp result = t; + return result -= d; +} +Duration operator-(const Timestamp& t1, const Timestamp& t2); + +inline ostream& operator<<(ostream& out, const Timestamp& t) { + out << google::protobuf::util::TimeUtil::ToString(t); + return out; +} + +} // namespace protobuf + + +} // namespace google +#endif // GOOGLE_PROTOBUF_UTIL_TIME_UTIL_H__ diff --git a/src/google/protobuf/util/time_util_test.cc b/src/google/protobuf/util/time_util_test.cc new file mode 100644 index 00000000..285740ab --- /dev/null +++ b/src/google/protobuf/util/time_util_test.cc @@ -0,0 +1,380 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <google/protobuf/util/time_util.h> + +#include <ctime> + +#include <google/protobuf/timestamp.pb.h> +#include <google/protobuf/duration.pb.h> +#include <google/protobuf/testing/googletest.h> +#include <gtest/gtest.h> + +namespace google { +namespace protobuf { +namespace util { + +using google::protobuf::Timestamp; +using google::protobuf::Duration; + +namespace { + +TEST(TimeUtilTest, TimestampStringFormat) { + Timestamp begin, end; + EXPECT_TRUE(TimeUtil::FromString("0001-01-01T00:00:00Z", &begin)); + EXPECT_EQ(TimeUtil::kTimestampMinSeconds, begin.seconds()); + EXPECT_EQ(0, begin.nanos()); + EXPECT_TRUE(TimeUtil::FromString("9999-12-31T23:59:59.999999999Z", &end)); + EXPECT_EQ(TimeUtil::kTimestampMaxSeconds, end.seconds()); + EXPECT_EQ(999999999, end.nanos()); + EXPECT_EQ("0001-01-01T00:00:00Z", TimeUtil::ToString(begin)); + EXPECT_EQ("9999-12-31T23:59:59.999999999Z", TimeUtil::ToString(end)); + + // Test negative timestamps. + Timestamp time = TimeUtil::NanosecondsToTimestamp(-1); + EXPECT_EQ(-1, time.seconds()); + // Timestamp's nano part is always non-negative. + EXPECT_EQ(999999999, time.nanos()); + EXPECT_EQ("1969-12-31T23:59:59.999999999Z", TimeUtil::ToString(time)); + + // Generated output should contain 3, 6, or 9 fractional digits. + EXPECT_EQ("1970-01-01T00:00:00Z", + TimeUtil::ToString(TimeUtil::NanosecondsToTimestamp(0))); + EXPECT_EQ("1970-01-01T00:00:00.010Z", + TimeUtil::ToString(TimeUtil::NanosecondsToTimestamp(10000000))); + EXPECT_EQ("1970-01-01T00:00:00.000010Z", + TimeUtil::ToString(TimeUtil::NanosecondsToTimestamp(10000))); + EXPECT_EQ("1970-01-01T00:00:00.000000010Z", + TimeUtil::ToString(TimeUtil::NanosecondsToTimestamp(10))); + + // Parsing accepts an fractional digits as long as they fit into nano + // precision. + EXPECT_TRUE(TimeUtil::FromString("1970-01-01T00:00:00.1Z", &time)); + EXPECT_EQ(100000000, TimeUtil::TimestampToNanoseconds(time)); + EXPECT_TRUE(TimeUtil::FromString("1970-01-01T00:00:00.0001Z", &time)); + EXPECT_EQ(100000, TimeUtil::TimestampToNanoseconds(time)); + EXPECT_TRUE(TimeUtil::FromString("1970-01-01T00:00:00.0000001Z", &time)); + EXPECT_EQ(100, TimeUtil::TimestampToNanoseconds(time)); + + // Also accpets offsets. + EXPECT_TRUE(TimeUtil::FromString("1970-01-01T00:00:00-08:00", &time)); + EXPECT_EQ(8 * 3600, TimeUtil::TimestampToSeconds(time)); +} + +TEST(TimeUtilTest, DurationStringFormat) { + Timestamp begin, end; + EXPECT_TRUE(TimeUtil::FromString("0001-01-01T00:00:00Z", &begin)); + EXPECT_TRUE(TimeUtil::FromString("9999-12-31T23:59:59.999999999Z", &end)); + + EXPECT_EQ("315537897599.999999999s", TimeUtil::ToString(end - begin)); + EXPECT_EQ(999999999, (end - begin).nanos()); + EXPECT_EQ("-315537897599.999999999s", TimeUtil::ToString(begin - end)); + EXPECT_EQ(-999999999, (begin - end).nanos()); + + // Generated output should contain 3, 6, or 9 fractional digits. + EXPECT_EQ("1s", TimeUtil::ToString(TimeUtil::SecondsToDuration(1))); + EXPECT_EQ("0.010s", TimeUtil::ToString(TimeUtil::MillisecondsToDuration(10))); + EXPECT_EQ("0.000010s", + TimeUtil::ToString(TimeUtil::MicrosecondsToDuration(10))); + EXPECT_EQ("0.000000010s", + TimeUtil::ToString(TimeUtil::NanosecondsToDuration(10))); + + // Parsing accepts an fractional digits as long as they fit into nano + // precision. + Duration d; + EXPECT_TRUE(TimeUtil::FromString("0.1s", &d)); + EXPECT_EQ(100, TimeUtil::DurationToMilliseconds(d)); + EXPECT_TRUE(TimeUtil::FromString("0.0001s", &d)); + EXPECT_EQ(100, TimeUtil::DurationToMicroseconds(d)); + EXPECT_TRUE(TimeUtil::FromString("0.0000001s", &d)); + EXPECT_EQ(100, TimeUtil::DurationToNanoseconds(d)); + + // Duration must support range from -315,576,000,000s to +315576000000s + // which includes negative values. + EXPECT_TRUE(TimeUtil::FromString("315576000000.999999999s", &d)); + EXPECT_EQ(315576000000LL, d.seconds()); + EXPECT_EQ(999999999, d.nanos()); + EXPECT_TRUE(TimeUtil::FromString("-315576000000.999999999s", &d)); + EXPECT_EQ(-315576000000LL, d.seconds()); + EXPECT_EQ(-999999999, d.nanos()); +} + +TEST(TimeUtilTest, GetEpoch) { + EXPECT_EQ(0, TimeUtil::TimestampToNanoseconds(TimeUtil::GetEpoch())); +} + +TEST(TimeUtilTest, DurationIntegerConversion) { + EXPECT_EQ("0.000000001s", + TimeUtil::ToString(TimeUtil::NanosecondsToDuration(1))); + EXPECT_EQ("-0.000000001s", + TimeUtil::ToString(TimeUtil::NanosecondsToDuration(-1))); + EXPECT_EQ("0.000001s", + TimeUtil::ToString(TimeUtil::MicrosecondsToDuration(1))); + EXPECT_EQ("-0.000001s", + TimeUtil::ToString(TimeUtil::MicrosecondsToDuration(-1))); + EXPECT_EQ("0.001s", TimeUtil::ToString(TimeUtil::MillisecondsToDuration(1))); + EXPECT_EQ("-0.001s", + TimeUtil::ToString(TimeUtil::MillisecondsToDuration(-1))); + EXPECT_EQ("1s", TimeUtil::ToString(TimeUtil::SecondsToDuration(1))); + EXPECT_EQ("-1s", TimeUtil::ToString(TimeUtil::SecondsToDuration(-1))); + EXPECT_EQ("60s", TimeUtil::ToString(TimeUtil::MinutesToDuration(1))); + EXPECT_EQ("-60s", TimeUtil::ToString(TimeUtil::MinutesToDuration(-1))); + EXPECT_EQ("3600s", TimeUtil::ToString(TimeUtil::HoursToDuration(1))); + EXPECT_EQ("-3600s", TimeUtil::ToString(TimeUtil::HoursToDuration(-1))); + + EXPECT_EQ( + 1, TimeUtil::DurationToNanoseconds(TimeUtil::NanosecondsToDuration(1))); + EXPECT_EQ( + -1, TimeUtil::DurationToNanoseconds(TimeUtil::NanosecondsToDuration(-1))); + EXPECT_EQ( + 1, TimeUtil::DurationToMicroseconds(TimeUtil::MicrosecondsToDuration(1))); + EXPECT_EQ(-1, TimeUtil::DurationToMicroseconds( + TimeUtil::MicrosecondsToDuration(-1))); + EXPECT_EQ( + 1, TimeUtil::DurationToMilliseconds(TimeUtil::MillisecondsToDuration(1))); + EXPECT_EQ(-1, TimeUtil::DurationToMilliseconds( + TimeUtil::MillisecondsToDuration(-1))); + EXPECT_EQ(1, TimeUtil::DurationToSeconds(TimeUtil::SecondsToDuration(1))); + EXPECT_EQ(-1, TimeUtil::DurationToSeconds(TimeUtil::SecondsToDuration(-1))); + EXPECT_EQ(1, TimeUtil::DurationToMinutes(TimeUtil::MinutesToDuration(1))); + EXPECT_EQ(-1, TimeUtil::DurationToMinutes(TimeUtil::MinutesToDuration(-1))); + EXPECT_EQ(1, TimeUtil::DurationToHours(TimeUtil::HoursToDuration(1))); + EXPECT_EQ(-1, TimeUtil::DurationToHours(TimeUtil::HoursToDuration(-1))); + + // Test truncation behavior. + EXPECT_EQ(1, TimeUtil::DurationToMicroseconds( + TimeUtil::NanosecondsToDuration(1999))); + // For negative values, Duration will be rounded towards 0. + EXPECT_EQ(-1, TimeUtil::DurationToMicroseconds( + TimeUtil::NanosecondsToDuration(-1999))); +} + +TEST(TestUtilTest, TimestampIntegerConversion) { + EXPECT_EQ("1970-01-01T00:00:00.000000001Z", + TimeUtil::ToString(TimeUtil::NanosecondsToTimestamp(1))); + EXPECT_EQ("1969-12-31T23:59:59.999999999Z", + TimeUtil::ToString(TimeUtil::NanosecondsToTimestamp(-1))); + EXPECT_EQ("1970-01-01T00:00:00.000001Z", + TimeUtil::ToString(TimeUtil::MicrosecondsToTimestamp(1))); + EXPECT_EQ("1969-12-31T23:59:59.999999Z", + TimeUtil::ToString(TimeUtil::MicrosecondsToTimestamp(-1))); + EXPECT_EQ("1970-01-01T00:00:00.001Z", + TimeUtil::ToString(TimeUtil::MillisecondsToTimestamp(1))); + EXPECT_EQ("1969-12-31T23:59:59.999Z", + TimeUtil::ToString(TimeUtil::MillisecondsToTimestamp(-1))); + EXPECT_EQ("1970-01-01T00:00:01Z", + TimeUtil::ToString(TimeUtil::SecondsToTimestamp(1))); + EXPECT_EQ("1969-12-31T23:59:59Z", + TimeUtil::ToString(TimeUtil::SecondsToTimestamp(-1))); + + EXPECT_EQ( + 1, TimeUtil::TimestampToNanoseconds(TimeUtil::NanosecondsToTimestamp(1))); + EXPECT_EQ(-1, TimeUtil::TimestampToNanoseconds( + TimeUtil::NanosecondsToTimestamp(-1))); + EXPECT_EQ(1, TimeUtil::TimestampToMicroseconds( + TimeUtil::MicrosecondsToTimestamp(1))); + EXPECT_EQ(-1, TimeUtil::TimestampToMicroseconds( + TimeUtil::MicrosecondsToTimestamp(-1))); + EXPECT_EQ(1, TimeUtil::TimestampToMilliseconds( + TimeUtil::MillisecondsToTimestamp(1))); + EXPECT_EQ(-1, TimeUtil::TimestampToMilliseconds( + TimeUtil::MillisecondsToTimestamp(-1))); + EXPECT_EQ(1, TimeUtil::TimestampToSeconds(TimeUtil::SecondsToTimestamp(1))); + EXPECT_EQ(-1, TimeUtil::TimestampToSeconds(TimeUtil::SecondsToTimestamp(-1))); + + // Test truncation behavior. + EXPECT_EQ(1, TimeUtil::TimestampToMicroseconds( + TimeUtil::NanosecondsToTimestamp(1999))); + // For negative values, Timestamp will be rounded down. + // For example, "1969-12-31T23:59:59.5Z" (i.e., -0.5s) rounded to seconds + // will be "1969-12-31T23:59:59Z" (i.e., -1s) rather than + // "1970-01-01T00:00:00Z" (i.e., 0s). + EXPECT_EQ(-2, TimeUtil::TimestampToMicroseconds( + TimeUtil::NanosecondsToTimestamp(-1999))); +} + +TEST(TimeUtilTest, TimeTConversion) { + time_t value = time(NULL); + EXPECT_EQ(value, + TimeUtil::TimestampToTimeT(TimeUtil::TimeTToTimestamp(value))); + EXPECT_EQ( + 1, TimeUtil::TimestampToTimeT(TimeUtil::MillisecondsToTimestamp(1999))); +} + +TEST(TimeUtilTest, TimevalConversion) { + timeval value = TimeUtil::TimestampToTimeval( + TimeUtil::NanosecondsToTimestamp(1999999999)); + EXPECT_EQ(1, value.tv_sec); + EXPECT_EQ(999999, value.tv_usec); + value = TimeUtil::TimestampToTimeval( + TimeUtil::NanosecondsToTimestamp(-1999999999)); + EXPECT_EQ(-2, value.tv_sec); + EXPECT_EQ(0, value.tv_usec); + + value = + TimeUtil::DurationToTimeval(TimeUtil::NanosecondsToDuration(1999999999)); + EXPECT_EQ(1, value.tv_sec); + EXPECT_EQ(999999, value.tv_usec); + value = + TimeUtil::DurationToTimeval(TimeUtil::NanosecondsToDuration(-1999999999)); + EXPECT_EQ(-2, value.tv_sec); + EXPECT_EQ(1, value.tv_usec); +} + +TEST(TimeUtilTest, DurationOperators) { + Duration one_second = TimeUtil::SecondsToDuration(1); + Duration one_nano = TimeUtil::NanosecondsToDuration(1); + + // Test +/- + Duration a = one_second; + a += one_second; + a -= one_nano; + EXPECT_EQ("1.999999999s", TimeUtil::ToString(a)); + Duration b = -a; + EXPECT_EQ("-1.999999999s", TimeUtil::ToString(b)); + EXPECT_EQ("3.999999998s", TimeUtil::ToString(a + a)); + EXPECT_EQ("0s", TimeUtil::ToString(a + b)); + EXPECT_EQ("0s", TimeUtil::ToString(b + a)); + EXPECT_EQ("-3.999999998s", TimeUtil::ToString(b + b)); + EXPECT_EQ("3.999999998s", TimeUtil::ToString(a - b)); + EXPECT_EQ("0s", TimeUtil::ToString(a - a)); + EXPECT_EQ("0s", TimeUtil::ToString(b - b)); + EXPECT_EQ("-3.999999998s", TimeUtil::ToString(b - a)); + + // Test * + EXPECT_EQ(a + a, a * 2); + EXPECT_EQ(b + b, a * (-2)); + EXPECT_EQ(b + b, b * 2); + EXPECT_EQ(a + a, b * (-2)); + EXPECT_EQ("0.999999999s", TimeUtil::ToString(a * 0.5)); + EXPECT_EQ("-0.999999999s", TimeUtil::ToString(b * 0.5)); + // Multiplication should not overflow if the result fits into the supported + // range of Duration (intermediate result may be larger than int64). + EXPECT_EQ("315575999684.424s", + TimeUtil::ToString((one_second - one_nano) * 315576000000LL)); + EXPECT_EQ("-315575999684.424s", + TimeUtil::ToString((one_nano - one_second) * 315576000000LL)); + EXPECT_EQ("-315575999684.424s", + TimeUtil::ToString((one_second - one_nano) * (-315576000000LL))); + + // Test / and % + EXPECT_EQ("0.999999999s", TimeUtil::ToString(a / 2)); + EXPECT_EQ("-0.999999999s", TimeUtil::ToString(b / 2)); + Duration large = TimeUtil::SecondsToDuration(315576000000LL) - one_nano; + // We have to handle division with values beyond 64 bits. + EXPECT_EQ("0.999999999s", TimeUtil::ToString(large / 315576000000LL)); + EXPECT_EQ("-0.999999999s", TimeUtil::ToString((-large) / 315576000000LL)); + EXPECT_EQ("-0.999999999s", TimeUtil::ToString(large / (-315576000000LL))); + Duration large2 = large + one_nano; + EXPECT_EQ(large, large % large2); + EXPECT_EQ(-large, (-large) % large2); + EXPECT_EQ(large, large % (-large2)); + EXPECT_EQ(one_nano, large2 % large); + EXPECT_EQ(-one_nano, (-large2) % large); + EXPECT_EQ(one_nano, large2 % (-large)); + // Some corner cases about negative values. + // + // (-5) / 2 = -2, remainder = -1 + // (-5) / (-2) = 2, remainder = -1 + a = TimeUtil::NanosecondsToDuration(-5); + EXPECT_EQ(TimeUtil::NanosecondsToDuration(-2), a / 2); + EXPECT_EQ(TimeUtil::NanosecondsToDuration(2), a / (-2)); + b = TimeUtil::NanosecondsToDuration(2); + EXPECT_EQ(-2, a / b); + EXPECT_EQ(TimeUtil::NanosecondsToDuration(-1), a % b); + EXPECT_EQ(2, a / (-b)); + EXPECT_EQ(TimeUtil::NanosecondsToDuration(-1), a % (-b)); + + // Test relational operators. + EXPECT_TRUE(one_nano < one_second); + EXPECT_FALSE(one_second < one_second); + EXPECT_FALSE(one_second < one_nano); + EXPECT_FALSE(-one_nano < -one_second); + EXPECT_FALSE(-one_second < -one_second); + EXPECT_TRUE(-one_second < -one_nano); + EXPECT_TRUE(-one_nano < one_nano); + EXPECT_FALSE(one_nano < -one_nano); + + EXPECT_FALSE(one_nano > one_second); + EXPECT_FALSE(one_nano > one_nano); + EXPECT_TRUE(one_second > one_nano); + + EXPECT_FALSE(one_nano >= one_second); + EXPECT_TRUE(one_nano >= one_nano); + EXPECT_TRUE(one_second >= one_nano); + + EXPECT_TRUE(one_nano <= one_second); + EXPECT_TRUE(one_nano <= one_nano); + EXPECT_FALSE(one_second <= one_nano); + + EXPECT_TRUE(one_nano == one_nano); + EXPECT_FALSE(one_nano == one_second); + + EXPECT_FALSE(one_nano != one_nano); + EXPECT_TRUE(one_nano != one_second); +} + +TEST(TimeUtilTest, TimestampOperators) { + Timestamp begin, end; + EXPECT_TRUE(TimeUtil::FromString("0001-01-01T00:00:00Z", &begin)); + EXPECT_TRUE(TimeUtil::FromString("9999-12-31T23:59:59.999999999Z", &end)); + Duration d = end - begin; + EXPECT_TRUE(end == begin + d); + EXPECT_TRUE(end == d + begin); + EXPECT_TRUE(begin == end - d); + + // Test relational operators + Timestamp t1 = begin + d / 4; + Timestamp t2 = end - d / 4; + EXPECT_TRUE(t1 < t2); + EXPECT_FALSE(t1 < t1); + EXPECT_FALSE(t2 < t1); + EXPECT_FALSE(t1 > t2); + EXPECT_FALSE(t1 > t1); + EXPECT_TRUE(t2 > t1); + EXPECT_FALSE(t1 >= t2); + EXPECT_TRUE(t1 >= t1); + EXPECT_TRUE(t2 >= t1); + EXPECT_TRUE(t1 <= t2); + EXPECT_TRUE(t1 <= t1); + EXPECT_FALSE(t2 <= t1); + + EXPECT_FALSE(t1 == t2); + EXPECT_TRUE(t1 == t1); + EXPECT_FALSE(t2 == t1); + EXPECT_TRUE(t1 != t2); + EXPECT_FALSE(t1 != t1); + EXPECT_TRUE(t2 != t1); +} + +} // namespace +} // namespace util +} // namespace protobuf +} // namespace google |