diff --git a/Src/Newtonsoft.Json.TestConsole/Program.cs b/Src/Newtonsoft.Json.TestConsole/Program.cs index ebd74129..994af9f7 100644 --- a/Src/Newtonsoft.Json.TestConsole/Program.cs +++ b/Src/Newtonsoft.Json.TestConsole/Program.cs @@ -52,13 +52,10 @@ namespace Newtonsoft.Json.TestConsole //t.DeserializeLargeJson(); //DeserializeLargeJson(); - //WriteLargeJson(); + WriteLargeJson(); //DeserializeJson(); //ReadLargeJson(); //ReadLargeJsonJavaScriptSerializer(); - DiscriminatedUnionConverterTests t = new DiscriminatedUnionConverterTests(); - t.SerializePerformance(); - t.DeserializePerformance(); Console.WriteLine(); Console.WriteLine("Finished"); diff --git a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs index 297651e3..3778f7cc 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs @@ -24,6 +24,7 @@ #endregion using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using Newtonsoft.Json.Linq; @@ -1175,8 +1176,8 @@ third line", jsonTextReader.Value); public class FakeBufferPool : IJsonBufferPool { - public readonly IList FreeBuffers = new List(); - public readonly IList UsedBuffers = new List(); + public readonly List FreeBuffers = new List(); + public readonly List UsedBuffers = new List(); public char[] RentBuffer(int minSize) { @@ -1197,10 +1198,12 @@ third line", jsonTextReader.Value); public void ReturnBuffer(ref char[] buffer) { - UsedBuffers.Remove(buffer); - if (!FreeBuffers.Contains(buffer)) + if (UsedBuffers.Remove(buffer)) { FreeBuffers.Add(buffer); + + // smallest first so the first buffer large enough is rented + FreeBuffers.Sort((b1, b2) => Comparer.Default.Compare(b1.Length, b2.Length)); } buffer = null; @@ -1215,7 +1218,8 @@ third line", jsonTextReader.Value); ""Description"": ""Amazing!\nBuy now!"", ""Drives"": [ ""DVD read/writer"", - ""500 gigabyte hard drive"" + ""500 gigabyte hard drive"", + ""Amazing Drive" + new string('!', 9000) + @""" ] }"; @@ -1238,7 +1242,8 @@ third line", jsonTextReader.Value); } } - Assert.AreEqual(2, bufferPool.FreeBuffers.Count); + Assert.AreEqual(0, bufferPool.UsedBuffers.Count); + Assert.AreEqual(6, bufferPool.FreeBuffers.Count); } [Test] diff --git a/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs index 6ca293cb..acbb43dc 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs @@ -59,6 +59,50 @@ namespace Newtonsoft.Json.Tests [TestFixture] public class JsonTextWriterTest : TestFixtureBase { + [Test] + public void BufferTest() + { + JsonTextReaderTest.FakeBufferPool bufferPool = new JsonTextReaderTest.FakeBufferPool(); + + string longString = new string('A', 2000); + string longEscapedString = "Hello!" + new string('!', 50) + new string('\n', 1000) + "Good bye!"; + string longerEscapedString = "Hello!" + new string('!', 2000) + new string('\n', 1000) + "Good bye!"; + + for (int i = 0; i < 1000; i++) + { + StringWriter sw = new StringWriter(CultureInfo.InvariantCulture); + + using (JsonTextWriter writer = new JsonTextWriter(sw)) + { + writer.BufferPool = bufferPool; + + writer.WriteStartObject(); + + writer.WritePropertyName("Prop1"); + writer.WriteValue(new DateTime(2000, 12, 12, 12, 12, 12, DateTimeKind.Utc)); + + writer.WritePropertyName("Prop2"); + writer.WriteValue(longString); + + writer.WritePropertyName("Prop3"); + writer.WriteValue(longEscapedString); + + writer.WritePropertyName("Prop4"); + writer.WriteValue(longerEscapedString); + + writer.WriteEndObject(); + } + + if ((i + 1) % 100 == 0) + { + Console.WriteLine("Allocated buffers: " + bufferPool.FreeBuffers.Count); + } + } + + Assert.AreEqual(0, bufferPool.UsedBuffers.Count); + Assert.AreEqual(3, bufferPool.FreeBuffers.Count); + } + [Test] public void NewLine() { diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 4f01a765..82a9ae80 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -7487,6 +7487,34 @@ Path '', line 1, position 1."); Assert.AreEqual(2, dic2[new DateTime(2013, 12, 12, 12, 12, 12, DateTimeKind.Utc)]); } + [Test] + public void DateTimeDictionaryKey_DateTime_Iso_Local() + { + IDictionary dic1 = new Dictionary + { + { new DateTime(2000, 12, 12, 12, 12, 12, DateTimeKind.Utc), 1 }, + { new DateTime(2013, 12, 12, 12, 12, 12, DateTimeKind.Utc), 2 } + }; + + string json = JsonConvert.SerializeObject(dic1, Formatting.Indented, new JsonSerializerSettings + { + DateTimeZoneHandling = DateTimeZoneHandling.Local + }); + + JObject o = JObject.Parse(json); + Assert.IsFalse(o.Properties().ElementAt(0).Name.Contains("Z")); + Assert.IsFalse(o.Properties().ElementAt(1).Name.Contains("Z")); + + IDictionary dic2 = JsonConvert.DeserializeObject>(json, new JsonSerializerSettings + { + DateTimeZoneHandling = DateTimeZoneHandling.Utc + }); + + Assert.AreEqual(2, dic2.Count); + Assert.AreEqual(1, dic2[new DateTime(2000, 12, 12, 12, 12, 12, DateTimeKind.Utc)]); + Assert.AreEqual(2, dic2[new DateTime(2013, 12, 12, 12, 12, 12, DateTimeKind.Utc)]); + } + [Test] public void DateTimeDictionaryKey_DateTime_MS() { diff --git a/Src/Newtonsoft.Json.Tests/Utilities/DateTimeUtilsTests.cs b/Src/Newtonsoft.Json.Tests/Utilities/DateTimeUtilsTests.cs index 90efefa9..6a91d1a1 100644 --- a/Src/Newtonsoft.Json.Tests/Utilities/DateTimeUtilsTests.cs +++ b/Src/Newtonsoft.Json.Tests/Utilities/DateTimeUtilsTests.cs @@ -53,16 +53,20 @@ namespace Newtonsoft.Json.Tests.Utilities RoundtripDateIso(DateTime.MaxValue); } + private static StringReference CreateStringReference(string s) + { + return new StringReference(s.ToCharArray(), 0, s.Length); + } + private static void RoundtripDateIso(DateTime value) { StringWriter sw = new StringWriter(); DateTimeUtils.WriteDateTimeString(sw, value, DateFormatHandling.IsoDateFormat, null, CultureInfo.InvariantCulture); string minDateText = sw.ToString(); - object dt; - DateTimeUtils.TryParseDateIso(minDateText, DateParseHandling.DateTime, DateTimeZoneHandling.RoundtripKind, out dt); + DateTime parsedDt; + DateTimeUtils.TryParseDateTimeIso(CreateStringReference(minDateText), DateTimeZoneHandling.RoundtripKind, out parsedDt); - DateTime parsedDt = (DateTime)dt; Assert.AreEqual(value, parsedDt); } @@ -118,8 +122,12 @@ namespace Newtonsoft.Json.Tests.Utilities oldDate = oldDt; } - object newDt; - DateTimeUtils.TryParseDateIso(text, DateParseHandling.DateTime, DateTimeZoneHandling.RoundtripKind, out newDt); + object newDt = null; + DateTime temp; + if (DateTimeUtils.TryParseDateTimeIso(CreateStringReference(text), DateTimeZoneHandling.RoundtripKind, out temp)) + { + newDt = temp; + } if (!Equals(oldDate, newDt)) { @@ -175,17 +183,26 @@ namespace Newtonsoft.Json.Tests.Utilities private void AssertNewDateTimeOffsetParseEqual(string text) { object oldDt; - object newDt; + object newDt = null; + TryParseDateIso(text, DateParseHandling.DateTimeOffset, DateTimeZoneHandling.Unspecified, out oldDt); - DateTimeUtils.TryParseDateIso(text, DateParseHandling.DateTimeOffset, DateTimeZoneHandling.Unspecified, out newDt); + + DateTimeOffset temp; + if (DateTimeUtils.TryParseDateTimeOffsetIso(CreateStringReference(text), out temp)) + { + newDt = temp; + } if (!Equals(oldDt, newDt)) { + long? oldTicks = oldDt != null ? (long?)((DateTime)oldDt).Ticks : null; + long? newTicks = newDt != null ? (long?)((DateTime)newDt).Ticks : null; + Assert.AreEqual(oldDt, newDt, "DateTimeOffset parse not equal. Text: '{0}' Old ticks: {1} New ticks: {2}".FormatWith( CultureInfo.InvariantCulture, text, - ((DateTime)oldDt).Ticks, - ((DateTime)newDt).Ticks)); + oldTicks, + newTicks)); } } #endif diff --git a/Src/Newtonsoft.Json/Bson/BsonReader.cs b/Src/Newtonsoft.Json/Bson/BsonReader.cs index 4c3effc5..aac3b406 100644 --- a/Src/Newtonsoft.Json/Bson/BsonReader.cs +++ b/Src/Newtonsoft.Json/Bson/BsonReader.cs @@ -215,7 +215,7 @@ namespace Newtonsoft.Json.Bson /// /// Reads the next JSON token from the stream as a . /// - /// A . This method will return null at the end of an array. + /// A . This method will return null at the end of an array. public override DateTime? ReadAsDateTime() { return ReadAsDateTimeInternal(); diff --git a/Src/Newtonsoft.Json/JsonReader.cs b/Src/Newtonsoft.Json/JsonReader.cs index 5e1a3861..bab1d2f3 100644 --- a/Src/Newtonsoft.Json/JsonReader.cs +++ b/Src/Newtonsoft.Json/JsonReader.cs @@ -422,7 +422,7 @@ namespace Newtonsoft.Json /// /// Reads the next JSON token from the stream as a . /// - /// A . This method will return null at the end of an array. + /// A . This method will return null at the end of an array. public abstract DateTime? ReadAsDateTime(); #if !NET20 @@ -478,11 +478,9 @@ namespace Newtonsoft.Json return null; } - object temp; DateTimeOffset dt; - if (DateTimeUtils.TryParseDateTime(s, DateParseHandling.DateTimeOffset, DateTimeZoneHandling, _dateFormatString, Culture, out temp)) + if (DateTimeUtils.TryParseDateTimeOffset(s, _dateFormatString, Culture, out dt)) { - dt = (DateTimeOffset)temp; SetToken(JsonToken.Date, dt, false); return dt; } @@ -793,10 +791,8 @@ namespace Newtonsoft.Json } DateTime dt; - object temp; - if (DateTimeUtils.TryParseDateTime(s, DateParseHandling.DateTime, DateTimeZoneHandling, _dateFormatString, Culture, out temp)) + if (DateTimeUtils.TryParseDateTime(s, DateTimeZoneHandling, _dateFormatString, Culture, out dt)) { - dt = (DateTime)temp; dt = DateTimeUtils.EnsureDateTime(dt, DateTimeZoneHandling); SetToken(JsonToken.Date, dt, false); return dt; diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index c274d903..460a0008 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -54,6 +54,7 @@ namespace Newtonsoft.Json { private const char UnicodeReplacementChar = '\uFFFD'; private const int MaximumJavascriptIntegerCharacterLength = 380; + private readonly TextReader _reader; private char[] _chars; private int _charsUsed; @@ -73,7 +74,9 @@ namespace Newtonsoft.Json public JsonTextReader(TextReader reader) { if (reader == null) + { throw new ArgumentNullException("reader"); + } _reader = reader; _lineNumber = 1; @@ -107,7 +110,7 @@ namespace Newtonsoft.Json { if (_stringBuffer.IsEmpty) { - _stringBuffer = new StringBuffer(_bufferPool, 1025); + _stringBuffer = new StringBuffer(_bufferPool, 1024); } } @@ -153,33 +156,52 @@ namespace Newtonsoft.Json } else { - string text = _stringReference.ToString(); - if (_dateParseHandling != DateParseHandling.None) { DateParseHandling dateParseHandling; if (_readType == ReadType.ReadAsDateTime) + { dateParseHandling = DateParseHandling.DateTime; + } #if !NET20 else if (_readType == ReadType.ReadAsDateTimeOffset) + { dateParseHandling = DateParseHandling.DateTimeOffset; + } #endif else - dateParseHandling = _dateParseHandling; - - object dt; - if (DateTimeUtils.TryParseDateTime(text, dateParseHandling, DateTimeZoneHandling, DateFormatString, Culture, out dt)) { - SetToken(JsonToken.Date, dt, false); - return; + dateParseHandling = _dateParseHandling; } + + if (dateParseHandling == DateParseHandling.DateTime) + { + DateTime dt; + if (DateTimeUtils.TryParseDateTime(_stringReference, DateTimeZoneHandling, DateFormatString, Culture, out dt)) + { + SetToken(JsonToken.Date, dt, false); + return; + } + } +#if !NET20 + else + { + DateTimeOffset dt; + if (DateTimeUtils.TryParseDateTimeOffset(_stringReference, DateFormatString, Culture, out dt)) + { + SetToken(JsonToken.Date, dt, false); + return; + } + } +#endif } - SetToken(JsonToken.String, text, false); + SetToken(JsonToken.String, _stringReference.ToString(), false); _quoteChar = quote; } } + private static void BlockCopyChars(char[] src, int srcOffset, char[] dst, int dstOffset, int count) { const int charByteCount = 2; @@ -196,7 +218,9 @@ namespace Newtonsoft.Json { int count = _charsUsed - _charPos; if (count > 0) + { BlockCopyChars(_chars, _charPos, _chars, 0, count); + } _lineStartPos -= _charPos; _charPos = 0; @@ -213,7 +237,9 @@ namespace Newtonsoft.Json private int ReadData(bool append, int charsRequired) { if (_isEndOfFile) + { return 0; + } // char buffer is full if (_charsUsed + charsRequired >= _chars.Length - 1) @@ -242,7 +268,9 @@ namespace Newtonsoft.Json char[] dst = BufferUtils.RentBuffer(_bufferPool, remainingCharCount + charsRequired + 1); if (remainingCharCount > 0) + { BlockCopyChars(_chars, _charPos, dst, 0, remainingCharCount); + } BufferUtils.ReturnBuffer(_bufferPool, ref _chars); @@ -252,7 +280,9 @@ namespace Newtonsoft.Json { // copy any remaining data to the beginning of the buffer if needed and reset positions if (remainingCharCount > 0) + { BlockCopyChars(_chars, _charPos, _chars, 0, remainingCharCount); + } } _lineStartPos -= _charPos; @@ -268,7 +298,9 @@ namespace Newtonsoft.Json _charsUsed += charsRead; if (charsRead == 0) + { _isEndOfFile = true; + } _chars[_charsUsed] = '\0'; return charsRead; @@ -277,7 +309,9 @@ namespace Newtonsoft.Json private bool EnsureChars(int relativePosition, bool append) { if (_charPos + relativePosition >= _charsUsed) + { return ReadChars(relativePosition, append); + } return true; } @@ -285,7 +319,9 @@ namespace Newtonsoft.Json private bool ReadChars(int relativePosition, bool append) { if (_isEndOfFile) + { return false; + } int charsRequired = _charPos + relativePosition - _charsUsed + 1; @@ -299,13 +335,17 @@ namespace Newtonsoft.Json // no more content if (charsRead == 0) + { break; + } totalCharsRead += charsRead; } while (totalCharsRead < charsRequired); if (totalCharsRead < charsRequired) + { return false; + } return true; } @@ -369,7 +409,7 @@ namespace Newtonsoft.Json /// /// Reads the next JSON token from the stream as a . /// - /// A . This method will return null at the end of an array. + /// A . This method will return null at the end of an array. public override DateTime? ReadAsDateTime() { return ReadAsDateTimeInternal(); @@ -391,6 +431,7 @@ namespace Newtonsoft.Json if (_chars == null) { _chars = BufferUtils.RentBuffer(_bufferPool, 1024); + _chars[0] = '\0'; } while (true) @@ -658,7 +699,9 @@ namespace Newtonsoft.Json if (_charsUsed == charPos) { if (ReadData(true) == 0) + { return; + } } else { @@ -800,7 +843,9 @@ namespace Newtonsoft.Json if (_charsUsed == _charPos) { if (ReadData(false) == 0) + { return false; + } } else { @@ -871,7 +916,9 @@ namespace Newtonsoft.Json // no match in name table if (propertyName == null) + { propertyName = _stringReference.ToString(); + } } else { @@ -881,7 +928,9 @@ namespace Newtonsoft.Json EatWhitespace(false); if (_chars[_charPos] != ':') + { throw JsonReaderException.Create(this, "Invalid character after parsing property name. Expected ':' but got: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos])); + } _charPos++; @@ -971,11 +1020,17 @@ namespace Newtonsoft.Json char next = _chars[_charPos + 1]; if (next == 'u') + { ParseNull(); + } else if (next == 'e') + { ParseConstructor(); + } else + { throw JsonReaderException.Create(this, "Unexpected character encountered while parsing value: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos])); + } } else { @@ -990,9 +1045,13 @@ namespace Newtonsoft.Json return true; case '-': if (EnsureChars(1, true) && _chars[_charPos + 1] == 'I') + { ParseNumberNegativeInfinity(); + } else + { ParseNumber(); + } return true; case '/': ParseComment(); @@ -1061,7 +1120,9 @@ namespace Newtonsoft.Json _charPos++; if (EnsureChars(1, append) && _chars[_charPos] == StringUtils.LineFeed) + { _charPos++; + } OnNewLine(_charPos); } @@ -1080,7 +1141,9 @@ namespace Newtonsoft.Json if (_charsUsed == _charPos) { if (ReadData(false) == 0) + { finished = true; + } } else { @@ -1127,7 +1190,9 @@ namespace Newtonsoft.Json if (_charsUsed == _charPos) { if (ReadData(true) == 0) + { throw JsonReaderException.Create(this, "Unexpected end while parsing constructor."); + } } else { @@ -1175,7 +1240,9 @@ namespace Newtonsoft.Json EatWhitespace(false); if (_chars[_charPos] != '(') + { throw JsonReaderException.Create(this, "Unexpected character while parsing constructor: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos])); + } _charPos++; @@ -1241,11 +1308,17 @@ namespace Newtonsoft.Json int value; ParseResult parseResult = ConvertUtils.Int32TryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out value); if (parseResult == ParseResult.Success) + { numberValue = value; + } else if (parseResult == ParseResult.Overflow) + { throw JsonReaderException.Create(this, "JSON integer {0} is too large or small for an Int32.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); + } else + { throw JsonReaderException.Create(this, "Input string '{0}' is not a valid integer.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); + } } numberType = JsonToken.Integer; @@ -1281,9 +1354,13 @@ namespace Newtonsoft.Json decimal value; if (decimal.TryParse(number, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out value)) + { numberValue = value; + } else + { throw JsonReaderException.Create(this, "Input string '{0}' is not a valid decimal.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); + } } numberType = JsonToken.Float; @@ -1328,7 +1405,9 @@ namespace Newtonsoft.Json string number = _stringReference.ToString(); if (number.Length > MaximumJavascriptIntegerCharacterLength) + { throw JsonReaderException.Create(this, "JSON integer {0} is too large to parse.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); + } numberValue = BigIntegerParse(number, CultureInfo.InvariantCulture); numberType = JsonToken.Integer; @@ -1344,17 +1423,25 @@ namespace Newtonsoft.Json { decimal d; if (decimal.TryParse(number, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out d)) + { numberValue = d; + } else + { throw JsonReaderException.Create(this, "Input string '{0}' is not a valid decimal.".FormatWith(CultureInfo.InvariantCulture, number)); + } } else { double d; if (double.TryParse(number, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out d)) + { numberValue = d; + } else + { throw JsonReaderException.Create(this, "Input string '{0}' is not a valid number.".FormatWith(CultureInfo.InvariantCulture, number)); + } } numberType = JsonToken.Float; @@ -1391,11 +1478,17 @@ namespace Newtonsoft.Json bool singlelineComment; if (_chars[_charPos] == '*') + { singlelineComment = false; + } else if (_chars[_charPos] == '/') + { singlelineComment = true; + } else + { throw JsonReaderException.Create(this, "Error parsing comment. Expected: *, got {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos])); + } _charPos++; @@ -1413,7 +1506,9 @@ namespace Newtonsoft.Json if (ReadData(true) == 0) { if (!singlelineComment) + { throw JsonReaderException.Create(this, "Unexpected end while parsing comment."); + } _stringReference = new StringReference(_chars, initialPosition, _charPos - initialPosition); commentFinished = true; @@ -1471,7 +1566,9 @@ namespace Newtonsoft.Json private bool MatchValue(string value) { if (!EnsureChars(value.Length - 1, true)) + { return false; + } for (int i = 0; i < value.Length; i++) { @@ -1492,10 +1589,14 @@ namespace Newtonsoft.Json bool match = MatchValue(value); if (!match) + { return false; + } if (!EnsureChars(0, false)) + { return true; + } return IsSeparator(_chars[_charPos]) || _chars[_charPos] == '\0'; } @@ -1511,14 +1612,18 @@ namespace Newtonsoft.Json case '/': // check next character to see if start of a comment if (!EnsureChars(1, false)) + { return false; + } var nextChart = _chars[_charPos + 1]; return (nextChart == '*' || nextChart == '/'); case ')': if (CurrentState == State.Constructor || CurrentState == State.ConstructorStart) + { return true; + } break; case ' ': case StringUtils.Tab: @@ -1527,7 +1632,9 @@ namespace Newtonsoft.Json return true; default: if (char.IsWhiteSpace(c)) + { return true; + } break; } @@ -1590,7 +1697,9 @@ namespace Newtonsoft.Json if (MatchValueWithTrailingSeparator(JsonConvert.NegativeInfinity)) { if (_floatParseHandling == FloatParseHandling.Decimal) + { throw new JsonReaderException("Cannot read -Infinity as a decimal."); + } SetToken(JsonToken.Float, double.NegativeInfinity); } @@ -1605,7 +1714,9 @@ namespace Newtonsoft.Json if (MatchValueWithTrailingSeparator(JsonConvert.PositiveInfinity)) { if (_floatParseHandling == FloatParseHandling.Decimal) + { throw new JsonReaderException("Cannot read Infinity as a decimal."); + } SetToken(JsonToken.Float, double.PositiveInfinity); } @@ -1620,7 +1731,9 @@ namespace Newtonsoft.Json if (MatchValueWithTrailingSeparator(JsonConvert.NaN)) { if (_floatParseHandling == FloatParseHandling.Decimal) + { throw new JsonReaderException("Cannot read NaN as a decimal."); + } SetToken(JsonToken.Float, double.NaN); } @@ -1676,7 +1789,9 @@ namespace Newtonsoft.Json get { if (CurrentState == State.Start && LinePosition == 0 && TokenType != JsonToken.Comment) + { return 0; + } return _lineNumber; } diff --git a/Src/Newtonsoft.Json/JsonValidatingReader.cs b/Src/Newtonsoft.Json/JsonValidatingReader.cs index 1f444ce1..17d04370 100644 --- a/Src/Newtonsoft.Json/JsonValidatingReader.cs +++ b/Src/Newtonsoft.Json/JsonValidatingReader.cs @@ -417,7 +417,7 @@ namespace Newtonsoft.Json /// /// Reads the next JSON token from the stream as a . /// - /// A . This method will return null at the end of an array. + /// A . This method will return null at the end of an array. public override DateTime? ReadAsDateTime() { DateTime? dateTime = _reader.ReadAsDateTime(); diff --git a/Src/Newtonsoft.Json/Linq/JTokenReader.cs b/Src/Newtonsoft.Json/Linq/JTokenReader.cs index 349ecad2..c8c3dd25 100644 --- a/Src/Newtonsoft.Json/Linq/JTokenReader.cs +++ b/Src/Newtonsoft.Json/Linq/JTokenReader.cs @@ -104,7 +104,7 @@ namespace Newtonsoft.Json.Linq /// /// Reads the next JSON token from the stream as a . /// - /// A . This method will return null at the end of an array. + /// A . This method will return null at the end of an array. public override DateTime? ReadAsDateTime() { return ReadAsDateTimeInternal(); diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 9859c9bc..6ab506de 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -1180,7 +1180,7 @@ namespace Newtonsoft.Json.Serialization contract.ItemContract = GetContractSafe(contract.DictionaryValueType); JsonConverter dictionaryValueConverter = contract.ItemConverter ?? GetConverter(contract.ItemContract, null, contract, containerProperty); - PrimitiveTypeCode keyTypeCode = (contract.KeyContract is JsonPrimitiveContract) ? ((JsonPrimitiveContract)contract.KeyContract).TypeCode : PrimitiveTypeCode.Empty; + PrimitiveTypeCode keyTypeCode = (contract.KeyContract is JsonPrimitiveContract) ? ((JsonPrimitiveContract) contract.KeyContract).TypeCode : PrimitiveTypeCode.Empty; bool finished = false; do @@ -1196,30 +1196,43 @@ namespace Newtonsoft.Json.Serialization { try { - DateParseHandling dateParseHandling; + // this is for correctly reading ISO and MS formatted dictionary keys switch (keyTypeCode) { case PrimitiveTypeCode.DateTime: case PrimitiveTypeCode.DateTimeNullable: - dateParseHandling = DateParseHandling.DateTime; + { + DateTime dt; + if (DateTimeUtils.TryParseDateTime(keyValue.ToString(), reader.DateTimeZoneHandling, reader.DateFormatString, reader.Culture, out dt)) + { + keyValue = dt; + } + else + { + keyValue = EnsureType(reader, keyValue, CultureInfo.InvariantCulture, contract.KeyContract, contract.DictionaryKeyType); + } break; + } #if !NET20 case PrimitiveTypeCode.DateTimeOffset: case PrimitiveTypeCode.DateTimeOffsetNullable: - dateParseHandling = DateParseHandling.DateTimeOffset; + { + DateTimeOffset dt; + if (DateTimeUtils.TryParseDateTimeOffset(keyValue.ToString(), reader.DateFormatString, reader.Culture, out dt)) + { + keyValue = dt; + } + else + { + keyValue = EnsureType(reader, keyValue, CultureInfo.InvariantCulture, contract.KeyContract, contract.DictionaryKeyType); + } break; + } #endif default: - dateParseHandling = DateParseHandling.None; + keyValue = EnsureType(reader, keyValue, CultureInfo.InvariantCulture, contract.KeyContract, contract.DictionaryKeyType); 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) { @@ -1227,7 +1240,9 @@ namespace Newtonsoft.Json.Serialization } if (!ReadForType(reader, contract.ItemContract, dictionaryValueConverter != null)) + { throw JsonSerializationException.Create(reader, "Unexpected end when deserializing object."); + } object itemValue; if (dictionaryValueConverter != null && dictionaryValueConverter.CanRead) diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index fc779989..c011de90 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -980,9 +980,11 @@ namespace Newtonsoft.Json.Serialization JsonPrimitiveContract primitiveContract = (JsonPrimitiveContract)contract; if (primitiveContract.TypeCode == PrimitiveTypeCode.DateTime || primitiveContract.TypeCode == PrimitiveTypeCode.DateTimeNullable) { + DateTime dt = DateTimeUtils.EnsureDateTime((DateTime)name, writer.DateTimeZoneHandling); + escape = false; StringWriter sw = new StringWriter(CultureInfo.InvariantCulture); - DateTimeUtils.WriteDateTimeString(sw, (DateTime)name, writer.DateFormatHandling, writer.DateFormatString, writer.Culture); + DateTimeUtils.WriteDateTimeString(sw, dt, writer.DateFormatHandling, writer.DateFormatString, writer.Culture); return sw.ToString(); } #if !NET20 diff --git a/Src/Newtonsoft.Json/Utilities/DateTimeParser.cs b/Src/Newtonsoft.Json/Utilities/DateTimeParser.cs index 0e506885..0a182694 100644 --- a/Src/Newtonsoft.Json/Utilities/DateTimeParser.cs +++ b/Src/Newtonsoft.Json/Utilities/DateTimeParser.cs @@ -67,8 +67,8 @@ namespace Newtonsoft.Json.Utilities public int ZoneMinute; public ParserTimeZone Zone; - private string _text; - private int _length; + private char[] _text; + private int _end; private static readonly int[] Power10; @@ -88,13 +88,15 @@ namespace Newtonsoft.Json.Utilities private const short MaxFractionDigits = 7; - public bool Parse(string text) + public bool Parse(char[] text, int startIndex, int length) { _text = text; - _length = text.Length; + _end = startIndex + length; - if (ParseDate(0) && ParseChar(Lzyyyy_MM_dd, 'T') && ParseTimeAndZoneAndWhitespace(Lzyyyy_MM_ddT)) + if (ParseDate(startIndex) && ParseChar(Lzyyyy_MM_dd + startIndex, 'T') && ParseTimeAndZoneAndWhitespace(Lzyyyy_MM_ddT + startIndex)) + { return true; + } return false; } @@ -138,11 +140,13 @@ namespace Newtonsoft.Json.Utilities Fraction = 0; int numberOfDigits = 0; - while (++start < _length && numberOfDigits < MaxFractionDigits) + while (++start < _end && numberOfDigits < MaxFractionDigits) { int digit = _text[start] - '0'; if (digit < 0 || digit > 9) + { break; + } Fraction = (Fraction * 10) + digit; @@ -152,7 +156,9 @@ namespace Newtonsoft.Json.Utilities if (numberOfDigits < MaxFractionDigits) { if (numberOfDigits == 0) + { return false; + } Fraction *= Power10[MaxFractionDigits - numberOfDigits]; } @@ -162,7 +168,7 @@ namespace Newtonsoft.Json.Utilities private bool ParseZone(int start) { - if (start < _length) + if (start < _end) { char ch = _text[start]; if (ch == 'Z' || ch == 'z') @@ -172,7 +178,7 @@ namespace Newtonsoft.Json.Utilities } else { - if (start + 2 < _length + if (start + 2 < _end && Parse2Digit(start + Lz_, out ZoneHour) && ZoneHour <= 99) { @@ -190,13 +196,13 @@ namespace Newtonsoft.Json.Utilities } } - if (start < _length) + if (start < _end) { if (ParseChar(start, ':')) { start += 1; - if (start + 1 < _length + if (start + 1 < _end && Parse2Digit(start, out ZoneMinute) && ZoneMinute <= 99) { @@ -205,7 +211,7 @@ namespace Newtonsoft.Json.Utilities } else { - if (start + 1 < _length + if (start + 1 < _end && Parse2Digit(start, out ZoneMinute) && ZoneMinute <= 99) { @@ -216,12 +222,12 @@ namespace Newtonsoft.Json.Utilities } } - return (start == _length); + return (start == _end); } private bool Parse4Digit(int start, out int num) { - if (start + 3 < _length) + if (start + 3 < _end) { int digit1 = _text[start] - '0'; int digit2 = _text[start + 1] - '0'; @@ -242,7 +248,7 @@ namespace Newtonsoft.Json.Utilities private bool Parse2Digit(int start, out int num) { - if (start + 1 < _length) + if (start + 1 < _end) { int digit1 = _text[start] - '0'; int digit2 = _text[start + 1] - '0'; @@ -259,7 +265,7 @@ namespace Newtonsoft.Json.Utilities private bool ParseChar(int start, char ch) { - return (start < _length && _text[start] == ch); + return (start < _end && _text[start] == ch); } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs b/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs index f6f9db51..afe56b35 100644 --- a/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs @@ -33,6 +33,7 @@ namespace Newtonsoft.Json.Utilities internal static class DateTimeUtils { internal static readonly long InitialJavaScriptDateTicks = 621355968000000000; + private const string IsoDateFormat = "yyyy-MM-ddTHH:mm:ss.FFFFFFFK"; private const int DaysPer100Years = 36524; private const int DaysPer400Years = 146097; @@ -131,7 +132,9 @@ namespace Newtonsoft.Json.Utilities private static long ToUniversalTicks(DateTime dateTime) { if (dateTime.Kind == DateTimeKind.Utc) + { return dateTime.Ticks; + } return ToUniversalTicks(dateTime, dateTime.GetUtcOffset()); } @@ -141,14 +144,20 @@ namespace Newtonsoft.Json.Utilities // special case min and max value // they never have a timezone appended to avoid issues if (dateTime.Kind == DateTimeKind.Utc || dateTime == DateTime.MaxValue || dateTime == DateTime.MinValue) + { return dateTime.Ticks; + } long ticks = dateTime.Ticks - offset.Ticks; if (ticks > 3155378975999999999L) + { return 3155378975999999999L; + } if (ticks < 0L) + { return 0L; + } return ticks; } @@ -187,235 +196,393 @@ namespace Newtonsoft.Json.Utilities } #region Parse - internal static bool TryParseDateIso(string text, DateParseHandling dateParseHandling, DateTimeZoneHandling dateTimeZoneHandling, out object dt) + + internal static bool TryParseDateTimeIso(StringReference text, DateTimeZoneHandling dateTimeZoneHandling, out DateTime dt) { DateTimeParser dateTimeParser = new DateTimeParser(); - if (!dateTimeParser.Parse(text)) + if (!dateTimeParser.Parse(text.Chars, text.StartIndex, text.Length)) { - dt = null; + dt = default(DateTime); return false; } DateTime d = new DateTime(dateTimeParser.Year, dateTimeParser.Month, dateTimeParser.Day, dateTimeParser.Hour, dateTimeParser.Minute, dateTimeParser.Second); d = d.AddTicks(dateTimeParser.Fraction); -#if !NET20 - if (dateParseHandling == DateParseHandling.DateTimeOffset) + long ticks; + + switch (dateTimeParser.Zone) { - TimeSpan offset; + case ParserTimeZone.Utc: + d = new DateTime(d.Ticks, DateTimeKind.Utc); + break; - switch (dateTimeParser.Zone) + case ParserTimeZone.LocalWestOfUtc: { - case ParserTimeZone.Utc: - offset = new TimeSpan(0L); - break; - case ParserTimeZone.LocalWestOfUtc: - offset = new TimeSpan(-dateTimeParser.ZoneHour, -dateTimeParser.ZoneMinute, 0); - break; - case ParserTimeZone.LocalEastOfUtc: - offset = new TimeSpan(dateTimeParser.ZoneHour, dateTimeParser.ZoneMinute, 0); - break; - default: - offset = TimeZoneInfo.Local.GetUtcOffset(d); - break; - } - - long ticks = d.Ticks - offset.Ticks; - if (ticks < 0 || ticks > 3155378975999999999) - { - dt = null; - return false; - } - - dt = new DateTimeOffset(d, offset); - return true; - } - else -#endif - { - long ticks; - - switch (dateTimeParser.Zone) - { - case ParserTimeZone.Utc: - d = new DateTime(d.Ticks, DateTimeKind.Utc); - break; - - case ParserTimeZone.LocalWestOfUtc: + TimeSpan offset = new TimeSpan(dateTimeParser.ZoneHour, dateTimeParser.ZoneMinute, 0); + ticks = d.Ticks + offset.Ticks; + if (ticks <= DateTime.MaxValue.Ticks) { - TimeSpan offset = new TimeSpan(dateTimeParser.ZoneHour, dateTimeParser.ZoneMinute, 0); - ticks = d.Ticks + offset.Ticks; - if (ticks <= DateTime.MaxValue.Ticks) - { - d = new DateTime(ticks, DateTimeKind.Utc).ToLocalTime(); - } - else - { - ticks += d.GetUtcOffset().Ticks; - if (ticks > DateTime.MaxValue.Ticks) - ticks = DateTime.MaxValue.Ticks; - - d = new DateTime(ticks, DateTimeKind.Local); - } - break; + d = new DateTime(ticks, DateTimeKind.Utc).ToLocalTime(); } - case ParserTimeZone.LocalEastOfUtc: + else { - TimeSpan offset = new TimeSpan(dateTimeParser.ZoneHour, dateTimeParser.ZoneMinute, 0); - ticks = d.Ticks - offset.Ticks; - if (ticks >= DateTime.MinValue.Ticks) - { - d = new DateTime(ticks, DateTimeKind.Utc).ToLocalTime(); - } - else - { - ticks += d.GetUtcOffset().Ticks; - if (ticks < DateTime.MinValue.Ticks) - ticks = DateTime.MinValue.Ticks; + ticks += d.GetUtcOffset().Ticks; + if (ticks > DateTime.MaxValue.Ticks) + ticks = DateTime.MaxValue.Ticks; - d = new DateTime(ticks, DateTimeKind.Local); - } - break; + d = new DateTime(ticks, DateTimeKind.Local); } + break; } + case ParserTimeZone.LocalEastOfUtc: + { + TimeSpan offset = new TimeSpan(dateTimeParser.ZoneHour, dateTimeParser.ZoneMinute, 0); + ticks = d.Ticks - offset.Ticks; + if (ticks >= DateTime.MinValue.Ticks) + { + d = new DateTime(ticks, DateTimeKind.Utc).ToLocalTime(); + } + else + { + ticks += d.GetUtcOffset().Ticks; + if (ticks < DateTime.MinValue.Ticks) + ticks = DateTime.MinValue.Ticks; - dt = EnsureDateTime(d, dateTimeZoneHandling); - return true; + d = new DateTime(ticks, DateTimeKind.Local); + } + break; + } } + + dt = EnsureDateTime(d, dateTimeZoneHandling); + return true; } - internal static bool TryParseDateTime(string s, DateParseHandling dateParseHandling, DateTimeZoneHandling dateTimeZoneHandling, string dateFormatString, CultureInfo culture, out object dt) +#if !NET20 + internal static bool TryParseDateTimeOffsetIso(StringReference text, out DateTimeOffset dt) + { + DateTimeParser dateTimeParser = new DateTimeParser(); + if (!dateTimeParser.Parse(text.Chars, text.StartIndex, text.Length)) + { + dt = default(DateTimeOffset); + return false; + } + + DateTime d = new DateTime(dateTimeParser.Year, dateTimeParser.Month, dateTimeParser.Day, dateTimeParser.Hour, dateTimeParser.Minute, dateTimeParser.Second); + d = d.AddTicks(dateTimeParser.Fraction); + + TimeSpan offset; + + switch (dateTimeParser.Zone) + { + case ParserTimeZone.Utc: + offset = new TimeSpan(0L); + break; + case ParserTimeZone.LocalWestOfUtc: + offset = new TimeSpan(-dateTimeParser.ZoneHour, -dateTimeParser.ZoneMinute, 0); + break; + case ParserTimeZone.LocalEastOfUtc: + offset = new TimeSpan(dateTimeParser.ZoneHour, dateTimeParser.ZoneMinute, 0); + break; + default: + offset = TimeZoneInfo.Local.GetUtcOffset(d); + break; + } + + long ticks = d.Ticks - offset.Ticks; + if (ticks < 0 || ticks > 3155378975999999999) + { + dt = default(DateTimeOffset); + return false; + } + + dt = new DateTimeOffset(d, offset); + return true; + } +#endif + + internal static bool TryParseDateTime(StringReference s, DateTimeZoneHandling dateTimeZoneHandling, string dateFormatString, CultureInfo culture, out DateTime dt) + { + if (s.Length > 0) + { + int i = s.StartIndex; + if (s[i] == '/') + { + if (s.Length >= 9 && s.StartsWith("/Date(") && s.EndsWith(")/")) + { + if (TryParseDateTimeMicrosoft(s, dateTimeZoneHandling, out dt)) + { + return true; + } + } + } + else if (s.Length >= 19 && s.Length <= 40 && char.IsDigit(s[i]) && s[i + 10] == 'T') + { + if (TryParseDateTimeIso(s, dateTimeZoneHandling, out dt)) + { + return true; + } + } + + if (!string.IsNullOrEmpty(dateFormatString)) + { + if (TryParseDateTimeExact(s.ToString(), dateTimeZoneHandling, dateFormatString, culture, out dt)) + { + return true; + } + } + } + + dt = default(DateTime); + return false; + } + + internal static bool TryParseDateTime(string s, DateTimeZoneHandling dateTimeZoneHandling, string dateFormatString, CultureInfo culture, out DateTime dt) { if (s.Length > 0) { if (s[0] == '/') { - if (s.StartsWith("/Date(", StringComparison.Ordinal) && s.EndsWith(")/", StringComparison.Ordinal)) + if (s.Length >= 9 && s.StartsWith("/Date(", StringComparison.Ordinal) && s.EndsWith(")/", StringComparison.Ordinal)) { - if (TryParseDateMicrosoft(s, dateParseHandling, dateTimeZoneHandling, out dt)) + if (TryParseDateTimeMicrosoft(new StringReference(s.ToCharArray(), 0, s.Length), dateTimeZoneHandling, out dt)) + { return true; + } } } else if (s.Length >= 19 && s.Length <= 40 && char.IsDigit(s[0]) && s[10] == 'T') { - if (TryParseDateIso(s, dateParseHandling, dateTimeZoneHandling, out dt)) + if (DateTime.TryParseExact(s, IsoDateFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dt)) + { + dt = EnsureDateTime(dt, dateTimeZoneHandling); return true; + } } if (!string.IsNullOrEmpty(dateFormatString)) { - if (TryParseDateExact(s, dateParseHandling, dateTimeZoneHandling, dateFormatString, culture, out dt)) + if (TryParseDateTimeExact(s, dateTimeZoneHandling, dateFormatString, culture, out dt)) + { return true; + } } } - dt = null; + dt = default(DateTime); return false; } - private static bool TryParseDateMicrosoft(string text, DateParseHandling dateParseHandling, DateTimeZoneHandling dateTimeZoneHandling, out object dt) +#if !NET20 + internal static bool TryParseDateTimeOffset(StringReference s, string dateFormatString, CultureInfo culture, out DateTimeOffset dt) { - string value = text.Substring(6, text.Length - 8); - DateTimeKind kind = DateTimeKind.Utc; + if (s.Length > 0) + { + int i = s.StartIndex; + if (s[i] == '/') + { + if (s.Length >= 9 && s.StartsWith("/Date(") && s.EndsWith(")/")) + { + if (TryParseDateTimeOffsetMicrosoft(s, out dt)) + { + return true; + } + } + } + else if (s.Length >= 19 && s.Length <= 40 && char.IsDigit(s[i]) && s[i + 10] == 'T') + { + if (TryParseDateTimeOffsetIso(s, out dt)) + { + return true; + } + } - int index = value.IndexOf('+', 1); + if (!string.IsNullOrEmpty(dateFormatString)) + { + if (TryParseDateTimeOffsetExact(s.ToString(), dateFormatString, culture, out dt)) + { + return true; + } + } + } + + dt = default(DateTimeOffset); + return false; + } + + internal static bool TryParseDateTimeOffset(string s, string dateFormatString, CultureInfo culture, out DateTimeOffset dt) + { + if (s.Length > 0) + { + if (s[0] == '/') + { + if (s.Length >= 9 && s.StartsWith("/Date(", StringComparison.Ordinal) && s.EndsWith(")/", StringComparison.Ordinal)) + { + if (TryParseDateTimeOffsetMicrosoft(new StringReference(s.ToCharArray(), 0, s.Length), out dt)) + { + return true; + } + } + } + else if (s.Length >= 19 && s.Length <= 40 && char.IsDigit(s[0]) && s[10] == 'T') + { + if (DateTimeOffset.TryParseExact(s, IsoDateFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dt)) + if (TryParseDateTimeOffsetIso(new StringReference(s.ToCharArray(), 0, s.Length), out dt)) + { + return true; + } + } + + if (!string.IsNullOrEmpty(dateFormatString)) + { + if (TryParseDateTimeOffsetExact(s, dateFormatString, culture, out dt)) + { + return true; + } + } + } + + dt = default(DateTimeOffset); + return false; + } +#endif + + private static bool TryParseMicrosoftDate(StringReference text, out long ticks, out TimeSpan offset, out DateTimeKind kind) + { + kind = DateTimeKind.Utc; + + int index = text.IndexOf('+', 7, text.Length - 8); if (index == -1) - index = value.IndexOf('-', 1); - -#if !NET20 - TimeSpan offset = TimeSpan.Zero; -#endif + index = text.IndexOf('-', 7, text.Length - 8); if (index != -1) { kind = DateTimeKind.Local; -#if !NET20 - offset = ReadOffset(value.Substring(index)); -#endif - value = value.Substring(0, index); - } - long javaScriptTicks; - if (!long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out javaScriptTicks)) - { - dt = null; - return false; - } - - DateTime utcDateTime = ConvertJavaScriptTicksToDateTime(javaScriptTicks); - -#if !NET20 - if (dateParseHandling == DateParseHandling.DateTimeOffset) - { - dt = new DateTimeOffset(utcDateTime.Add(offset).Ticks, offset); - return true; - } -#endif - - DateTime dateTime; - switch (kind) - { - case DateTimeKind.Unspecified: - dateTime = DateTime.SpecifyKind(utcDateTime.ToLocalTime(), DateTimeKind.Unspecified); - break; - case DateTimeKind.Local: - dateTime = utcDateTime.ToLocalTime(); - break; - default: - dateTime = utcDateTime; - break; - } - - dt = EnsureDateTime(dateTime, dateTimeZoneHandling); - return true; - } - - 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)) + if (!TryReadOffset(text, index, out offset)) { - dt = temp; - return true; + ticks = 0; + return false; } } else -#endif { - DateTime temp; - if (DateTime.TryParseExact(text, dateFormatString, culture, DateTimeStyles.RoundtripKind, out temp)) - { - temp = EnsureDateTime(temp, dateTimeZoneHandling); - dt = temp; - return true; - } + offset = TimeSpan.Zero; + index = text.Length - 2; } - dt = null; + return (ConvertUtils.Int64TryParse(text.Chars, 6, index - 6, out ticks) == ParseResult.Success); + } + + private static bool TryParseDateTimeMicrosoft(StringReference text, DateTimeZoneHandling dateTimeZoneHandling, out DateTime dt) + { + long ticks; + TimeSpan offset; + DateTimeKind kind; + + if (!TryParseMicrosoftDate(text, out ticks, out offset, out kind)) + { + dt = default(DateTime); + return false; + } + + DateTime utcDateTime = ConvertJavaScriptTicksToDateTime(ticks); + + switch (kind) + { + case DateTimeKind.Unspecified: + dt = DateTime.SpecifyKind(utcDateTime.ToLocalTime(), DateTimeKind.Unspecified); + break; + case DateTimeKind.Local: + dt = utcDateTime.ToLocalTime(); + break; + default: + dt = utcDateTime; + break; + } + + dt = EnsureDateTime(dt, dateTimeZoneHandling); + return true; + } + + private static bool TryParseDateTimeExact(string text, DateTimeZoneHandling dateTimeZoneHandling, string dateFormatString, CultureInfo culture, out DateTime dt) + { + DateTime temp; + if (DateTime.TryParseExact(text, dateFormatString, culture, DateTimeStyles.RoundtripKind, out temp)) + { + temp = EnsureDateTime(temp, dateTimeZoneHandling); + dt = temp; + return true; + } + + dt = default(DateTime); return false; } #if !NET20 - private static TimeSpan ReadOffset(string offsetText) + private static bool TryParseDateTimeOffsetMicrosoft(StringReference text, out DateTimeOffset dt) { - bool negative = (offsetText[0] == '-'); + long ticks; + TimeSpan offset; + DateTimeKind kind; - int hours = int.Parse(offsetText.Substring(1, 2), NumberStyles.Integer, CultureInfo.InvariantCulture); - int minutes = 0; - if (offsetText.Length >= 5) - minutes = int.Parse(offsetText.Substring(3, 2), NumberStyles.Integer, CultureInfo.InvariantCulture); + if (!TryParseMicrosoftDate(text, out ticks, out offset, out kind)) + { + dt = default(DateTime); + return false; + } - TimeSpan offset = TimeSpan.FromHours(hours) + TimeSpan.FromMinutes(minutes); - if (negative) - offset = offset.Negate(); + DateTime utcDateTime = ConvertJavaScriptTicksToDateTime(ticks); - return offset; + dt = new DateTimeOffset(utcDateTime.Add(offset).Ticks, offset); + return true; + } + + private static bool TryParseDateTimeOffsetExact(string text, string dateFormatString, CultureInfo culture, out DateTimeOffset dt) + { + DateTimeOffset temp; + if (DateTimeOffset.TryParseExact(text, dateFormatString, culture, DateTimeStyles.RoundtripKind, out temp)) + { + dt = temp; + return true; + } + + dt = default(DateTimeOffset); + return false; } #endif + + private static bool TryReadOffset(StringReference offsetText, int startIndex, out TimeSpan offset) + { + bool negative = (offsetText[startIndex] == '-'); + + int hours; + if (ConvertUtils.Int32TryParse(offsetText.Chars, startIndex + 1, 2, out hours) != ParseResult.Success) + { + offset = default(TimeSpan); + return false; + } + + int minutes = 0; + if (offsetText.Length - startIndex > 5) + { + if (ConvertUtils.Int32TryParse(offsetText.Chars, startIndex + 3, 2, out minutes) != ParseResult.Success) + { + offset = default(TimeSpan); + return false; + } + } + + offset = TimeSpan.FromHours(hours) + TimeSpan.FromMinutes(minutes); + if (negative) + { + offset = offset.Negate(); + } + + return true; + } #endregion #region Write diff --git a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs index c1eaebb0..9aaacb2a 100644 --- a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs @@ -48,7 +48,8 @@ namespace Newtonsoft.Json.Utilities return new char[minSize]; } - return bufferPool.RentBuffer(minSize); + char[] buffer = bufferPool.RentBuffer(minSize); + return buffer; } public static void ReturnBuffer(IJsonBufferPool bufferPool, ref char[] buffer) @@ -236,7 +237,9 @@ namespace Newtonsoft.Json.Utilities // the unicode text is already in the buffer // copy it over when creating new buffer if (isEscapedUnicodeText) + { Array.Copy(writeBuffer, newBuffer, UnicodeTextLength); + } BufferUtils.ReturnBuffer(bufferPool, ref writeBuffer); diff --git a/Src/Newtonsoft.Json/Utilities/StringReference.cs b/Src/Newtonsoft.Json/Utilities/StringReference.cs index 0feaa490..d1e8c474 100644 --- a/Src/Newtonsoft.Json/Utilities/StringReference.cs +++ b/Src/Newtonsoft.Json/Utilities/StringReference.cs @@ -23,6 +23,8 @@ // OTHER DEALINGS IN THE SOFTWARE. #endregion +using System; + namespace Newtonsoft.Json.Utilities { internal struct StringReference @@ -31,6 +33,11 @@ namespace Newtonsoft.Json.Utilities private readonly int _startIndex; private readonly int _length; + public char this[int i] + { + get { return _chars[i]; } + } + public char[] Chars { get { return _chars; } @@ -58,4 +65,64 @@ namespace Newtonsoft.Json.Utilities return new string(_chars, _startIndex, _length); } } + + internal static class StringReferenceExtensions + { + public static int IndexOf(this StringReference s, char c) + { + return IndexOf(s, c, 0, 0); + } + + public static int IndexOf(this StringReference s, char c, int startIndex, int length) + { + int index = Array.IndexOf(s.Chars, c, s.StartIndex + startIndex, length); + if (index == -1) + { + return -1; + } + + return index - s.StartIndex; + } + + public static bool StartsWith(this StringReference s, string text) + { + if (text.Length > s.Length) + { + return false; + } + + char[] chars = s.Chars; + + for (int i = 0; i < text.Length; i++) + { + if (text[i] != chars[i + s.StartIndex]) + { + return false; + } + } + + return true; + } + + public static bool EndsWith(this StringReference s, string text) + { + if (text.Length > s.Length) + { + return false; + } + + char[] chars = s.Chars; + + int start = s.StartIndex + s.Length - text.Length; + for (int i = 0; i < text.Length; i++) + { + if (text[i] != chars[i + start]) + { + return false; + } + } + + return true; + } + } } \ No newline at end of file