Skip to content

Commit d847d5d

Browse files
committed
Serialization tests for DateTime and DateTimeOffset
Comparison of JSON.Net's default DateTime and DateTimeOffset serialization compared to NEST, which uses IsoDateTimeConverter to serialize DateTime(?)
1 parent b9eff57 commit d847d5d

File tree

5 files changed

+529
-109
lines changed

5 files changed

+529
-109
lines changed

src/Tests/Nest.Tests.Unit/Internals/Serialize/ConnectionSettingsTests.cs

Lines changed: 0 additions & 108 deletions
This file was deleted.
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Text;
7+
using Elasticsearch.Net;
8+
using FluentAssertions;
9+
using Nest.Resolvers;
10+
using Newtonsoft.Json;
11+
using Newtonsoft.Json.Serialization;
12+
using NUnit.Framework;
13+
14+
namespace Nest.Tests.Unit.Internals.Serialize
15+
{
16+
/// <summary>
17+
/// Tests for Json.Net default DateTime zone serialization
18+
/// </summary>
19+
/// <remarks>
20+
/// These tests are based on the Json.NET unit tests for DateTimeZoneHandling
21+
/// http://www.newtonsoft.com/json/help/html/SerializeDateTimeZoneHandling.htm
22+
///
23+
/// and
24+
///
25+
/// https://github.com/JamesNK/Newtonsoft.Json/blob/bcd6982419c6165ed2c606eb9994c1aa6bce3735/Src/Newtonsoft.Json.Tests/Documentation/Samples/Serializer/SerializeDateTimeZoneHandling.cs
26+
/// </remarks>
27+
[TestFixture]
28+
public class DateTimeZoneHandlingTests
29+
{
30+
private Flight _flight;
31+
private string _offset;
32+
private TimeSpan _timeSpanOffset;
33+
34+
[SetUp]
35+
public void SetUp()
36+
{
37+
var departureDateLocal = new DateTime(2013, 1, 21, 0, 0, 0, DateTimeKind.Local);
38+
_timeSpanOffset = TimeZoneInfo.Local.GetUtcOffset(departureDateLocal);
39+
40+
_flight = new Flight
41+
{
42+
DepartureDate = new DateTime(2013, 1, 21, 0, 0, 0, DateTimeKind.Unspecified),
43+
DepartureDateUtc = new DateTime(2013, 1, 21, 0, 0, 0, DateTimeKind.Utc),
44+
DepartureDateLocal = departureDateLocal,
45+
DepartureDateOffset = new DateTimeOffset(2013, 1, 21, 0, 0, 0, _timeSpanOffset),
46+
DepartureDateOffsetZero = new DateTimeOffset(2013, 1, 21, 0, 0, 0, TimeSpan.Zero),
47+
};
48+
49+
_offset = string.Format("{0}:{1}",
50+
_timeSpanOffset.Hours.ToString("+00;-00;"),
51+
_timeSpanOffset.Minutes.ToString("00"));
52+
}
53+
54+
/// <remarks>
55+
/// Timezone offset serialized is based on DateTimeKind
56+
/// Unspecified = None
57+
/// Utc = UTC Timezone identifier
58+
/// Local = Local Timezone offset
59+
/// </remarks>
60+
[Test]
61+
public void RoundTripKind()
62+
{
63+
var dateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind;
64+
65+
var jsonWithRoundtripTimeZone = this.SerializeUsing(_flight, dateTimeZoneHandling);
66+
var expected = @" {
67+
""DepartureDate"": ""2013-01-21T00:00:00"",
68+
""DepartureDateUtc"": ""2013-01-21T00:00:00Z"",
69+
""DepartureDateLocal"": ""2013-01-21T00:00:00" + _offset + @""",
70+
""DepartureDateOffset"": ""2013-01-21T00:00:00" + _offset + @""",
71+
""DepartureDateOffsetZero"": ""2013-01-21T00:00:00+00:00"",
72+
}";
73+
jsonWithRoundtripTimeZone.JsonEquals(expected).Should().BeTrue("{0}", jsonWithRoundtripTimeZone);
74+
75+
var flight = this.DeserializeUsing(jsonWithRoundtripTimeZone, dateTimeZoneHandling);
76+
77+
flight.Should().Be(_flight);
78+
flight.DepartureDate.Kind.Should().Be(_flight.DepartureDate.Kind);
79+
flight.DepartureDateLocal.Kind.Should().Be(_flight.DepartureDateLocal.Kind);
80+
flight.DepartureDateUtc.Kind.Should().Be(_flight.DepartureDateUtc.Kind);
81+
flight.DepartureDateOffset.Offset.Should().Be(_flight.DepartureDateOffset.Offset);
82+
flight.DepartureDateOffsetZero.Offset.Should().Be(_flight.DepartureDateOffsetZero.Offset);
83+
}
84+
85+
/// <remarks>
86+
/// Serialization
87+
/// -------------
88+
/// Unspecified = Serialized as is with UTC offset
89+
/// UTC = Serialized as is with UTC Offset
90+
/// Local = Converted to UniversalTime and serialized
91+
/// </remarks>
92+
[Test]
93+
public void Utc()
94+
{
95+
var dateTimeZoneHandling = DateTimeZoneHandling.Utc;
96+
var dateTimeKind = DateTimeKind.Utc;
97+
98+
var departureDateLocal = _flight.DepartureDateUtc.Subtract(_timeSpanOffset);
99+
var depatureDateLocalString = departureDateLocal.ToString("yyyy-MM-ddTHH:mm:ssZ");
100+
101+
var jsonWithUtcTimeZone = this.SerializeUsing(_flight, dateTimeZoneHandling);
102+
var expected = @" {
103+
""DepartureDate"": ""2013-01-21T00:00:00Z"",
104+
""DepartureDateUtc"": ""2013-01-21T00:00:00Z"",
105+
""DepartureDateLocal"": """ + depatureDateLocalString + @""",
106+
""DepartureDateOffset"": ""2013-01-21T00:00:00" + _offset + @""",
107+
""DepartureDateOffsetZero"": ""2013-01-21T00:00:00+00:00"",
108+
}";
109+
jsonWithUtcTimeZone.JsonEquals(expected).Should().BeTrue("{0}", jsonWithUtcTimeZone);
110+
111+
var flight = this.DeserializeUsing(jsonWithUtcTimeZone, dateTimeZoneHandling);
112+
113+
flight.DepartureDate.Should().Be(_flight.DepartureDate);
114+
flight.DepartureDate.Kind.Should().Be(dateTimeKind);
115+
116+
// The deserialized local will be the UTC DateTime + the local timezone offset,
117+
// AND with a DateTimeKind of UTC when deserialized.
118+
flight.DepartureDateLocal.Should().Be(departureDateLocal);
119+
flight.DepartureDateLocal.Kind.Should().Be(dateTimeKind);
120+
121+
flight.DepartureDateUtc.Should().Be(_flight.DepartureDateUtc);
122+
flight.DepartureDateUtc.Kind.Should().Be(dateTimeKind);
123+
124+
flight.DepartureDateOffset.Should().Be(_flight.DepartureDateOffset);
125+
flight.DepartureDateOffset.Offset.Should().Be(_flight.DepartureDateOffset.Offset);
126+
127+
flight.DepartureDateOffsetZero.Should().Be(_flight.DepartureDateOffsetZero);
128+
flight.DepartureDateOffsetZero.Offset.Should().Be(_flight.DepartureDateOffsetZero.Offset);
129+
}
130+
131+
/// <remarks>
132+
/// No Timezone offset is serialized
133+
/// </remarks>
134+
[Test]
135+
public void Unspecified()
136+
{
137+
var dateTimeZoneHandling = DateTimeZoneHandling.Unspecified;
138+
var dateTimeKind = DateTimeKind.Unspecified;
139+
140+
var jsonWithUnspecifiedTimeZone = this.SerializeUsing(_flight, dateTimeZoneHandling);
141+
var expected = @" {
142+
""DepartureDate"": ""2013-01-21T00:00:00"",
143+
""DepartureDateUtc"": ""2013-01-21T00:00:00"",
144+
""DepartureDateLocal"": ""2013-01-21T00:00:00"",
145+
""DepartureDateOffset"": ""2013-01-21T00:00:00" + _offset + @""",
146+
""DepartureDateOffsetZero"": ""2013-01-21T00:00:00+00:00"",
147+
}";
148+
jsonWithUnspecifiedTimeZone.JsonEquals(expected).Should().BeTrue("{0}", jsonWithUnspecifiedTimeZone);
149+
150+
var flight = this.DeserializeUsing(jsonWithUnspecifiedTimeZone, dateTimeZoneHandling);
151+
152+
flight.Should().Be(_flight);
153+
flight.DepartureDate.Kind.Should().Be(dateTimeKind);
154+
flight.DepartureDateLocal.Kind.Should().Be(dateTimeKind);
155+
flight.DepartureDateUtc.Kind.Should().Be(dateTimeKind);
156+
flight.DepartureDateOffset.Offset.Should().Be(_flight.DepartureDateOffset.Offset);
157+
flight.DepartureDateOffsetZero.Offset.Should().Be(_flight.DepartureDateOffsetZero.Offset);
158+
}
159+
160+
/// <remarks>
161+
/// Utc DateTime is converted to Local time
162+
/// Unspecified DateTime is assumed to be local and saved as-is with local offset
163+
/// Local DateTime is saved as is with local offset
164+
/// </remarks>
165+
[Test]
166+
public void Local()
167+
{
168+
var dateTimeZoneHandling = DateTimeZoneHandling.Local;
169+
var dateTimeKind = DateTimeKind.Local;
170+
171+
var jsonWithLocalTimeZone = this.SerializeUsing(_flight, dateTimeZoneHandling);
172+
var departureDateLocal = _flight.DepartureDateUtc.Add(_timeSpanOffset);
173+
var depatureDateLocalString = departureDateLocal.ToString("yyyy-MM-ddTHH:mm:ss");
174+
175+
var expected = @"
176+
{
177+
""DepartureDate"": ""2013-01-21T00:00:00" + _offset + @""",
178+
""DepartureDateUtc"": """ + depatureDateLocalString + _offset + @""",
179+
""DepartureDateLocal"": ""2013-01-21T00:00:00" + _offset + @""",
180+
""DepartureDateOffset"": ""2013-01-21T00:00:00" + _offset + @""",
181+
""DepartureDateOffsetZero"": ""2013-01-21T00:00:00+00:00"",
182+
}";
183+
jsonWithLocalTimeZone.JsonEquals(expected).Should().BeTrue("{0}", jsonWithLocalTimeZone);
184+
185+
var flight = this.DeserializeUsing(jsonWithLocalTimeZone, dateTimeZoneHandling);
186+
187+
flight.DepartureDate.Should().Be(_flight.DepartureDate);
188+
flight.DepartureDate.Kind.Should().Be(dateTimeKind);
189+
190+
// The deserialized local will be the local DateTime
191+
// and a DateTimeKind of Local when deserialized.
192+
flight.DepartureDateLocal.Should().Be(_flight.DepartureDateLocal);
193+
flight.DepartureDateLocal.Kind.Should().Be(dateTimeKind);
194+
195+
// The deserialized UTC will be the UTC DateTime + the local timezone offset
196+
// BUT with a DateTimeKind of LOCAL when deserialized.
197+
//
198+
// Calling .ToUniversalTime() will return DepartureDateUtc with correct
199+
// UTC datetime and DateTimeKind.Utc
200+
flight.DepartureDateUtc.Should().Be(departureDateLocal);
201+
flight.DepartureDateUtc.Kind.Should().Be(dateTimeKind);
202+
203+
flight.DepartureDateOffset.Should().Be(_flight.DepartureDateOffset);
204+
flight.DepartureDateOffset.Offset.Should().Be(_flight.DepartureDateOffset.Offset);
205+
206+
flight.DepartureDateOffsetZero.Should().Be(_flight.DepartureDateOffsetZero);
207+
flight.DepartureDateOffsetZero.Offset.Should().Be(_flight.DepartureDateOffsetZero.Offset);
208+
}
209+
210+
private string SerializeUsing(Flight flight, DateTimeZoneHandling handling)
211+
{
212+
var settings = new ConnectionSettings();
213+
214+
settings
215+
.SetDefaultPropertyNameInferrer(p => p)
216+
.SetJsonSerializerSettingsModifier(s =>
217+
{
218+
s.DateFormatHandling = DateFormatHandling.IsoDateFormat;
219+
s.DateTimeZoneHandling = handling;
220+
s.Formatting = Formatting.Indented;
221+
s.ContractResolver = new DefaultDateTimeContractResolver(settings);
222+
});
223+
var client = new ElasticClient(settings);
224+
return client.Serializer.Serialize(flight).Utf8String();
225+
}
226+
227+
private Flight DeserializeUsing(string json, DateTimeZoneHandling handling)
228+
{
229+
var settings = new ConnectionSettings()
230+
.SetDefaultPropertyNameInferrer(p => p)
231+
.SetJsonSerializerSettingsModifier(s =>
232+
{
233+
s.DateFormatHandling = DateFormatHandling.IsoDateFormat;
234+
s.DateTimeZoneHandling = handling;
235+
});
236+
var client = new ElasticClient(settings);
237+
238+
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
239+
{
240+
return client.Serializer.Deserialize<Flight>(stream);
241+
}
242+
}
243+
244+
private class DefaultDateTimeContractResolver : ElasticContractResolver
245+
{
246+
public DefaultDateTimeContractResolver(IConnectionSettingsValues connectionSettings)
247+
: base(connectionSettings)
248+
{
249+
}
250+
251+
protected override JsonContract CreateContract(Type objectType)
252+
{
253+
var contract = base.CreateContract(objectType);
254+
255+
// remove the default IsoDateTimeConverter
256+
if (objectType == typeof(DateTime) || objectType == typeof(DateTime?))
257+
contract.Converter = null;
258+
259+
return contract;
260+
}
261+
}
262+
}
263+
}

0 commit comments

Comments
 (0)