diff --git a/Src/Newtonsoft.Json.Tests/Converters/JavaScriptDateTimeConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/JavaScriptDateTimeConverterTests.cs index 0111607f..c13dd0bd 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/JavaScriptDateTimeConverterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/JavaScriptDateTimeConverterTests.cs @@ -60,6 +60,13 @@ namespace Newtonsoft.Json.Tests.Converters Assert.AreEqual("new Date(976918263055)", result); } + [Test] + public void sdfs() + { + int i = Convert.ToInt32("+1"); + Console.WriteLine(i); + } + [Test] public void SerializeNullableDateTimeClass() { diff --git a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs index 2d2dc090..6a57fa6c 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs @@ -692,5 +692,95 @@ bye", reader.Value); Assert.IsTrue(reader.Read()); Assert.AreEqual(JsonToken.EndObject, reader.TokenType); } + +#if !NET20 + [Test] + public void ReadAsDateTimeOffset() + { + string json = "{\"Offset\":\"\\/Date(946663200000+0600)\\/\"}"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartObject, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); + + reader.ReadAsDateTimeOffset(); + Assert.AreEqual(JsonToken.Date, reader.TokenType); + Assert.AreEqual(typeof(DateTimeOffset), reader.ValueType); + Assert.AreEqual(new DateTimeOffset(new DateTime(2000, 1, 1), TimeSpan.FromHours(6)), reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndObject, reader.TokenType); + } + + [Test] + public void ReadAsDateTimeOffsetNegative() + { + string json = @"{""Offset"":""\/Date(946706400000-0600)\/""}"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartObject, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); + + reader.ReadAsDateTimeOffset(); + Assert.AreEqual(JsonToken.Date, reader.TokenType); + Assert.AreEqual(typeof(DateTimeOffset), reader.ValueType); + Assert.AreEqual(new DateTimeOffset(new DateTime(2000, 1, 1), TimeSpan.FromHours(-6)), reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndObject, reader.TokenType); + } + + [Test] + public void ReadAsDateTimeOffsetHoursOnly() + { + string json = "{\"Offset\":\"\\/Date(946663200000+06)\\/\"}"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartObject, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); + + reader.ReadAsDateTimeOffset(); + Assert.AreEqual(JsonToken.Date, reader.TokenType); + Assert.AreEqual(typeof(DateTimeOffset), reader.ValueType); + Assert.AreEqual(new DateTimeOffset(new DateTime(2000, 1, 1), TimeSpan.FromHours(6)), reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndObject, reader.TokenType); + } + + [Test] + public void ReadAsDateTimeOffsetWithMinutes() + { + string json = @"{""Offset"":""\/Date(946708260000-0631)\/""}"; + + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.StartObject, reader.TokenType); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.PropertyName, reader.TokenType); + + reader.ReadAsDateTimeOffset(); + Assert.AreEqual(JsonToken.Date, reader.TokenType); + Assert.AreEqual(typeof(DateTimeOffset), reader.ValueType); + Assert.AreEqual(new DateTimeOffset(new DateTime(2000, 1, 1), TimeSpan.FromHours(-6).Add(TimeSpan.FromMinutes(-31))), reader.Value); + + Assert.IsTrue(reader.Read()); + Assert.AreEqual(JsonToken.EndObject, reader.TokenType); + } +#endif } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 32b4e070..25acb198 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -3539,24 +3539,26 @@ keyword such as type of business."" Assert.AreEqual(int.MaxValue, newStruct.MyInt); } - //public class TimeZoneOffsetObject - //{ - // public DateTimeOffset Offset { get; set; } - //} + public class TimeZoneOffsetObject + { + public DateTimeOffset Offset { get; set; } + } - //[Test] - //public void ReadWriteTimeZoneOffset() - //{ - // var serializeObject = JsonConvert.SerializeObject(new TimeZoneOffsetObject - // { - // Offset = new DateTimeOffset(new DateTime(2000, 1, 1), TimeSpan.FromHours(6)) - // }); +#if !NET20 + [Test] + public void ReadWriteTimeZoneOffset() + { + var serializeObject = JsonConvert.SerializeObject(new TimeZoneOffsetObject + { + Offset = new DateTimeOffset(new DateTime(2000, 1, 1), TimeSpan.FromHours(6)) + }); - // Assert.AreEqual("{\"Offset\":\"\\/Date(946663200000+0600)\\/\"}", serializeObject); - // var deserializeObject = JsonConvert.DeserializeObject(serializeObject); - // Assert.AreEqual(TimeSpan.FromHours(6), deserializeObject.Offset.Offset); // fails here - // Assert.AreEqual(new DateTime(2000, 1, 1), deserializeObject.Offset.Date); - //} + Assert.AreEqual("{\"Offset\":\"\\/Date(946663200000+0600)\\/\"}", serializeObject); + var deserializeObject = JsonConvert.DeserializeObject(serializeObject); + Assert.AreEqual(TimeSpan.FromHours(6), deserializeObject.Offset.Offset); + Assert.AreEqual(new DateTime(2000, 1, 1), deserializeObject.Offset.Date); + } +#endif public abstract class LogEvent { diff --git a/Src/Newtonsoft.Json/Bson/BsonReader.cs b/Src/Newtonsoft.Json/Bson/BsonReader.cs index 4b943571..8db767ad 100644 --- a/Src/Newtonsoft.Json/Bson/BsonReader.cs +++ b/Src/Newtonsoft.Json/Bson/BsonReader.cs @@ -150,6 +150,23 @@ namespace Newtonsoft.Json.Bson return (byte[])Value; } +#if !NET20 + /// + /// Reads the next JSON token from the stream as a . + /// + /// + /// A . + /// + public override DateTimeOffset ReadAsDateTimeOffset() + { + Read(); + if (TokenType != JsonToken.Date) + throw new JsonReaderException("Error reading date. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + + return new DateTimeOffset((DateTime)Value); + } +#endif + /// /// Reads the next JSON token from the stream. /// diff --git a/Src/Newtonsoft.Json/JsonReader.cs b/Src/Newtonsoft.Json/JsonReader.cs index d5165785..2ec28834 100644 --- a/Src/Newtonsoft.Json/JsonReader.cs +++ b/Src/Newtonsoft.Json/JsonReader.cs @@ -210,6 +210,14 @@ namespace Newtonsoft.Json /// A or a null reference if the next JSON token is null. public abstract byte[] ReadAsBytes(); +#if !NET20 + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . + public abstract DateTimeOffset ReadAsDateTimeOffset(); +#endif + /// /// Skips the children of the current token. /// diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index 9121732d..7b45688f 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -41,7 +41,10 @@ namespace Newtonsoft.Json private enum ReadType { Read, - ReadAsBytes + ReadAsBytes, +#if !NET20 + ReadAsDateTimeOffset +#endif } private readonly TextReader _reader; @@ -190,6 +193,22 @@ namespace Newtonsoft.Json return new JsonReaderException(message, null, _currentLineNumber, _currentLinePosition); } + private TimeSpan ReadOffset(string offsetText) + { + bool negative = (offsetText[0] == '-'); + + 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); + + TimeSpan offset = TimeSpan.FromHours(hours) + TimeSpan.FromMinutes(minutes); + if (negative) + offset = offset.Negate(); + + return offset; + } + private void ParseDate(string text) { string value = text.Substring(6, text.Length - 8); @@ -200,30 +219,44 @@ namespace Newtonsoft.Json if (index == -1) index = value.IndexOf('-', 1); + TimeSpan offset = TimeSpan.Zero; + if (index != -1) { kind = DateTimeKind.Local; + offset = ReadOffset(value.Substring(index)); value = value.Substring(0, index); } long javaScriptTicks = long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + DateTime utcDateTime = JsonConvert.ConvertJavaScriptTicksToDateTime(javaScriptTicks); - DateTime dateTime; - switch (kind) +#if !NET20 + if (_readType == ReadType.ReadAsDateTimeOffset) { - case DateTimeKind.Unspecified: - dateTime = DateTime.SpecifyKind(utcDateTime.ToLocalTime(), DateTimeKind.Unspecified); - break; - case DateTimeKind.Local: - dateTime = utcDateTime.ToLocalTime(); - break; - default: - dateTime = utcDateTime; - break; + SetToken(JsonToken.Date, new DateTimeOffset(utcDateTime.Add(offset).Ticks, offset)); } + else +#endif + { + DateTime dateTime; - SetToken(JsonToken.Date, 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; + } + + SetToken(JsonToken.Date, dateTime); + } } private const int LineFeedValue = StringUtils.LineFeed; @@ -299,6 +332,24 @@ namespace Newtonsoft.Json throw CreateJsonReaderException("Unexpected token when reading bytes: {0}. Line {1}, position {2}.", TokenType, _currentLineNumber, _currentLinePosition); } +#if !NET20 + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . + public override DateTimeOffset ReadAsDateTimeOffset() + { + _readType = ReadType.ReadAsDateTimeOffset; + if (!ReadInternal()) + throw CreateJsonReaderException("Unexpected end when reading date: Line {0}, position {1}.", _currentLineNumber, _currentLinePosition); + + if (TokenType == JsonToken.Date) + return (DateTimeOffset)Value; + + throw CreateJsonReaderException("Unexpected token when reading date: {0}. Line {1}, position {2}.", TokenType, _currentLineNumber, _currentLinePosition); + } +#endif + private bool ReadInternal() { while (true) diff --git a/Src/Newtonsoft.Json/JsonValidatingReader.cs b/Src/Newtonsoft.Json/JsonValidatingReader.cs index 4a46a909..b90abc6c 100644 --- a/Src/Newtonsoft.Json/JsonValidatingReader.cs +++ b/Src/Newtonsoft.Json/JsonValidatingReader.cs @@ -318,6 +318,20 @@ namespace Newtonsoft.Json return data; } +#if !NET20 + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . + public override DateTimeOffset ReadAsDateTimeOffset() + { + DateTimeOffset dateTimeOffset = _reader.ReadAsDateTimeOffset(); + + ValidateCurrentToken(); + return dateTimeOffset; + } +#endif + /// /// Reads the next JSON token from the stream. /// diff --git a/Src/Newtonsoft.Json/Linq/JTokenReader.cs b/Src/Newtonsoft.Json/Linq/JTokenReader.cs index b09037d0..7bd68fec 100644 --- a/Src/Newtonsoft.Json/Linq/JTokenReader.cs +++ b/Src/Newtonsoft.Json/Linq/JTokenReader.cs @@ -54,6 +54,22 @@ namespace Newtonsoft.Json.Linq throw new JsonReaderException("Error reading bytes. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); } +#if !NET20 + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . + public override DateTimeOffset ReadAsDateTimeOffset() + { + Read(); + + if (TokenType == JsonToken.Date) + return new DateTimeOffset((DateTime)Value); + + throw new JsonReaderException("Error reading date. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + } +#endif + /// /// Reads the next JSON token from the stream. /// diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 4ca5afbc..2ab32955 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -928,10 +928,20 @@ namespace Newtonsoft.Json.Serialization continue; } - if (property.PropertyType == typeof(byte[])) + // don't read properties with converters as a specific value + // the value might be a string which will then get converted which will error if read as date for example + bool hasConverter = (GetConverter(GetContractSafe(property.PropertyType), property.Converter) != null); + + if (property.PropertyType == typeof(byte[]) && !hasConverter) { reader.ReadAsBytes(); } +#if !NET20 + else if (property.PropertyType == typeof(DateTimeOffset) && !hasConverter) + { + reader.ReadAsDateTimeOffset(); + } +#endif else { if (!reader.Read())