1+ using System ;
2+ using System . IO ;
3+ using System . Text ;
4+ using Elasticsearch . Net ;
5+ using FluentAssertions ;
6+ using Nest ;
7+ using Newtonsoft . Json ;
8+ using Newtonsoft . Json . Linq ;
9+ using Tests . Framework ;
10+
11+ namespace Tests . ClientConcepts . Serializer
12+ {
13+ /// <summary>
14+ /// Tests for default DateTime zone serialization within NEST
15+ /// </summary>
16+ public class IsoDateTimeConverterHandlingTests
17+ {
18+ private readonly Flight _flight ;
19+ private readonly string _offset ;
20+ private readonly TimeSpan _timeSpanOffset ;
21+
22+ public IsoDateTimeConverterHandlingTests ( )
23+ {
24+ var departureDateLocal = new DateTime ( 2013 , 1 , 21 , 0 , 0 , 0 , DateTimeKind . Local ) ;
25+ _timeSpanOffset = TimeZoneInfo . Local . GetUtcOffset ( departureDateLocal ) ;
26+
27+ _flight = new Flight
28+ {
29+ DepartureDate = new DateTime ( 2013 , 1 , 21 , 0 , 0 , 0 , DateTimeKind . Unspecified ) ,
30+ DepartureDateUtc = new DateTime ( 2013 , 1 , 21 , 0 , 0 , 0 , DateTimeKind . Utc ) ,
31+ DepartureDateLocal = departureDateLocal ,
32+ DepartureDateOffset = new DateTimeOffset ( 2013 , 1 , 21 , 0 , 0 , 0 , _timeSpanOffset ) ,
33+ DepartureDateOffsetZero = new DateTimeOffset ( 2013 , 1 , 21 , 0 , 0 , 0 , TimeSpan . Zero ) ,
34+ DepartureDateOffsetNonLocal = new DateTimeOffset ( 2013 , 1 , 21 , 0 , 0 , 0 , TimeSpan . FromHours ( - 6.25 ) ) ,
35+ } ;
36+
37+ _offset = $ "{ _timeSpanOffset . Hours . ToString ( "+00;-00;" ) } :{ _timeSpanOffset . Minutes . ToString ( "00" ) } ";
38+ }
39+
40+ /// <remarks>
41+ /// Timezone offset serialized is based on DateTimeKind
42+ /// Unspecified = None
43+ /// Utc = UTC Timezone identifier
44+ /// Local = Local Timezone offset
45+ /// Offset = Timezone offset specified
46+ /// </remarks>
47+ [ U ]
48+ public void RoundTripKind ( )
49+ {
50+ var dateTimeZoneHandling = DateTimeZoneHandling . RoundtripKind ;
51+
52+ var jsonWithRoundtripTimeZone = this . SerializeUsing ( dateTimeZoneHandling ) ;
53+ var expected = @" {
54+ ""DepartureDate"": ""2013-01-21T00:00:00"",
55+ ""DepartureDateUtc"": ""2013-01-21T00:00:00Z"",
56+ ""DepartureDateLocal"": ""2013-01-21T00:00:00" + _offset + @""",
57+ ""DepartureDateOffset"": ""2013-01-21T00:00:00" + _offset + @""",
58+ ""DepartureDateOffsetZero"": ""2013-01-21T00:00:00+00:00"",
59+ ""DepartureDateOffsetNonLocal"": ""2013-01-21T00:00:00-06:15""
60+ }" ;
61+
62+ jsonWithRoundtripTimeZone . JsonEquals ( expected ) . Should ( ) . BeTrue ( "{0}" , jsonWithRoundtripTimeZone ) ;
63+
64+ var flight = this . DeserializeUsing ( jsonWithRoundtripTimeZone , dateTimeZoneHandling ) ;
65+
66+ flight . Should ( ) . Be ( _flight ) ;
67+ flight . DepartureDate . Kind . Should ( ) . Be ( _flight . DepartureDate . Kind ) ;
68+ flight . DepartureDateLocal . Kind . Should ( ) . Be ( _flight . DepartureDateLocal . Kind ) ;
69+ flight . DepartureDateUtc . Kind . Should ( ) . Be ( _flight . DepartureDateUtc . Kind ) ;
70+ flight . DepartureDateOffset . Offset . Should ( ) . Be ( _flight . DepartureDateOffset . Offset ) ;
71+ flight . DepartureDateOffsetZero . Offset . Should ( ) . Be ( _flight . DepartureDateOffsetZero . Offset ) ;
72+ flight . DepartureDateOffsetNonLocal . Offset . Should ( ) . Be ( _flight . DepartureDateOffsetNonLocal . Offset ) ;
73+ }
74+
75+ /// <remarks>
76+ /// Unspecified = Serialized as is with UTC offset
77+ /// UTC = Serialized as is with UTC Offset
78+ /// Local = Serialied as is with the local offset
79+ /// Offset = Serialized as is with specified offset
80+ /// </remarks>
81+ [ U ]
82+ public void Utc ( )
83+ {
84+ var dateTimeZoneHandling = DateTimeZoneHandling . Utc ;
85+ var dateTimeKind = DateTimeKind . Utc ;
86+
87+ var departureDateLocalInUtc = TimeZoneInfo . ConvertTime ( _flight . DepartureDateLocal , TimeZoneInfo . Local , TimeZoneInfo . Utc ) ;
88+
89+ var jsonWithUtcTimeZone = this . SerializeUsing ( dateTimeZoneHandling ) ;
90+ var expected = @" {
91+ ""DepartureDate"": ""2013-01-21T00:00:00Z"",
92+ ""DepartureDateUtc"": ""2013-01-21T00:00:00Z"",
93+ ""DepartureDateLocal"": ""2013-01-21T00:00:00" + _offset + @""",
94+ ""DepartureDateOffset"": ""2013-01-21T00:00:00" + _offset + @""",
95+ ""DepartureDateOffsetZero"": ""2013-01-21T00:00:00+00:00"",
96+ ""DepartureDateOffsetNonLocal"": ""2013-01-21T00:00:00-06:15""
97+ }" ;
98+
99+ jsonWithUtcTimeZone . JsonEquals ( expected ) . Should ( ) . BeTrue ( "{0}" , jsonWithUtcTimeZone ) ;
100+
101+ var flight = this . DeserializeUsing ( jsonWithUtcTimeZone , dateTimeZoneHandling ) ;
102+
103+ flight . DepartureDate . Should ( ) . Be ( _flight . DepartureDate ) ;
104+ flight . DepartureDate . Kind . Should ( ) . Be ( dateTimeKind ) ;
105+
106+ // The deserialized local will be the UTC DateTime + the local timezone offset,
107+ // and with a DateTimeKind of UTC when deserialized.
108+ //
109+ // Calling .ToLocalTime() will return DepartureDateLocal with correct
110+ // local datetime and DateTimeKind.Local
111+ flight . DepartureDateLocal . Should ( ) . Be ( departureDateLocalInUtc ) ;
112+ flight . DepartureDateLocal . Kind . Should ( ) . Be ( dateTimeKind ) ;
113+
114+ flight . DepartureDateUtc . Should ( ) . Be ( _flight . DepartureDateUtc ) ;
115+ flight . DepartureDateUtc . Kind . Should ( ) . Be ( dateTimeKind ) ;
116+
117+ flight . DepartureDateOffset . Should ( ) . Be ( _flight . DepartureDateOffset ) ;
118+ flight . DepartureDateOffset . Offset . Should ( ) . Be ( _flight . DepartureDateOffset . Offset ) ;
119+
120+ flight . DepartureDateOffsetZero . Should ( ) . Be ( _flight . DepartureDateOffsetZero ) ;
121+ flight . DepartureDateOffsetZero . Offset . Should ( ) . Be ( _flight . DepartureDateOffsetZero . Offset ) ;
122+
123+ flight . DepartureDateOffsetNonLocal . Should ( ) . Be ( _flight . DepartureDateOffsetNonLocal ) ;
124+ flight . DepartureDateOffsetNonLocal . Offset . Should ( ) . Be ( _flight . DepartureDateOffsetNonLocal . Offset ) ;
125+ }
126+
127+ [ U ]
128+ public void Unspecified ( )
129+ {
130+ var dateTimeZoneHandling = DateTimeZoneHandling . Unspecified ;
131+ var dateTimeKind = DateTimeKind . Unspecified ;
132+
133+ var jsonWithUnspecifiedTimeZone = this . SerializeUsing ( dateTimeZoneHandling ) ;
134+ var expected = @" {
135+ ""DepartureDate"": ""2013-01-21T00:00:00"",
136+ ""DepartureDateUtc"": ""2013-01-21T00:00:00"",
137+ ""DepartureDateLocal"": ""2013-01-21T00:00:00"",
138+ ""DepartureDateOffset"": ""2013-01-21T00:00:00" + _offset + @""",
139+ ""DepartureDateOffsetZero"": ""2013-01-21T00:00:00+00:00"",
140+ ""DepartureDateOffsetNonLocal"": ""2013-01-21T00:00:00-06:15""
141+ }" ;
142+
143+ jsonWithUnspecifiedTimeZone . JsonEquals ( expected ) . Should ( ) . BeTrue ( "{0}" , jsonWithUnspecifiedTimeZone ) ;
144+
145+ var flight = this . DeserializeUsing ( jsonWithUnspecifiedTimeZone , dateTimeZoneHandling ) ;
146+
147+ flight . Should ( ) . Be ( _flight ) ;
148+ flight . DepartureDate . Kind . Should ( ) . Be ( dateTimeKind ) ;
149+ flight . DepartureDateLocal . Kind . Should ( ) . Be ( dateTimeKind ) ;
150+ flight . DepartureDateUtc . Kind . Should ( ) . Be ( dateTimeKind ) ;
151+ flight . DepartureDateOffset . Offset . Should ( ) . Be ( _flight . DepartureDateOffset . Offset ) ;
152+ flight . DepartureDateOffsetZero . Offset . Should ( ) . Be ( _flight . DepartureDateOffsetZero . Offset ) ;
153+ flight . DepartureDateOffsetNonLocal . Offset . Should ( ) . Be ( _flight . DepartureDateOffsetNonLocal . Offset ) ;
154+ }
155+
156+ [ U ]
157+ public void Local ( )
158+ {
159+ var dateTimeZoneHandling = DateTimeZoneHandling . Local ;
160+ var dateTimeKind = DateTimeKind . Local ;
161+
162+ var jsonWithLocalTimeZone = this . SerializeUsing ( dateTimeZoneHandling ) ;
163+ var departureDateUtcInLocal = TimeZoneInfo . ConvertTime ( _flight . DepartureDateUtc , TimeZoneInfo . Utc , TimeZoneInfo . Local ) ;
164+
165+ var expected = @"
166+ {
167+ ""DepartureDate"": ""2013-01-21T00:00:00"",
168+ ""DepartureDateUtc"": ""2013-01-21T00:00:00Z"",
169+ ""DepartureDateLocal"": ""2013-01-21T00:00:00" + _offset + @""",
170+ ""DepartureDateOffset"": ""2013-01-21T00:00:00" + _offset + @""",
171+ ""DepartureDateOffsetZero"": ""2013-01-21T00:00:00+00:00"",
172+ ""DepartureDateOffsetNonLocal"": ""2013-01-21T00:00:00-06:15""
173+ }" ;
174+
175+ jsonWithLocalTimeZone . JsonEquals ( expected ) . Should ( ) . BeTrue ( "{0}" , jsonWithLocalTimeZone ) ;
176+
177+ var flight = this . DeserializeUsing ( jsonWithLocalTimeZone , dateTimeZoneHandling ) ;
178+
179+ flight . DepartureDate . Should ( ) . Be ( _flight . DepartureDate ) ;
180+ flight . DepartureDate . Kind . Should ( ) . Be ( dateTimeKind ) ;
181+
182+
183+ flight . DepartureDateLocal . Should ( ) . Be ( _flight . DepartureDateLocal ) ;
184+ flight . DepartureDateLocal . Kind . Should ( ) . Be ( dateTimeKind ) ;
185+
186+ // The deserialized UTC will be the UTC DateTime + the local timezone offset
187+ // and a DateTimeKind of LOCAL when deserialized.
188+ //
189+ // Calling .ToUniversalTime() will return DepartureDateUtc with correct
190+ // UTC datetime and DateTimeKind.Utc
191+ flight . DepartureDateUtc . Should ( ) . Be ( departureDateUtcInLocal ) ;
192+ flight . DepartureDateUtc . Kind . Should ( ) . Be ( dateTimeKind ) ;
193+
194+ flight . DepartureDateOffset . Should ( ) . Be ( _flight . DepartureDateOffset ) ;
195+ flight . DepartureDateOffset . Offset . Should ( ) . Be ( _flight . DepartureDateOffset . Offset ) ;
196+
197+ flight . DepartureDateOffsetZero . Should ( ) . Be ( _flight . DepartureDateOffsetZero ) ;
198+ flight . DepartureDateOffsetZero . Offset . Should ( ) . Be ( _flight . DepartureDateOffsetZero . Offset ) ;
199+
200+ flight . DepartureDateOffsetNonLocal . Should ( ) . Be ( _flight . DepartureDateOffsetNonLocal ) ;
201+ flight . DepartureDateOffsetNonLocal . Offset . Should ( ) . Be ( _flight . DepartureDateOffsetNonLocal . Offset ) ;
202+ }
203+
204+ private string SerializeUsing ( DateTimeZoneHandling handling )
205+ {
206+ var pool = new SingleNodeConnectionPool ( new Uri ( "http://localhost:9200" ) ) ;
207+ var settings = new ConnectionSettings ( pool , new InMemoryConnection ( ) , new SerializerFactory (
208+ ( serializerSettings , connectionSettings ) =>
209+ {
210+ serializerSettings . DateTimeZoneHandling = handling ;
211+ serializerSettings . Formatting = Formatting . Indented ;
212+ } ) )
213+ . DefaultFieldNameInferrer ( p => p ) ;
214+
215+ var client = new ElasticClient ( settings ) ;
216+ return client . Serializer . SerializeToString ( _flight ) ;
217+ }
218+
219+ private Flight DeserializeUsing ( string json , DateTimeZoneHandling handling )
220+ {
221+ var pool = new SingleNodeConnectionPool ( new Uri ( "http://localhost:9200" ) ) ;
222+ var settings = new ConnectionSettings ( pool , new InMemoryConnection ( ) , new SerializerFactory (
223+ ( serializerSettings , connectionSettings ) =>
224+ {
225+ serializerSettings . DateTimeZoneHandling = handling ;
226+ } ) )
227+ . DefaultFieldNameInferrer ( p => p ) ;
228+
229+ var client = new ElasticClient ( settings ) ;
230+ using ( var stream = new MemoryStream ( Encoding . UTF8 . GetBytes ( json ) ) )
231+ {
232+ return client . Serializer . Deserialize < Flight > ( stream ) ;
233+ }
234+ }
235+ }
236+
237+ internal class Flight
238+ {
239+ public DateTime DepartureDate { get ; set ; }
240+ public DateTime DepartureDateUtc { get ; set ; }
241+ public DateTime DepartureDateLocal { get ; set ; }
242+ public DateTimeOffset DepartureDateOffset { get ; set ; }
243+ public DateTimeOffset DepartureDateOffsetZero { get ; set ; }
244+ public DateTimeOffset DepartureDateOffsetNonLocal { get ; set ; }
245+
246+ protected bool Equals ( Flight other )
247+ {
248+ return DepartureDate . Equals ( other . DepartureDate ) &&
249+ DepartureDateUtc . Equals ( other . DepartureDateUtc ) &&
250+ DepartureDateLocal . Equals ( other . DepartureDateLocal ) &&
251+ DepartureDateOffset . Equals ( other . DepartureDateOffset ) &&
252+ DepartureDateOffsetZero . Equals ( other . DepartureDateOffsetZero ) &&
253+ DepartureDateOffsetNonLocal . Equals ( other . DepartureDateOffsetNonLocal ) ;
254+ }
255+
256+ public override bool Equals ( object obj )
257+ {
258+ if ( ReferenceEquals ( null , obj ) ) return false ;
259+ if ( ReferenceEquals ( this , obj ) ) return true ;
260+ if ( obj . GetType ( ) != this . GetType ( ) ) return false ;
261+ return Equals ( ( Flight ) obj ) ;
262+ }
263+
264+ public override int GetHashCode ( )
265+ {
266+ unchecked
267+ {
268+ var hashCode = DepartureDate . GetHashCode ( ) ;
269+ hashCode = ( hashCode * 397 ) ^ DepartureDateUtc . GetHashCode ( ) ;
270+ hashCode = ( hashCode * 397 ) ^ DepartureDateLocal . GetHashCode ( ) ;
271+ hashCode = ( hashCode * 397 ) ^ DepartureDateOffset . GetHashCode ( ) ;
272+ hashCode = ( hashCode * 397 ) ^ DepartureDateOffsetZero . GetHashCode ( ) ;
273+ hashCode = ( hashCode * 397 ) ^ DepartureDateOffsetNonLocal . GetHashCode ( ) ;
274+ return hashCode ;
275+ }
276+ }
277+ }
278+
279+ internal static class JsonExtensions
280+ {
281+ internal static bool JsonEquals ( this string value , string other )
282+ {
283+ var valueToken = JObject . Parse ( value ) ;
284+ var otherToken = JObject . Parse ( other ) ;
285+ return JToken . DeepEquals ( valueToken , otherToken ) ;
286+ }
287+ }
288+ }
0 commit comments