diff --git a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs index 529cbf73..eb06d293 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs @@ -2399,6 +2399,77 @@ bye", reader.Value); Assert.IsTrue(reader.Read()); } + + [Test] + public void DateParseHandling() + { + string json = @"[""1970-01-01T00:00:00Z"",""\/Date(0)\/""]"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + reader.DateParseHandling = Json.DateParseHandling.DateTime; + + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + Assert.AreEqual(new DateTime(JsonConvert.InitialJavaScriptDateTicks, DateTimeKind.Utc), reader.Value); + Assert.AreEqual(typeof(DateTime), reader.ValueType); + Assert.IsTrue(reader.Read()); + Assert.AreEqual(new DateTime(JsonConvert.InitialJavaScriptDateTicks, DateTimeKind.Utc), reader.Value); + Assert.AreEqual(typeof(DateTime), reader.ValueType); + Assert.IsTrue(reader.Read()); + +#if !NET20 + reader = new JsonTextReader(new StringReader(json)); + reader.DateParseHandling = Json.DateParseHandling.DateTimeOffset; + + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + Assert.AreEqual(new DateTimeOffset(JsonConvert.InitialJavaScriptDateTicks, TimeSpan.Zero), reader.Value); + Assert.AreEqual(typeof(DateTimeOffset), reader.ValueType); + Assert.IsTrue(reader.Read()); + Assert.AreEqual(new DateTimeOffset(JsonConvert.InitialJavaScriptDateTicks, TimeSpan.Zero), reader.Value); + Assert.AreEqual(typeof(DateTimeOffset), reader.ValueType); + Assert.IsTrue(reader.Read()); +#endif + + reader = new JsonTextReader(new StringReader(json)); + reader.DateParseHandling = Json.DateParseHandling.None; + + Assert.IsTrue(reader.Read()); + Assert.IsTrue(reader.Read()); + Assert.AreEqual(@"1970-01-01T00:00:00Z", reader.Value); + Assert.AreEqual(typeof(string), reader.ValueType); + Assert.IsTrue(reader.Read()); + Assert.AreEqual(@"/Date(0)/", reader.Value); + Assert.AreEqual(typeof(string), reader.ValueType); + Assert.IsTrue(reader.Read()); + +#if !NET20 + reader = new JsonTextReader(new StringReader(json)); + reader.DateParseHandling = Json.DateParseHandling.DateTime; + + Assert.IsTrue(reader.Read()); + reader.ReadAsDateTimeOffset(); + Assert.AreEqual(new DateTimeOffset(JsonConvert.InitialJavaScriptDateTicks, TimeSpan.Zero), reader.Value); + Assert.AreEqual(typeof(DateTimeOffset), reader.ValueType); + reader.ReadAsDateTimeOffset(); + Assert.AreEqual(new DateTimeOffset(JsonConvert.InitialJavaScriptDateTicks, TimeSpan.Zero), reader.Value); + Assert.AreEqual(typeof(DateTimeOffset), reader.ValueType); + Assert.IsTrue(reader.Read()); + + + reader = new JsonTextReader(new StringReader(json)); + reader.DateParseHandling = Json.DateParseHandling.DateTimeOffset; + + Assert.IsTrue(reader.Read()); + reader.ReadAsDateTime(); + Assert.AreEqual(new DateTime(JsonConvert.InitialJavaScriptDateTicks, DateTimeKind.Utc), reader.Value); + Assert.AreEqual(typeof(DateTime), reader.ValueType); + reader.ReadAsDateTime(); + Assert.AreEqual(new DateTime(JsonConvert.InitialJavaScriptDateTicks, DateTimeKind.Utc), reader.Value); + Assert.AreEqual(typeof(DateTime), reader.ValueType); + Assert.IsTrue(reader.Read()); +#endif + } } public class SlowStream : Stream diff --git a/Src/Newtonsoft.Json.Tests/Linq/JTokenTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JTokenTests.cs index e163a6f4..84f904e2 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JTokenTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JTokenTests.cs @@ -80,6 +80,15 @@ namespace Newtonsoft.Json.Tests.Linq v = (JValue)JToken.ReadFrom(new JsonTextReader(new StringReader(@"1.1"))); Assert.AreEqual(1.1, (double)v); + +#if !NET20 + v = (JValue)JToken.ReadFrom(new JsonTextReader(new StringReader(@"""1970-01-01T00:00:00+12:31""")) + { + DateParseHandling = DateParseHandling.DateTimeOffset + }); + Assert.AreEqual(typeof(DateTimeOffset), v.Value.GetType()); + Assert.AreEqual(new DateTimeOffset(JsonConvert.InitialJavaScriptDateTicks, new TimeSpan(12, 31, 0)), v.Value); +#endif } [Test] diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index ffad92d0..3a907527 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -920,6 +920,47 @@ keyword such as type of business."" Assert.AreEqual(testDate, m2.MemberConverter); } + [Test] + public void SerializerShouldUseMemberConverter_MsDate_DateParseNone() + { + DateTime testDate = new DateTime(JsonConvert.InitialJavaScriptDateTicks, DateTimeKind.Utc); + MemberConverterClass m1 = new MemberConverterClass { DefaultConverter = testDate, MemberConverter = testDate }; + + string json = JsonConvert.SerializeObject(m1, new JsonSerializerSettings + { + DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, + }); + Assert.AreEqual(@"{""DefaultConverter"":""\/Date(0)\/"",""MemberConverter"":""1970-01-01T00:00:00Z""}", json); + + ExceptionAssert.Throws( + "Could not convert string to DateTime: /Date(0)/. Line 1, position 33.", + () => + { + JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + DateParseHandling = DateParseHandling.None + }); + }); + } + + [Test] + public void SerializerShouldUseMemberConverter_IsoDate_DateParseNone() + { + DateTime testDate = new DateTime(JsonConvert.InitialJavaScriptDateTicks, DateTimeKind.Utc); + MemberConverterClass m1 = new MemberConverterClass { DefaultConverter = testDate, MemberConverter = testDate }; + + string json = JsonConvert.SerializeObject(m1, new JsonSerializerSettings + { + DateFormatHandling = DateFormatHandling.IsoDateFormat, + }); + Assert.AreEqual(@"{""DefaultConverter"":""1970-01-01T00:00:00Z"",""MemberConverter"":""1970-01-01T00:00:00Z""}", json); + + MemberConverterClass m2 = JsonConvert.DeserializeObject(json); + + Assert.AreEqual(testDate, m2.DefaultConverter); + Assert.AreEqual(testDate, m2.MemberConverter); + } + [Test] public void SerializerShouldUseMemberConverterOverArgumentConverter() { diff --git a/Src/Newtonsoft.Json/DateParseHandling.cs b/Src/Newtonsoft.Json/DateParseHandling.cs new file mode 100644 index 00000000..18b76ef3 --- /dev/null +++ b/Src/Newtonsoft.Json/DateParseHandling.cs @@ -0,0 +1,23 @@ +namespace Newtonsoft.Json +{ + /// + /// Specifies how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON text. + /// + public enum DateParseHandling + { + /// + /// Date formatted strings are not parsed to a date type and are read as strings. + /// + None, + /// + /// Date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed to . + /// + DateTime, +#if !NET20 + /// + /// Date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed to . + /// + DateTimeOffset +#endif + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonReader.cs b/Src/Newtonsoft.Json/JsonReader.cs index 3d193cb3..1a4a2e75 100644 --- a/Src/Newtonsoft.Json/JsonReader.cs +++ b/Src/Newtonsoft.Json/JsonReader.cs @@ -111,6 +111,7 @@ namespace Newtonsoft.Json private DateTimeZoneHandling _dateTimeZoneHandling; private int? _maxDepth; private bool _hasExceededMaxDepth; + internal DateParseHandling _dateParseHandling; /// /// Gets the current reader state. @@ -151,6 +152,15 @@ namespace Newtonsoft.Json set { _dateTimeZoneHandling = value; } } + /// + /// Get or set how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. + /// + public DateParseHandling DateParseHandling + { + get { return _dateParseHandling; } + set { _dateParseHandling = value; } + } + /// /// Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . /// @@ -237,6 +247,7 @@ namespace Newtonsoft.Json _currentState = State.Start; _stack = new List(4); _dateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind; + _dateParseHandling = DateParseHandling.DateTime; CloseInput = true; } diff --git a/Src/Newtonsoft.Json/JsonSerializer.cs b/Src/Newtonsoft.Json/JsonSerializer.cs index e4102a7d..cb8ba89c 100644 --- a/Src/Newtonsoft.Json/JsonSerializer.cs +++ b/Src/Newtonsoft.Json/JsonSerializer.cs @@ -60,6 +60,7 @@ namespace Newtonsoft.Json private Formatting? _formatting; private DateFormatHandling? _dateFormatHandling; private DateTimeZoneHandling? _dateTimeZoneHandling; + private DateParseHandling? _dateParseHandling; private CultureInfo _culture; private int? _maxDepth; private bool _maxDepthSet; @@ -314,6 +315,15 @@ namespace Newtonsoft.Json set { _dateTimeZoneHandling = value; } } + /// + /// Get or set how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. + /// + public virtual DateParseHandling DateParseHandling + { + get { return _dateParseHandling ?? JsonSerializerSettings.DefaultDateParseHandling; } + set { _dateParseHandling = value; } + } + /// /// Gets or sets the culture used when reading JSON. Defaults to . /// @@ -383,11 +393,12 @@ namespace Newtonsoft.Json jsonSerializer.ConstructorHandling = settings.ConstructorHandling; jsonSerializer.Context = settings.Context; - // reader specific - // unset values won't override reader set values + // reader/writer specific + // unset values won't override reader/writer set values jsonSerializer._formatting = settings._formatting; jsonSerializer._dateFormatHandling = settings._dateFormatHandling; jsonSerializer._dateTimeZoneHandling = settings._dateTimeZoneHandling; + jsonSerializer._dateParseHandling = settings._dateParseHandling; jsonSerializer._culture = settings._culture; jsonSerializer._maxDepth = settings._maxDepth; jsonSerializer._maxDepthSet = settings._maxDepthSet; @@ -498,6 +509,12 @@ namespace Newtonsoft.Json previousDateTimeZoneHandling = reader.DateTimeZoneHandling; reader.DateTimeZoneHandling = _dateTimeZoneHandling.Value; } + DateParseHandling? previousDateParseHandling = null; + if (_dateParseHandling != null && reader.DateTimeZoneHandling != _dateTimeZoneHandling) + { + previousDateParseHandling = reader.DateParseHandling; + reader.DateParseHandling = _dateParseHandling.Value; + } int? previousMaxDepth = null; if (_maxDepthSet && reader.MaxDepth != _maxDepth) { @@ -513,6 +530,8 @@ namespace Newtonsoft.Json reader.Culture = previousCulture; if (previousDateTimeZoneHandling != null) reader.DateTimeZoneHandling = previousDateTimeZoneHandling.Value; + if (previousDateParseHandling != null) + reader.DateParseHandling = previousDateParseHandling.Value; if (_maxDepthSet) reader.MaxDepth = previousMaxDepth; diff --git a/Src/Newtonsoft.Json/JsonSerializerSettings.cs b/Src/Newtonsoft.Json/JsonSerializerSettings.cs index d5404be6..77c2d0ae 100644 --- a/Src/Newtonsoft.Json/JsonSerializerSettings.cs +++ b/Src/Newtonsoft.Json/JsonSerializerSettings.cs @@ -26,11 +26,13 @@ namespace Newtonsoft.Json internal const Formatting DefaultFormatting = Formatting.None; internal const DateFormatHandling DefaultDateFormatHandling = DateFormatHandling.IsoDateFormat; internal const DateTimeZoneHandling DefaultDateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind; + internal const DateParseHandling DefaultDateParseHandling = DateParseHandling.DateTime; internal static readonly CultureInfo DefaultCulture; internal Formatting? _formatting; internal DateFormatHandling? _dateFormatHandling; internal DateTimeZoneHandling? _dateTimeZoneHandling; + internal DateParseHandling? _dateParseHandling; internal CultureInfo _culture; internal int? _maxDepth; internal bool _maxDepthSet; @@ -169,6 +171,15 @@ namespace Newtonsoft.Json set { _dateTimeZoneHandling = value; } } + /// + /// Get or set how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. + /// + public DateParseHandling DateParseHandling + { + get { return _dateParseHandling ?? DefaultDateParseHandling; } + set { _dateParseHandling = value; } + } + /// /// Gets or sets the culture used when reading JSON. Defaults to . /// diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index 3d033a1a..8ac6a831 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -137,20 +137,23 @@ namespace Newtonsoft.Json { string text = _stringReference.ToString(); - if (text.Length > 0) + if (_dateParseHandling != DateParseHandling.None) { - if (text[0] == '/') + if (text.Length > 0) { - if (text.StartsWith("/Date(", StringComparison.Ordinal) && text.EndsWith(")/", StringComparison.Ordinal)) + if (text[0] == '/') { - ParseDateMicrosoft(text); - return; + if (text.StartsWith("/Date(", StringComparison.Ordinal) && text.EndsWith(")/", StringComparison.Ordinal)) + { + ParseDateMicrosoft(text); + return; + } + } + else if (char.IsDigit(text[0]) && text.Length >= 19 && text.Length <= 40) + { + if (ParseDateIso(text)) + return; } - } - else if (char.IsDigit(text[0]) && text.Length >= 19 && text.Length <= 40) - { - if (ParseDateIso(text)) - return; } } @@ -164,7 +167,7 @@ namespace Newtonsoft.Json const string isoDateFormat = "yyyy-MM-ddTHH:mm:ss.FFFFFFFK"; #if !NET20 - if (_readType == ReadType.ReadAsDateTimeOffset) + if (_readType == ReadType.ReadAsDateTimeOffset || (_readType == ReadType.Read && _dateParseHandling == DateParseHandling.DateTimeOffset)) { DateTimeOffset dateTimeOffset; if (DateTimeOffset.TryParseExact(text, isoDateFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTimeOffset)) @@ -213,7 +216,7 @@ namespace Newtonsoft.Json DateTime utcDateTime = JsonConvert.ConvertJavaScriptTicksToDateTime(javaScriptTicks); #if !NET20 - if (_readType == ReadType.ReadAsDateTimeOffset) + if (_readType == ReadType.ReadAsDateTimeOffset || (_readType == ReadType.Read && _dateParseHandling == DateParseHandling.DateTimeOffset)) { SetToken(JsonToken.Date, new DateTimeOffset(utcDateTime.Add(offset).Ticks, offset)); } diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj index 1940fad3..f7faaf34 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj @@ -93,6 +93,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj index 0a593d1a..440ee79e 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj @@ -105,6 +105,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj index 1292d297..35bd7b32 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj @@ -104,6 +104,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj index 580abf27..13bab127 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj @@ -77,6 +77,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index 1f8424d0..760ae37d 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -104,6 +104,7 @@ +