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