diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index f9655ecb..5cd90735 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -75,4 +75,4 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("6.0.0.0")] -[assembly: AssemblyFileVersion("6.0.2.17225")] +[assembly: AssemblyFileVersion("6.0.2.17226")] diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 1e8f26d3..6a0adaea 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -7818,5 +7818,76 @@ Parameter name: value", Assert.AreEqual(originalUri, uriWithPlus2.OriginalString); } + + [Test] + public void DateFormatStringWithDateTime() + { + DateTime dt = new DateTime(2000, 12, 22); + string dateFormatString = "yyyy'-pie-'MMM'-'dddd'-'dd"; + JsonSerializerSettings settings = new JsonSerializerSettings + { + DateFormatString = dateFormatString + }; + + string json = JsonConvert.SerializeObject(dt, settings); + + Assert.AreEqual(@"""2000-pie-Dec-Friday-22""", json); + + DateTime dt1 = JsonConvert.DeserializeObject(json, settings); + + Assert.AreEqual(dt, dt1); + + JsonTextReader reader = new JsonTextReader(new StringReader(json)) + { + DateFormatString = dateFormatString + }; + JValue v = (JValue)JToken.ReadFrom(reader); + + Assert.AreEqual(JTokenType.Date, v.Type); + Assert.AreEqual(typeof(DateTime), v.Value.GetType()); + Assert.AreEqual(dt, (DateTime)v.Value); + + reader = new JsonTextReader(new StringReader(@"""abc""")) + { + DateFormatString = dateFormatString + }; + v = (JValue)JToken.ReadFrom(reader); + + Assert.AreEqual(JTokenType.String, v.Type); + Assert.AreEqual(typeof(string), v.Value.GetType()); + Assert.AreEqual("abc", v.Value); + } + +#if !NET20 + [Test] + public void DateFormatStringWithDateTimeOffset() + { + DateTimeOffset dt = new DateTimeOffset(new DateTime(2000, 12, 22)); + string dateFormatString = "yyyy'-pie-'MMM'-'dddd'-'dd"; + JsonSerializerSettings settings = new JsonSerializerSettings + { + DateFormatString = dateFormatString + }; + + string json = JsonConvert.SerializeObject(dt, settings); + + Assert.AreEqual(@"""2000-pie-Dec-Friday-22""", json); + + DateTimeOffset dt1 = JsonConvert.DeserializeObject(json, settings); + + Assert.AreEqual(dt, dt1); + + JsonTextReader reader = new JsonTextReader(new StringReader(json)) + { + DateFormatString = dateFormatString, + DateParseHandling = DateParseHandling.DateTimeOffset + }; + JValue v = (JValue)JToken.ReadFrom(reader); + + Assert.AreEqual(JTokenType.Date, v.Type); + Assert.AreEqual(typeof(DateTimeOffset), v.Value.GetType()); + Assert.AreEqual(dt, (DateTimeOffset)v.Value); + } +#endif } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonReader.cs b/Src/Newtonsoft.Json/JsonReader.cs index 20a63879..e7a6c8da 100644 --- a/Src/Newtonsoft.Json/JsonReader.cs +++ b/Src/Newtonsoft.Json/JsonReader.cs @@ -127,6 +127,7 @@ namespace Newtonsoft.Json private bool _hasExceededMaxDepth; internal DateParseHandling _dateParseHandling; internal FloatParseHandling _floatParseHandling; + private string _dateFormatString; private readonly List _stack; /// @@ -193,6 +194,15 @@ namespace Newtonsoft.Json set { _floatParseHandling = value; } } + /// + /// Get or set how custom date formatted strings are parsed when reading JSON. + /// + public string DateFormatString + { + get { return _dateFormatString; } + set { _dateFormatString = value; } + } + /// /// Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . /// @@ -440,7 +450,7 @@ namespace Newtonsoft.Json } object temp; - if (DateTimeUtils.TryParseDateTime(s, DateParseHandling.DateTimeOffset, DateTimeZoneHandling, out temp)) + if (DateTimeUtils.TryParseDateTime(s, DateParseHandling.DateTimeOffset, DateTimeZoneHandling, _dateFormatString, Culture, out temp)) { dt = (DateTimeOffset)temp; SetToken(JsonToken.Date, dt, false); @@ -728,7 +738,7 @@ namespace Newtonsoft.Json } object temp; - if (DateTimeUtils.TryParseDateTime(s, DateParseHandling.DateTime, DateTimeZoneHandling, out temp)) + if (DateTimeUtils.TryParseDateTime(s, DateParseHandling.DateTime, DateTimeZoneHandling, _dateFormatString, Culture, out temp)) { dt = (DateTime)temp; dt = DateTimeUtils.EnsureDateTime(dt, DateTimeZoneHandling); diff --git a/Src/Newtonsoft.Json/JsonSerializer.cs b/Src/Newtonsoft.Json/JsonSerializer.cs index b1902096..5d299ebc 100644 --- a/Src/Newtonsoft.Json/JsonSerializer.cs +++ b/Src/Newtonsoft.Json/JsonSerializer.cs @@ -713,6 +713,13 @@ namespace Newtonsoft.Json reader.MaxDepth = _maxDepth; } + string previousDateFormatString = null; + if (_dateFormatStringSet && reader.DateFormatString != _dateFormatString) + { + previousDateFormatString = reader.DateFormatString; + reader.DateFormatString = _dateFormatString; + } + TraceJsonReader traceJsonReader = (TraceWriter != null && TraceWriter.LevelFilter >= TraceLevel.Verbose) ? new TraceJsonReader(reader) : null; @@ -734,6 +741,8 @@ namespace Newtonsoft.Json reader.FloatParseHandling = previousFloatParseHandling.Value; if (_maxDepthSet) reader.MaxDepth = previousMaxDepth; + if (_dateFormatStringSet) + reader.DateFormatString = previousDateFormatString; return value; } diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index 866c6f9d..c4068f9d 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -153,7 +153,7 @@ namespace Newtonsoft.Json dateParseHandling = _dateParseHandling; object dt; - if (DateTimeUtils.TryParseDateTime(text, dateParseHandling, DateTimeZoneHandling, out dt)) + if (DateTimeUtils.TryParseDateTime(text, dateParseHandling, DateTimeZoneHandling, DateFormatString, Culture, out dt)) { SetToken(JsonToken.Date, dt); return; diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index 0c32d858..87829cdf 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -91,5 +91,5 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("6.0.0.0")] -[assembly: AssemblyFileVersion("6.0.2.17225")] +[assembly: AssemblyFileVersion("6.0.2.17226")] [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index d093800d..e91e4a29 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -1121,28 +1121,35 @@ To fix this error either change the JSON to a {1} or change the deserialized typ object keyValue = reader.Value; if (CheckPropertyName(reader, keyValue.ToString())) continue; + try { try { - object dt; - // this is for correctly reading ISO and MS formatted dictionary keys - if ((keyTypeCode == PrimitiveTypeCode.DateTime || keyTypeCode == PrimitiveTypeCode.DateTimeNullable) - && DateTimeUtils.TryParseDateTime(keyValue.ToString(), DateParseHandling.DateTime, reader.DateTimeZoneHandling, out dt)) + DateParseHandling dateParseHandling; + switch (keyTypeCode) { - keyValue = dt; - } + case PrimitiveTypeCode.DateTime: + case PrimitiveTypeCode.DateTimeNullable: + dateParseHandling = DateParseHandling.DateTime; + break; #if !NET20 - else if ((keyTypeCode == PrimitiveTypeCode.DateTimeOffset || keyTypeCode == PrimitiveTypeCode.DateTimeOffsetNullable) - && DateTimeUtils.TryParseDateTime(keyValue.ToString(), DateParseHandling.DateTimeOffset, reader.DateTimeZoneHandling, out dt)) - { - keyValue = dt; - } + case PrimitiveTypeCode.DateTimeOffset: + case PrimitiveTypeCode.DateTimeOffsetNullable: + dateParseHandling = DateParseHandling.DateTimeOffset; + break; #endif - else - { - keyValue = EnsureType(reader, keyValue, CultureInfo.InvariantCulture, contract.KeyContract, contract.DictionaryKeyType); + default: + dateParseHandling = DateParseHandling.None; + break; } + + // this is for correctly reading ISO and MS formatted dictionary keys + object dt; + if (dateParseHandling != DateParseHandling.None && DateTimeUtils.TryParseDateTime(keyValue.ToString(), dateParseHandling, reader.DateTimeZoneHandling, reader.DateFormatString, reader.Culture, out dt)) + keyValue = dt; + else + keyValue = EnsureType(reader, keyValue, CultureInfo.InvariantCulture, contract.KeyContract, contract.DictionaryKeyType); } catch (Exception ex) { diff --git a/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs b/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs index 5343e238..3cecc9be 100644 --- a/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs @@ -284,7 +284,7 @@ namespace Newtonsoft.Json.Utilities } } - internal static bool TryParseDateTime(string s, DateParseHandling dateParseHandling, DateTimeZoneHandling dateTimeZoneHandling, out object dt) + internal static bool TryParseDateTime(string s, DateParseHandling dateParseHandling, DateTimeZoneHandling dateTimeZoneHandling, string dateFormatString, CultureInfo culture, out object dt) { if (s.Length > 0) { @@ -292,12 +292,20 @@ namespace Newtonsoft.Json.Utilities { if (s.StartsWith("/Date(", StringComparison.Ordinal) && s.EndsWith(")/", StringComparison.Ordinal)) { - return TryParseDateMicrosoft(s, dateParseHandling, dateTimeZoneHandling, out dt); + if (TryParseDateMicrosoft(s, dateParseHandling, dateTimeZoneHandling, out dt)) + return true; } } else if (s.Length >= 19 && s.Length <= 40 && char.IsDigit(s[0]) && s[10] == 'T') { - return TryParseDateIso(s, dateParseHandling, dateTimeZoneHandling, out dt); + if (TryParseDateIso(s, dateParseHandling, dateTimeZoneHandling, out dt)) + return true; + } + + if (!string.IsNullOrEmpty(dateFormatString)) + { + if (TryParseDateExact(s, dateParseHandling, dateTimeZoneHandling, dateFormatString, culture, out dt)) + return true; } } @@ -357,6 +365,34 @@ namespace Newtonsoft.Json.Utilities } } + private static bool TryParseDateExact(string text, DateParseHandling dateParseHandling, DateTimeZoneHandling dateTimeZoneHandling, string dateFormatString, CultureInfo culture, out object dt) + { +#if !NET20 + if (dateParseHandling == DateParseHandling.DateTimeOffset) + { + DateTimeOffset temp; + if (DateTimeOffset.TryParseExact(text, dateFormatString, culture, DateTimeStyles.RoundtripKind, out temp)) + { + dt = temp; + return true; + } + } + else +#endif + { + DateTime temp; + if (DateTime.TryParseExact(text, dateFormatString, culture, DateTimeStyles.RoundtripKind, out temp)) + { + temp = EnsureDateTime(temp, dateTimeZoneHandling); + dt = temp; + return true; + } + } + + dt = null; + return false; + } + private static TimeSpan ReadOffset(string offsetText) { bool negative = (offsetText[0] == '-');