diff --git a/Src/Newtonsoft.Json.Tests/Serialization/DateOnlyTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/DateOnlyTests.cs new file mode 100644 index 00000000..5d84052e --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/Serialization/DateOnlyTests.cs @@ -0,0 +1,203 @@ +#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 + +#if NET6_0_OR_GREATER +using System; +using System.Collections.Generic; +#if !NET20 +using System.Xml.Linq; +#endif +#if DNXCORE50 +using Xunit; +using Test = Xunit.FactAttribute; +using Assert = Newtonsoft.Json.Tests.XUnitAssert; +using Xunit.Abstractions; +#else +using NUnit.Framework; +#endif + +namespace Newtonsoft.Json.Tests.Serialization +{ + [TestFixture] + public class DateOnlyTests : TestFixtureBase + { + [Test] + public void Serialize() + { + DateOnly d = new DateOnly(2000, 12, 29); + string json = JsonConvert.SerializeObject(d, Formatting.Indented); + + Assert.AreEqual(@"""2000-12-29""", json); + } + + [Test] + public void SerializeDefault() + { + DateOnly d = default; + string json = JsonConvert.SerializeObject(d, Formatting.Indented); + + Assert.AreEqual(@"""0001-01-01""", json); + } + + [Test] + public void SerializeMaxValue() + { + DateOnly d = DateOnly.MaxValue; + string json = JsonConvert.SerializeObject(d, Formatting.Indented); + + Assert.AreEqual(@"""9999-12-31""", json); + } + + [Test] + public void SerializeMinValue() + { + DateOnly d = DateOnly.MinValue; + string json = JsonConvert.SerializeObject(d, Formatting.Indented); + + Assert.AreEqual(@"""0001-01-01""", json); + } + + [Test] + public void SerializeNullable_Null() + { + DateOnly? d = default; + string json = JsonConvert.SerializeObject(d, Formatting.Indented); + + Assert.AreEqual("null", json); + } + + [Test] + public void SerializeNullable_Value() + { + DateOnly? d = new DateOnly(2000, 12, 29); + string json = JsonConvert.SerializeObject(d, Formatting.Indented); + + Assert.AreEqual(@"""2000-12-29""", json); + } + + [Test] + public void SerializeList() + { + IList d = new List + { + new DateOnly(2000, 12, 29) + }; + string json = JsonConvert.SerializeObject(d, Formatting.Indented); + + Assert.AreEqual(@"[ + ""2000-12-29"" +]", json); + } + + [Test] + public void SerializeList_Nullable() + { + IList d = new List + { + new DateOnly(2000, 12, 29), + null + }; + string json = JsonConvert.SerializeObject(d, Formatting.Indented); + + Assert.AreEqual(@"[ + ""2000-12-29"", + null +]", json); + } + + [Test] + public void Deserialize() + { + DateOnly d = JsonConvert.DeserializeObject(@"""2000-12-29"""); + + Assert.AreEqual(new DateOnly(2000, 12, 29), d); + } + + [Test] + public void DeserializeDefault() + { + DateOnly d = JsonConvert.DeserializeObject(@"""0001-01-01"""); + + Assert.AreEqual(default(DateOnly), d); + } + + [Test] + public void DeserializeMaxValue() + { + DateOnly d = JsonConvert.DeserializeObject(@"""9999-12-31"""); + + Assert.AreEqual(DateOnly.MaxValue, d); + } + + [Test] + public void DeserializeMinValue() + { + DateOnly d = JsonConvert.DeserializeObject(@"""0001-01-01"""); + + Assert.AreEqual(DateOnly.MinValue, d); + } + + [Test] + public void DeserializeNullable_Null() + { + DateOnly? d = JsonConvert.DeserializeObject(@"null"); + + Assert.AreEqual(null, d); + } + + [Test] + public void DeserializeNullable_Value() + { + DateOnly? d = JsonConvert.DeserializeObject(@"""2000-12-29"""); + + Assert.AreEqual(new DateOnly(2000, 12, 29), d); + } + + [Test] + public void DeserializeList() + { + var l = JsonConvert.DeserializeObject>(@"[ + ""2000-12-29"" +]"); + + Assert.AreEqual(1, l.Count); + Assert.AreEqual(new DateOnly(2000, 12, 29), l[0]); + } + + [Test] + public void DeserializeList_Nullable() + { + var l = JsonConvert.DeserializeObject>(@"[ + ""2000-12-29"", + null +]"); + + Assert.AreEqual(2, l.Count); + Assert.AreEqual(new DateOnly(2000, 12, 29), l[0]); + Assert.AreEqual(null, l[1]); + } + } +} +#endif diff --git a/Src/Newtonsoft.Json.Tests/Serialization/TimeOnlyTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/TimeOnlyTests.cs new file mode 100644 index 00000000..ce9c3d5f --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/Serialization/TimeOnlyTests.cs @@ -0,0 +1,212 @@ +#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 + +#if NET6_0_OR_GREATER +using System; +using System.Collections.Generic; +#if !NET20 +using System.Xml.Linq; +#endif +#if DNXCORE50 +using Xunit; +using Test = Xunit.FactAttribute; +using Assert = Newtonsoft.Json.Tests.XUnitAssert; +using Xunit.Abstractions; +#else +using NUnit.Framework; +#endif + +namespace Newtonsoft.Json.Tests.Serialization +{ + [TestFixture] + public class TimeOnlyTests : TestFixtureBase + { + [Test] + public void Serialize() + { + TimeOnly t = new TimeOnly(23, 59, 59); + string json = JsonConvert.SerializeObject(t, Formatting.Indented); + + Assert.AreEqual(@"""23:59:59""", json); + } + + [Test] + public void Serialize_Milliseconds() + { + TimeOnly t = new TimeOnly(23, 59, 59, 999); + string json = JsonConvert.SerializeObject(t, Formatting.Indented); + + Assert.AreEqual(@"""23:59:59.999""", json); + } + + [Test] + public void SerializeDefault() + { + TimeOnly t = default; + string json = JsonConvert.SerializeObject(t, Formatting.Indented); + + Assert.AreEqual(@"""00:00:00""", json); + } + + [Test] + public void SerializeMaxValue() + { + TimeOnly t = TimeOnly.MaxValue; + string json = JsonConvert.SerializeObject(t, Formatting.Indented); + + Assert.AreEqual(@"""23:59:59.9999999""", json); + } + + [Test] + public void SerializeMinValue() + { + TimeOnly t = TimeOnly.MinValue; + string json = JsonConvert.SerializeObject(t, Formatting.Indented); + + Assert.AreEqual(@"""00:00:00""", json); + } + + [Test] + public void SerializeNullable_Null() + { + TimeOnly? t = default; + string json = JsonConvert.SerializeObject(t, Formatting.Indented); + + Assert.AreEqual("null", json); + } + + [Test] + public void SerializeNullable_Value() + { + TimeOnly? t = new TimeOnly(23, 59, 59, 999); + string json = JsonConvert.SerializeObject(t, Formatting.Indented); + + Assert.AreEqual(@"""23:59:59.999""", json); + } + + [Test] + public void SerializeList() + { + IList l = new List + { + new TimeOnly(23, 59, 59) + }; + string json = JsonConvert.SerializeObject(l, Formatting.Indented); + + Assert.AreEqual(@"[ + ""23:59:59"" +]", json); + } + + [Test] + public void SerializeList_Nullable() + { + IList l = new List + { + new TimeOnly(23, 59, 59), + null + }; + string json = JsonConvert.SerializeObject(l, Formatting.Indented); + + Assert.AreEqual(@"[ + ""23:59:59"", + null +]", json); + } + + [Test] + public void Deserialize() + { + TimeOnly t = JsonConvert.DeserializeObject(@"""23:59:59"""); + + Assert.AreEqual(new TimeOnly(23, 59, 59), t); + } + + [Test] + public void DeserializeDefault() + { + TimeOnly t = JsonConvert.DeserializeObject(@"""00:00:00"""); + + Assert.AreEqual(default(TimeOnly), t); + } + + [Test] + public void DeserializeMaxValue() + { + TimeOnly t = JsonConvert.DeserializeObject(@"""23:59:59.9999999"""); + + Assert.AreEqual(TimeOnly.MaxValue, t); + } + + [Test] + public void DeserializeMinValue() + { + TimeOnly t = JsonConvert.DeserializeObject(@"""00:00:00"""); + + Assert.AreEqual(TimeOnly.MinValue, t); + } + + [Test] + public void DeserializeNullable_Null() + { + TimeOnly? t = JsonConvert.DeserializeObject(@"null"); + + Assert.AreEqual(null, t); + } + + [Test] + public void DeserializeNullable_Value() + { + TimeOnly? t = JsonConvert.DeserializeObject(@"""23:59:59"""); + + Assert.AreEqual(new TimeOnly(23, 59, 59), t); + } + + [Test] + public void DeserializeList() + { + var l = JsonConvert.DeserializeObject>(@"[ + ""23:59:59"" +]"); + + Assert.AreEqual(1, l.Count); + Assert.AreEqual(new TimeOnly(23, 59, 59), l[0]); + } + + [Test] + public void DeserializeList_Nullable() + { + var l = JsonConvert.DeserializeObject>(@"[ + ""23:59:59"", + null +]"); + + Assert.AreEqual(2, l.Count); + Assert.AreEqual(new TimeOnly(23, 59, 59), l[0]); + Assert.AreEqual(null, l[1]); + } + } +} +#endif diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index 21b98b1e..60fe7e82 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -53,7 +53,7 @@ Json.NET .NET 6.0 - HAVE_ADO_NET;HAVE_APP_DOMAIN;HAVE_ASYNC;HAVE_BIG_INTEGER;HAVE_BINARY_FORMATTER;HAVE_BINARY_SERIALIZATION;HAVE_BINARY_EXCEPTION_SERIALIZATION;HAVE_CHAR_TO_LOWER_WITH_CULTURE;HAVE_CHAR_TO_STRING_WITH_CULTURE;HAVE_COM_ATTRIBUTES;HAVE_COMPONENT_MODEL;HAVE_CONCURRENT_COLLECTIONS;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DB_NULL_TYPE_CODE;HAVE_DYNAMIC;HAVE_EMPTY_TYPES;HAVE_ENTITY_FRAMEWORK;HAVE_EXPRESSIONS;HAVE_FAST_REVERSE;HAVE_FSHARP_TYPES;HAVE_FULL_REFLECTION;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_ICLONEABLE;HAVE_ICONVERTIBLE;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_INOTIFY_PROPERTY_CHANGING;HAVE_ISET;HAVE_LINQ;HAVE_MEMORY_BARRIER;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_NON_SERIALIZED_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_REFLECTION_EMIT;HAVE_REGEX_TIMEOUTS;HAVE_SECURITY_SAFE_CRITICAL_ATTRIBUTE;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STREAM_READER_WRITER_CLOSE;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TRACE_WRITER;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_XML_DOCUMENT;HAVE_XML_DOCUMENT_TYPE;HAVE_CONCURRENT_DICTIONARY;HAVE_INDEXOF_STRING_COMPARISON;HAVE_REPLACE_STRING_COMPARISON;HAVE_REPLACE_STRING_COMPARISON;HAVE_GETHASHCODE_STRING_COMPARISON;HAVE_NULLABLE_ATTRIBUTES;HAVE_DYNAMIC_CODE_COMPILED;HAS_ARRAY_EMPTY;$(AdditionalConstants) + HAVE_ADO_NET;HAVE_APP_DOMAIN;HAVE_ASYNC;HAVE_BIG_INTEGER;HAVE_BINARY_FORMATTER;HAVE_BINARY_SERIALIZATION;HAVE_BINARY_EXCEPTION_SERIALIZATION;HAVE_CHAR_TO_LOWER_WITH_CULTURE;HAVE_CHAR_TO_STRING_WITH_CULTURE;HAVE_COM_ATTRIBUTES;HAVE_COMPONENT_MODEL;HAVE_CONCURRENT_COLLECTIONS;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DB_NULL_TYPE_CODE;HAVE_DYNAMIC;HAVE_EMPTY_TYPES;HAVE_ENTITY_FRAMEWORK;HAVE_EXPRESSIONS;HAVE_FAST_REVERSE;HAVE_FSHARP_TYPES;HAVE_FULL_REFLECTION;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_ICLONEABLE;HAVE_ICONVERTIBLE;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_INOTIFY_PROPERTY_CHANGING;HAVE_ISET;HAVE_LINQ;HAVE_MEMORY_BARRIER;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_NON_SERIALIZED_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_REFLECTION_EMIT;HAVE_REGEX_TIMEOUTS;HAVE_SECURITY_SAFE_CRITICAL_ATTRIBUTE;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STREAM_READER_WRITER_CLOSE;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TRACE_WRITER;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_XML_DOCUMENT;HAVE_XML_DOCUMENT_TYPE;HAVE_CONCURRENT_DICTIONARY;HAVE_INDEXOF_STRING_COMPARISON;HAVE_REPLACE_STRING_COMPARISON;HAVE_REPLACE_STRING_COMPARISON;HAVE_GETHASHCODE_STRING_COMPARISON;HAVE_NULLABLE_ATTRIBUTES;HAVE_DYNAMIC_CODE_COMPILED;HAS_ARRAY_EMPTY;HAVE_DATE_ONLY;$(AdditionalConstants) Json.NET .NET 4.5 diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index dd6477e3..94bf5108 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -1292,6 +1292,13 @@ namespace Newtonsoft.Json.Serialization return true; } +#if HAVE_DATE_ONLY + if (type == typeof(DateOnly) || type == typeof(TimeOnly)) + { + return true; + } +#endif + return false; } diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index 4ea15838..7feb6ad3 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -396,6 +396,19 @@ namespace Newtonsoft.Json.Serialization internal static bool TryConvertToString(object value, Type type, [NotNullWhen(true)]out string? s) { +#if HAVE_DATE_ONLY + if (value is DateOnly dateOnly) + { + s = dateOnly.ToString("yyyy'-'MM'-'dd", CultureInfo.InvariantCulture); + return true; + } + if (value is TimeOnly timeOnly) + { + s = timeOnly.ToString("HH':'mm':'ss.FFFFFFF", CultureInfo.InvariantCulture); + return true; + } +#endif + #if HAVE_TYPE_DESCRIPTOR if (JsonTypeReflector.CanTypeDescriptorConvertString(type, out TypeConverter converter)) { diff --git a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs index 9ce44964..b6837eca 100644 --- a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs @@ -503,6 +503,18 @@ namespace Newtonsoft.Json.Utilities value = Type.GetType(s, true); return ConvertResult.Success; } +#if HAVE_DATE_ONLY + if (targetType == typeof(DateOnly)) + { + value = DateOnly.ParseExact(s, "yyyy'-'MM'-'dd", CultureInfo.InvariantCulture); + return ConvertResult.Success; + } + if (targetType == typeof(TimeOnly)) + { + value = TimeOnly.ParseExact(s, "HH':'mm':'ss.FFFFFFF", CultureInfo.InvariantCulture); + return ConvertResult.Success; + } +#endif } #if HAVE_BIG_INTEGER