diff --git a/.gitignore b/.gitignore index 810fdf33a..dbcfb2101 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ BenchmarkDotNet.Artifacts/ _ReSharper.* *.ReSharper.user *.resharper.user +*/.idea/* .vs/ .vscode/ *.lock.json diff --git a/Src/Newtonsoft.Json.Tests/Benchmarks/DeserializeBenchmarks.cs b/Src/Newtonsoft.Json.Tests/Benchmarks/DeserializeBenchmarks.cs index 289cc13fd..431b2fa65 100644 --- a/Src/Newtonsoft.Json.Tests/Benchmarks/DeserializeBenchmarks.cs +++ b/Src/Newtonsoft.Json.Tests/Benchmarks/DeserializeBenchmarks.cs @@ -27,14 +27,13 @@ using System; using System.Collections.Generic; +using System.Globalization; +using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Xml; using BenchmarkDotNet.Attributes; using Newtonsoft.Json.Linq; -using Newtonsoft.Json; using Newtonsoft.Json.Tests.TestObjects; +using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json.Tests.Benchmarks { @@ -42,12 +41,29 @@ public class DeserializeBenchmarks { private static readonly string LargeJsonText; private static readonly string FloatArrayJson; + private static readonly string DateArrayJson; + private static readonly StringReference[] DateTimeStringReferences; + private static readonly DateTimeOffset[] ParsedDateTimesArray; + private static readonly JsonSerializer Serializer = new(); static DeserializeBenchmarks() { LargeJsonText = System.IO.File.ReadAllText(TestFixtureBase.ResolvePath("large.json")); - FloatArrayJson = new JArray(Enumerable.Range(0, 5000).Select(i => i * 1.1m)).ToString(Formatting.None); + const int count = 5000; + FloatArrayJson = new JArray(Enumerable.Range(0, count).Select(i => i * 1.1m)).ToString(Formatting.None); + var dates = new DateTime[count]; + DateTimeStringReferences = new StringReference[count]; + ParsedDateTimesArray = new DateTimeOffset[count]; + DateTime time = new(1969, 7, 20, 2, 56, 15, DateTimeKind.Utc); + for (int i = 0; i < count; i++) + { + DateTime dateTime = time.AddDays(i); + dates[i] = dateTime; + string dateTimeString = dateTime.ToString("O", CultureInfo.InvariantCulture); + DateTimeStringReferences[i] = new StringReference(dateTimeString.ToCharArray(), 0, dateTimeString.Length); + } + DateArrayJson = new JArray(dates).ToString(Formatting.None); } [Benchmark] @@ -59,11 +75,10 @@ public IList DeserializeLargeJsonText() [Benchmark] public IList DeserializeLargeJsonFile() { - using (var jsonFile = System.IO.File.OpenText(TestFixtureBase.ResolvePath("large.json"))) + using StringReader jsonFile = new(LargeJsonText); using (JsonTextReader jsonTextReader = new JsonTextReader(jsonFile)) { - JsonSerializer serializer = new JsonSerializer(); - return serializer.Deserialize>(jsonTextReader); + return Serializer.Deserialize>(jsonTextReader); } } @@ -78,6 +93,24 @@ public IList DeserializeDecimalList() { return JsonConvert.DeserializeObject>(FloatArrayJson); } + + [Benchmark] + public IList DeserializeDateTimeList() + { + return JsonConvert.DeserializeObject>(DateArrayJson); + } + + [Benchmark] + public bool TryParseDateTimeOffsetIso() + { + bool success = true; + for (var index = 0; index < DateTimeStringReferences.Length; index++) + { + success &= DateTimeUtils.TryParseDateTimeOffsetIso(DateTimeStringReferences[index], out DateTimeOffset result); + ParsedDateTimesArray[index] = result; + } + return success; + } } } diff --git a/Src/Newtonsoft.Json.Tests/Benchmarks/SerializeBenchmarks.cs b/Src/Newtonsoft.Json.Tests/Benchmarks/SerializeBenchmarks.cs index c086aebb3..c4c37386e 100644 --- a/Src/Newtonsoft.Json.Tests/Benchmarks/SerializeBenchmarks.cs +++ b/Src/Newtonsoft.Json.Tests/Benchmarks/SerializeBenchmarks.cs @@ -25,16 +25,9 @@ #if HAVE_BENCHMARKS -using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Xml; using BenchmarkDotNet.Attributes; -using Newtonsoft.Json.Linq; -using Newtonsoft.Json; using Newtonsoft.Json.Tests.TestObjects; namespace Newtonsoft.Json.Tests.Benchmarks @@ -42,6 +35,9 @@ namespace Newtonsoft.Json.Tests.Benchmarks public class SerializeBenchmarks { private static readonly IList LargeCollection; + private static readonly MemoryStream ReusedMemoryStream = new(); + private static readonly StreamWriter JsonFile = new(ReusedMemoryStream); + private static readonly JsonSerializer Serializer = new() { Formatting = Formatting.Indented }; static SerializeBenchmarks() { @@ -53,12 +49,8 @@ static SerializeBenchmarks() [Benchmark] public void SerializeLargeJsonFile() { - using (StreamWriter file = System.IO.File.CreateText(TestFixtureBase.ResolvePath("largewrite.json"))) - { - JsonSerializer serializer = new JsonSerializer(); - serializer.Formatting = Formatting.Indented; - serializer.Serialize(file, LargeCollection); - } + ReusedMemoryStream.Seek(0, SeekOrigin.Begin); + Serializer.Serialize(JsonFile, LargeCollection); } } } diff --git a/Src/Newtonsoft.Json.Tests/Issues/Issue2768.cs b/Src/Newtonsoft.Json.Tests/Issues/Issue2768.cs index 6681c2a73..4580ffa6b 100644 --- a/Src/Newtonsoft.Json.Tests/Issues/Issue2768.cs +++ b/Src/Newtonsoft.Json.Tests/Issues/Issue2768.cs @@ -71,7 +71,7 @@ public void Test_Deserialize() { decimal d = JsonConvert.DeserializeObject("0.0"); - Assert.AreEqual("0.0", d.ToString()); + Assert.AreEqual("0.0", d.ToString(CultureInfo.InvariantCulture)); } [Test] @@ -79,7 +79,7 @@ public void Test_Deserialize_Negative() { decimal d = JsonConvert.DeserializeObject("-0.0"); - Assert.AreEqual("0.0", d.ToString()); + Assert.AreEqual("0.0", d.ToString(CultureInfo.InvariantCulture)); } [Test] @@ -95,7 +95,7 @@ public void Test_Deserialize_MultipleTrailingZeroes() { decimal d = JsonConvert.DeserializeObject("0.00"); - Assert.AreEqual("0.00", d.ToString()); + Assert.AreEqual("0.00", d.ToString(CultureInfo.InvariantCulture)); } [Test] @@ -127,7 +127,7 @@ public void ParseJsonDecimal() } } - Assert.AreEqual("0.0", parsedValue.ToString()); + Assert.AreEqual("0.0", parsedValue?.ToString(CultureInfo.InvariantCulture)); } [Test] diff --git a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs index 2611929a2..655066445 100644 --- a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs @@ -778,7 +778,7 @@ public void WriteDateTime() Assert.AreEqual(@"\/Date(253402300799999)\/", result.MsDateUtc); DateTime year2000local = new DateTime(2000, 1, 1, 1, 1, 1, DateTimeKind.Local); - string localToUtcDate = year2000local.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFFK"); + string localToUtcDate = year2000local.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFFK", CultureInfo.InvariantCulture); result = TestDateTime("DateTime Local", year2000local); Assert.AreEqual("2000-01-01T01:01:01" + GetOffset(year2000local, DateFormatHandling.IsoDateFormat), result.IsoDateRoundtrip); @@ -791,7 +791,7 @@ public void WriteDateTime() Assert.AreEqual(@"\/Date(" + DateTimeUtils.ConvertDateTimeToJavaScriptTicks(year2000local) + @")\/", result.MsDateUtc); DateTime millisecondsLocal = new DateTime(2000, 1, 1, 1, 1, 1, 999, DateTimeKind.Local); - localToUtcDate = millisecondsLocal.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFFK"); + localToUtcDate = millisecondsLocal.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFFK", CultureInfo.InvariantCulture); result = TestDateTime("DateTime Local with milliseconds", millisecondsLocal); Assert.AreEqual("2000-01-01T01:01:01.999" + GetOffset(millisecondsLocal, DateFormatHandling.IsoDateFormat), result.IsoDateRoundtrip); @@ -804,7 +804,7 @@ public void WriteDateTime() Assert.AreEqual(@"\/Date(" + DateTimeUtils.ConvertDateTimeToJavaScriptTicks(millisecondsLocal) + @")\/", result.MsDateUtc); DateTime ticksLocal = new DateTime(636556897826822481, DateTimeKind.Local); - localToUtcDate = ticksLocal.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFFK"); + localToUtcDate = ticksLocal.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFFK", CultureInfo.InvariantCulture); result = TestDateTime("DateTime Local with ticks", ticksLocal); Assert.AreEqual("2018-03-03T16:03:02.6822481" + GetOffset(ticksLocal, DateFormatHandling.IsoDateFormat), result.IsoDateRoundtrip); @@ -829,7 +829,7 @@ public void WriteDateTime() Assert.AreEqual(@"\/Date(" + DateTimeUtils.ConvertDateTimeToJavaScriptTicks(year2000Unspecified.ToLocalTime()) + @")\/", result.MsDateUtc); DateTime year2000Utc = new DateTime(2000, 1, 1, 1, 1, 1, DateTimeKind.Utc); - string utcTolocalDate = year2000Utc.ToLocalTime().ToString("yyyy-MM-ddTHH:mm:ss"); + string utcTolocalDate = year2000Utc.ToLocalTime().ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture); result = TestDateTime("DateTime Utc", year2000Utc); Assert.AreEqual("2000-01-01T01:01:01Z", result.IsoDateRoundtrip); @@ -842,7 +842,7 @@ public void WriteDateTime() Assert.AreEqual(@"\/Date(946688461000)\/", result.MsDateUtc); DateTime unixEpoc = new DateTime(621355968000000000, DateTimeKind.Utc); - utcTolocalDate = unixEpoc.ToLocalTime().ToString("yyyy-MM-ddTHH:mm:ss"); + utcTolocalDate = unixEpoc.ToLocalTime().ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture); result = TestDateTime("DateTime Unix Epoc", unixEpoc); Assert.AreEqual("1970-01-01T00:00:00Z", result.IsoDateRoundtrip); diff --git a/Src/Newtonsoft.Json/Utilities/DateTimeParser.cs b/Src/Newtonsoft.Json/Utilities/DateTimeParser.cs index 9addb98eb..3dea30913 100644 --- a/Src/Newtonsoft.Json/Utilities/DateTimeParser.cs +++ b/Src/Newtonsoft.Json/Utilities/DateTimeParser.cs @@ -24,10 +24,11 @@ #endregion using System; +using System.Runtime.InteropServices; namespace Newtonsoft.Json.Utilities { - internal enum ParserTimeZone + internal enum ParserTimeZone : byte { Unspecified = 0, Utc = 1, @@ -35,6 +36,7 @@ internal enum ParserTimeZone LocalEastOfUtc = 3 } + [StructLayout(LayoutKind.Auto)] internal struct DateTimeParser { static DateTimeParser() @@ -56,15 +58,15 @@ static DateTimeParser() Lz_zz = "-zz".Length; } - public int Year; - public int Month; - public int Day; - public int Hour; - public int Minute; - public int Second; + public ushort Year; + public byte Month; + public byte Day; + public byte Hour; + public byte Minute; + public byte Second; public int Fraction; - public int ZoneHour; - public int ZoneMinute; + public byte ZoneHour; + public byte ZoneMinute; public ParserTimeZone Zone; private char[] _text; @@ -130,7 +132,7 @@ private bool ParseTime(ref int start) && ParseChar(start + LzHH_mm, ':') && Parse2Digit(start + LzHH_mm_, out Second) && Second < 60 - && (Hour != 24 || (Minute == 0 && Second == 0)))) // hour can be 24 if minute/second is zero) + && ((Minute == 0 && Second == 0) || Hour != 24))) // hour can be 24 if minute/second is zero) { return false; } @@ -231,7 +233,7 @@ private bool ParseZone(int start) return (start == _end); } - private bool Parse4Digit(int start, out int num) + private bool Parse4Digit(int start, out ushort num) { if (start + 3 < _end) { @@ -244,7 +246,7 @@ private bool Parse4Digit(int start, out int num) && 0 <= digit3 && digit3 < 10 && 0 <= digit4 && digit4 < 10) { - num = (((((digit1 * 10) + digit2) * 10) + digit3) * 10) + digit4; + num = (ushort)((((((digit1 * 10) + digit2) * 10) + digit3) * 10) + digit4); return true; } } @@ -252,16 +254,17 @@ private bool Parse4Digit(int start, out int num) return false; } - private bool Parse2Digit(int start, out int num) + private bool Parse2Digit(int start, out byte num) { - if (start + 1 < _end) + int end = start + 1; + if (end < _end) { int digit1 = _text[start] - '0'; - int digit2 = _text[start + 1] - '0'; + int digit2 = _text[end] - '0'; if (0 <= digit1 && digit1 < 10 && 0 <= digit2 && digit2 < 10) { - num = (digit1 * 10) + digit2; + num = (byte)((digit1 * 10) + digit2); return true; } }