From 577ba03aa1e24d9a2a97ef4e42608070c40d0362 Mon Sep 17 00:00:00 2001 From: Poulad Date: Mon, 1 Jan 2018 21:18:22 -0500 Subject: [PATCH] Add unix date converter --- .../Converters/UnixDateTimeConverterTests.cs | 263 ++++++++++++++++++ .../Converters/UnixDateTimeConverter.cs | 120 ++++++++ 2 files changed, 383 insertions(+) create mode 100644 Src/Newtonsoft.Json.Tests/Converters/UnixDateTimeConverterTests.cs create mode 100644 Src/Newtonsoft.Json/Converters/UnixDateTimeConverter.cs diff --git a/Src/Newtonsoft.Json.Tests/Converters/UnixDateTimeConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/UnixDateTimeConverterTests.cs new file mode 100644 index 00000000..7d577046 --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/Converters/UnixDateTimeConverterTests.cs @@ -0,0 +1,263 @@ +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +using System; +using System.Collections.Generic; +#if DNXCORE50 +using Xunit; +using Test = Xunit.FactAttribute; +using Assert = Newtonsoft.Json.Tests.XUnitAssert; +#else +using NUnit.Framework; +#endif +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Tests.TestObjects; + +namespace Newtonsoft.Json.Tests.Converters +{ + [TestFixture] + public class UnixDateTimeConverterTests : TestFixtureBase + { + [Test] + public void SerializeDateTime() + { + DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + string result = JsonConvert.SerializeObject(unixEpoch, new UnixDateTimeConverter()); + + Assert.AreEqual("0", result); + } + + [Test] + public void SerializeDateTimeNow() + { + DateTime now = DateTime.Now; + long nowTicks = (long)(now.ToUniversalTime() - new DateTime(1970, 1, 1)).TotalSeconds; + + string result = JsonConvert.SerializeObject(now, new UnixDateTimeConverter()); + + Assert.AreEqual(nowTicks + "", result); + } + + [Test] + public void SerializeInvalidDate() + { + ExceptionAssert.Throws( + () => JsonConvert.SerializeObject(new DateTime(1964, 2, 7), new UnixDateTimeConverter()), + "Expected a valid Unix date." + ); + } + +#if !NET20 + [Test] + public void SerializeDateTimeOffset() + { + DateTimeOffset now = new DateTimeOffset(2018, 1, 1, 16, 1, 16, TimeSpan.FromHours(-5)); + + string result = JsonConvert.SerializeObject(now, new UnixDateTimeConverter()); + + Assert.AreEqual("1514840476", result); + } + + [Test] + public void SerializeNullableDateTimeClass() + { + NullableDateTimeTestClass t = new NullableDateTimeTestClass + { + DateTimeField = null, + DateTimeOffsetField = null + }; + + UnixDateTimeConverter converter = new UnixDateTimeConverter(); + + string result = JsonConvert.SerializeObject(t, converter); + + Assert.AreEqual(@"{""PreField"":null,""DateTimeField"":null,""DateTimeOffsetField"":null,""PostField"":null}", result); + + t = new NullableDateTimeTestClass + { + DateTimeField = new DateTime(2018, 1, 1, 21, 1, 16, DateTimeKind.Utc), + DateTimeOffsetField = new DateTimeOffset(1970, 2, 1, 20, 6, 18, TimeSpan.Zero) + }; + + result = JsonConvert.SerializeObject(t, converter); + Assert.AreEqual(@"{""PreField"":null,""DateTimeField"":1514840476,""DateTimeOffsetField"":2750778,""PostField"":null}", result); + } + + [Test] + public void DeserializeNullToNonNullable() + { + ExceptionAssert.Throws( + () => JsonConvert.DeserializeObject( + @"{""PreField"":""Pre"",""DateTimeField"":null,""DateTimeOffsetField"":null,""PostField"":""Post""}", + new UnixDateTimeConverter() + ), + "Cannot convert null value to System.DateTime. Path 'DateTimeField', line 1, position 38." + ); + } + + [Test] + public void DeserializeDateTimeOffset() + { + UnixDateTimeConverter converter = new UnixDateTimeConverter(); + DateTimeOffset d = new DateTimeOffset(1970, 2, 1, 20, 6, 18, TimeSpan.Zero); + + string json = JsonConvert.SerializeObject(d, converter); + + DateTimeOffset result = JsonConvert.DeserializeObject(json, converter); + + Assert.AreEqual(new DateTimeOffset(1970, 2, 1, 20, 6, 18, TimeSpan.Zero), result); + } + + [Test] + public void DeserializeStringToDateTimeOffset() + { + DateTimeOffset result = JsonConvert.DeserializeObject(@"""1514840476""", new UnixDateTimeConverter()); + + Assert.AreEqual(new DateTimeOffset(2018, 1, 1, 21, 1, 16, TimeSpan.Zero), result); + } +#endif + + [Test] + public void DeserializeIntegerToDateTime() + { + DateTime result = JsonConvert.DeserializeObject("1514840476", new UnixDateTimeConverter()); + + Assert.AreEqual(new DateTime(2018, 1, 1, 21, 1, 16, DateTimeKind.Utc), result); + } + + [Test] + public void DeserializeNullToNullable() + { + DateTime? result = JsonConvert.DeserializeObject("null", new UnixDateTimeConverter()); + + Assert.IsNull(result); + } + + [Test] + public void DeserializeInvalidValue() + { + ExceptionAssert.Throws( + () => JsonConvert.DeserializeObject("-1", new UnixDateTimeConverter()), + "Cannot convert invalid value -1 to System.DateTime. Path '', line 1, position 2." + ); + } + + [Test] + public void DeserializeInvalidValueType() + { + ExceptionAssert.Throws( + () => JsonConvert.DeserializeObject("false", new UnixDateTimeConverter()), + "Cannot convert invalid value to System.DateTime. Path '', line 1, position 5." + ); + } + + [Test] + public void ConverterList() + { + UnixConverterList l1 = new UnixConverterList + { + new DateTime(2018, 1, 1, 21, 1, 16, DateTimeKind.Utc), + new DateTime(1970, 1, 1, 0, 0, 3, DateTimeKind.Utc), + }; + + string json = JsonConvert.SerializeObject(l1, Formatting.Indented); + StringAssert.AreEqual(@"[ + 1514840476, + 3 +]", json); + + UnixConverterList l2 = JsonConvert.DeserializeObject>(json); + Assert.IsNotNull(l2); + + Assert.AreEqual(new DateTime(2018, 1, 1, 21, 1, 16, DateTimeKind.Utc), l2[0]); + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 3, DateTimeKind.Utc), l2[1]); + } + + [Test] + public void ConverterDictionary() + { + UnixConverterDictionary l1 = new UnixConverterDictionary + { + {"First", new DateTime(1970, 1, 1, 0, 0, 3, DateTimeKind.Utc)}, + {"Second", new DateTime(2018, 1, 1, 21, 1, 16, DateTimeKind.Utc)}, + }; + + string json = JsonConvert.SerializeObject(l1, Formatting.Indented); + StringAssert.AreEqual(@"{ + ""First"": 3, + ""Second"": 1514840476 +}", json); + + UnixConverterDictionary l2 = JsonConvert.DeserializeObject>(json); + Assert.IsNotNull(l2); + + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 3, DateTimeKind.Utc), l2["First"]); + Assert.AreEqual(new DateTime(2018, 1, 1, 21, 1, 16, DateTimeKind.Utc), l2["Second"]); + } + + [Test] + public void ConverterObject() + { + UnixConverterObject obj1 = new UnixConverterObject + { + Object1 = new DateTime(1970, 1, 1, 0, 0, 3, DateTimeKind.Utc), + Object2 = null, + ObjectNotHandled = new DateTime(2018, 1, 1, 21, 1, 16, DateTimeKind.Utc) + }; + + string json = JsonConvert.SerializeObject(obj1, Formatting.Indented); + StringAssert.AreEqual(@"{ + ""Object1"": 3, + ""Object2"": null, + ""ObjectNotHandled"": 1514840476 +}", json); + + UnixConverterObject obj2 = JsonConvert.DeserializeObject(json); + Assert.IsNotNull(obj2); + + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 3, DateTimeKind.Utc), obj2.Object1); + Assert.IsNull(obj2.Object2); + Assert.AreEqual(new DateTime(2018, 1, 1, 21, 1, 16, DateTimeKind.Utc), obj2.ObjectNotHandled); + } + } + + [JsonArray(ItemConverterType = typeof(UnixDateTimeConverter))] + public class UnixConverterList : List { } + + [JsonDictionary(ItemConverterType = typeof(UnixDateTimeConverter))] + public class UnixConverterDictionary : Dictionary { } + + [JsonObject(ItemConverterType = typeof(UnixDateTimeConverter))] + public class UnixConverterObject + { + public object Object1 { get; set; } + + public object Object2 { get; set; } + + [JsonConverter(typeof(UnixDateTimeConverter))] + public object ObjectNotHandled { get; set; } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Converters/UnixDateTimeConverter.cs b/Src/Newtonsoft.Json/Converters/UnixDateTimeConverter.cs new file mode 100644 index 00000000..8d1bc8b3 --- /dev/null +++ b/Src/Newtonsoft.Json/Converters/UnixDateTimeConverter.cs @@ -0,0 +1,120 @@ +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +using System; +using System.Globalization; +using Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Converters +{ + /// + /// Converts a to and from Unix epoch time + /// + public class UnixDateTimeConverter : DateTimeConverterBase + { + /// + /// Unix epoch time. Thursday, January 1, 1970 12:00:00 AM GMT + /// + public readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + long ticks = default; + + if (value is DateTime dateTime) + { + ticks = (long)(dateTime.ToUniversalTime() - UnixEpoch).TotalSeconds; + } +#if HAVE_DATE_TIME_OFFSET + else if (value is DateTimeOffset dateTimeOffset) + { + ticks = (long)(dateTimeOffset.ToUniversalTime() - UnixEpoch).TotalSeconds; + } +#endif + + if (ticks < 0) + { + throw new JsonSerializationException("Expected a valid Unix date."); + } + + writer.WriteValue(ticks); + } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing property value of the JSON that is being converted. + /// The calling serializer. + /// The object value. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + if (ReflectionUtils.IsNullable(objectType)) + return null; + else + throw JsonSerializationException.Create( + reader, "Cannot convert null value to {0}.".FormatWith(CultureInfo.InvariantCulture, objectType) + ); + } + + if (long.TryParse(reader.Value + "", out long ticks)) + { + if (ticks >= 0) + { + DateTime d = UnixEpoch.AddSeconds(ticks); + +#if HAVE_DATE_TIME_OFFSET + Type t = (ReflectionUtils.IsNullableType(objectType)) + ? Nullable.GetUnderlyingType(objectType) + : objectType; + if (t == typeof(DateTimeOffset)) + { + return new DateTimeOffset(d, TimeSpan.Zero); + } +#endif + return d; + } + else + throw JsonSerializationException.Create( + reader, "Cannot convert invalid value {0} to {1}." + .FormatWith(CultureInfo.InvariantCulture, ticks, objectType) + ); + } + else + throw JsonSerializationException.Create( + reader, "Cannot convert invalid value to {0}.".FormatWith(CultureInfo.InvariantCulture, objectType) + ); + } + } +} \ No newline at end of file