From 9629797331e6859ce68c5489fc690d3526a3915a Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Thu, 30 Jul 2015 09:29:52 +0100 Subject: Well-known type operations for Timestamp and Duration (but not JSON formatting). While I've provided operators, I haven't yet provided the method equivalents. It's not clear to me that they're actually a good idea, while we're really targeting C# developers who definitely *can* use the user-defined operators. --- csharp/src/Google.Protobuf/Google.Protobuf.csproj | 3 + .../WellKnownTypes/DurationPartial.cs | 145 ++++++++++++++++++ .../WellKnownTypes/TimeExtensions.cs | 61 ++++++++ .../WellKnownTypes/TimestampPartial.cs | 164 +++++++++++++++++++++ 4 files changed, 373 insertions(+) create mode 100644 csharp/src/Google.Protobuf/WellKnownTypes/DurationPartial.cs create mode 100644 csharp/src/Google.Protobuf/WellKnownTypes/TimeExtensions.cs create mode 100644 csharp/src/Google.Protobuf/WellKnownTypes/TimestampPartial.cs (limited to 'csharp/src/Google.Protobuf') diff --git a/csharp/src/Google.Protobuf/Google.Protobuf.csproj b/csharp/src/Google.Protobuf/Google.Protobuf.csproj index 7de4f51b..c7038eb4 100644 --- a/csharp/src/Google.Protobuf/Google.Protobuf.csproj +++ b/csharp/src/Google.Protobuf/Google.Protobuf.csproj @@ -100,11 +100,14 @@ + + + diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/DurationPartial.cs b/csharp/src/Google.Protobuf/WellKnownTypes/DurationPartial.cs new file mode 100644 index 00000000..c69d1b23 --- /dev/null +++ b/csharp/src/Google.Protobuf/WellKnownTypes/DurationPartial.cs @@ -0,0 +1,145 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 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. +#endregion + +using System; + +namespace Google.Protobuf.WellKnownTypes +{ + // Manually-written partial class for the Duration well-known type, + // providing a conversion to TimeSpan and convenience operators. + public partial class Duration + { + public const int NanosecondsPerSecond = 1000000000; + public const int NanosecondsPerTick = 100; + + /// + /// Converts this to a . + /// + /// If the duration is not a precise number of ticks, it is truncated towards 0. + /// The value of this duration, as a TimeSpan. + public TimeSpan ToTimeSpan() + { + checked + { + long ticks = Seconds * TimeSpan.TicksPerSecond + Nanos / NanosecondsPerTick; + return TimeSpan.FromTicks(ticks); + } + } + + /// + /// Converts the given to a . + /// + /// The TimeSpan to convert. + /// The value of the given TimeSpan, as a Duration. + public static Duration FromTimeSpan(TimeSpan timeSpan) + { + checked + { + long ticks = timeSpan.Ticks; + long seconds = ticks / TimeSpan.TicksPerSecond; + int nanos = (int) (ticks % TimeSpan.TicksPerSecond) * NanosecondsPerTick; + return new Duration { Seconds = seconds, Nanos = nanos }; + } + } + + /// + /// Returns the result of negating the duration. For example, the negation of 5 minutes is -5 minutes. + /// + /// The duration to negate. Must not be null. + /// The negated value of this duration. + public static Duration operator -(Duration value) + { + Preconditions.CheckNotNull(value, "value"); + checked + { + return Normalize(-value.Seconds, -value.Nanos); + } + } + + /// + /// Adds the two specified values together. + /// + /// The first value to add. Must not be null. + /// The second value to add. Must not be null. + /// + public static Duration operator +(Duration lhs, Duration rhs) + { + Preconditions.CheckNotNull(lhs, "lhs"); + Preconditions.CheckNotNull(rhs, "rhs"); + checked + { + return Normalize(lhs.Seconds + rhs.Seconds, lhs.Nanos + rhs.Nanos); + } + } + + /// + /// Subtracts one from another. + /// + /// The duration to subtract from. Must not be null. + /// The duration to subtract. Must not be null. + /// The difference between the two specified durations. + public static Duration operator -(Duration lhs, Duration rhs) + { + Preconditions.CheckNotNull(lhs, "lhs"); + Preconditions.CheckNotNull(rhs, "rhs"); + checked + { + return Normalize(lhs.Seconds - rhs.Seconds, lhs.Nanos - rhs.Nanos); + } + } + + /// + /// Creates a duration with the normalized values from the given number of seconds and + /// nanoseconds, conforming with the description in the proto file. + /// + internal static Duration Normalize(long seconds, int nanoseconds) + { + // Ensure that nanoseconds is in the range (-1,000,000,000, +1,000,000,000) + int extraSeconds = nanoseconds / NanosecondsPerSecond; + seconds += extraSeconds; + nanoseconds -= extraSeconds * NanosecondsPerSecond; + + // Now make sure that Sign(seconds) == Sign(nanoseconds) if Sign(seconds) != 0. + if (seconds < 0 && nanoseconds > 0) + { + seconds += 1; + nanoseconds -= NanosecondsPerSecond; + } + else if (seconds > 0 && nanoseconds < 0) + { + seconds -= 1; + nanoseconds += NanosecondsPerSecond; + } + return new Duration { Seconds = seconds, Nanos = nanoseconds }; + } + } +} diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/TimeExtensions.cs b/csharp/src/Google.Protobuf/WellKnownTypes/TimeExtensions.cs new file mode 100644 index 00000000..a3806b5c --- /dev/null +++ b/csharp/src/Google.Protobuf/WellKnownTypes/TimeExtensions.cs @@ -0,0 +1,61 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 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. +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Google.Protobuf.WellKnownTypes +{ + /// + /// Extension methods on BCL time-related types, converting to protobuf types. + /// + public static class TimeExtensions + { + public static Timestamp ToTimestamp(this DateTime dateTime) + { + return Timestamp.FromDateTime(dateTime); + } + + public static Timestamp ToTimestamp(this DateTimeOffset dateTimeOffset) + { + return Timestamp.FromDateTimeOffset(dateTimeOffset); + } + + public static Duration ToDuration(this TimeSpan timeSpan) + { + return Duration.FromTimeSpan(timeSpan); + } + } +} diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/TimestampPartial.cs b/csharp/src/Google.Protobuf/WellKnownTypes/TimestampPartial.cs new file mode 100644 index 00000000..f682d093 --- /dev/null +++ b/csharp/src/Google.Protobuf/WellKnownTypes/TimestampPartial.cs @@ -0,0 +1,164 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 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. +#endregion + +using System; + +namespace Google.Protobuf.WellKnownTypes +{ + public partial class Timestamp + { + private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private static readonly long BclSecondsAtUnixEpoch = UnixEpoch.Ticks / TimeSpan.TicksPerSecond; + + /// + /// Returns the difference between one and another, as a . + /// + /// The timestamp to subtract from. Must not be null. + /// The timestamp to subtract. Must not be null. + /// The difference between the two specified timestamps. + public static Duration operator -(Timestamp lhs, Timestamp rhs) + { + Preconditions.CheckNotNull(lhs, "lhs"); + Preconditions.CheckNotNull(rhs, "rhs"); + checked + { + return Duration.Normalize(lhs.Seconds - rhs.Seconds, lhs.Nanos - rhs.Nanos); + } + } + + /// + /// Adds a to a , to obtain another Timestamp. + /// + /// The timestamp to add the duration to. Must not be null. + /// The duration to add. Must not be null. + /// The result of adding the duration to the timestamp. + public static Timestamp operator +(Timestamp lhs, Duration rhs) + { + Preconditions.CheckNotNull(lhs, "lhs"); + Preconditions.CheckNotNull(rhs, "rhs"); + checked + { + return Normalize(lhs.Seconds + rhs.Seconds, lhs.Nanos + rhs.Nanos); + } + } + + /// + /// Subtracts a from a , to obtain another Timestamp. + /// + /// The timestamp to subtract the duration from. Must not be null. + /// The duration to subtract. + /// The result of subtracting the duration from the timestamp. + public static Timestamp operator -(Timestamp lhs, Duration rhs) + { + Preconditions.CheckNotNull(lhs, "lhs"); + Preconditions.CheckNotNull(rhs, "rhs"); + checked + { + return Normalize(lhs.Seconds - rhs.Seconds, lhs.Nanos - rhs.Nanos); + } + } + + /// + /// Converts this timestamp into a . + /// + /// + /// The resulting DateTime will always have a Kind of Utc. + /// If the timestamp is not a precise number of ticks, it will be truncated towards the start + /// of time. For example, a timestamp with a value of 99 will result in a + /// value precisely on a second. + /// + /// This timestamp as a DateTime. + public DateTime ToDateTime() + { + return UnixEpoch.AddSeconds(Seconds).AddTicks(Nanos / Duration.NanosecondsPerTick); + } + + /// + /// Converts this timestamp into a . + /// + /// + /// The resulting DateTimeOffset will always have an Offset of zero. + /// If the timestamp is not a precise number of ticks, it will be truncated towards the start + /// of time. For example, a timestamp with a value of 99 will result in a + /// value precisely on a second. + /// + /// This timestamp as a DateTimeOffset. + public DateTimeOffset ToDateTimeOffset() + { + return new DateTimeOffset(ToDateTime(), TimeSpan.Zero); + } + + /// + /// Converts the specified to a . + /// + /// + /// The Kind of is not DateTimeKind.Utc. + /// The converted timestamp. + public static Timestamp FromDateTime(DateTime dateTime) + { + if (dateTime.Kind != DateTimeKind.Utc) + { + throw new ArgumentException("Conversion from DateTime to Timestamp requires the DateTime kind to be Utc", "dateTime"); + } + // Do the arithmetic using DateTime.Ticks, which is always non-negative, making things simpler. + long secondsSinceBclEpoch = dateTime.Ticks / TimeSpan.TicksPerSecond; + int nanoseconds = (int) (dateTime.Ticks % TimeSpan.TicksPerSecond) * Duration.NanosecondsPerTick; + return new Timestamp { Seconds = secondsSinceBclEpoch - BclSecondsAtUnixEpoch, Nanos = nanoseconds }; + } + + /// + /// Converts the specified to a . + /// + /// + /// The converted timestamp. + public static Timestamp FromDateTimeOffset(DateTimeOffset dateTimeOffset) + { + // We don't need to worry about this having negative ticks: DateTimeOffset is constrained to handle + // values whose *UTC* value is in the range of DateTime. + return FromDateTime(dateTimeOffset.UtcDateTime); + } + + private static Timestamp Normalize(long seconds, int nanoseconds) + { + int extraSeconds = nanoseconds / Duration.NanosecondsPerSecond; + seconds += extraSeconds; + nanoseconds -= extraSeconds * Duration.NanosecondsPerSecond; + + if (nanoseconds < 0) + { + nanoseconds += Duration.NanosecondsPerSecond; + seconds--; + } + return new Timestamp { Seconds = seconds, Nanos = nanoseconds }; + } + } +} -- cgit v1.2.3