11namespace SqlServerSimulator ;
22
33/// <summary>
4- /// Exercises the simulator's <c>datetime2(N)</c> and <c>datetimeoffset(N)</c>
5- /// column support through EF Core's idiomatic surface. Confirms full-precision
6- /// (precision 7) round trips and lower-precision (precision 3 / 0) rounding-
7- /// on-store behavior land correctly through EF's parameter binding and reader
8- /// hydration. Datetimeoffset additionally pins offset preservation across the
9- /// round trip and equality-by-UTC-instant in <c>WHERE</c>.
4+ /// Exercises <c>datetime2(N)</c> and <c>datetimeoffset(N)</c> column support
5+ /// through EF Core. Confirms full-precision (precision 7) round trips,
6+ /// lower-precision rounding-on-store, datetimeoffset offset preservation,
7+ /// equality-by-UTC-instant, and legacy datetime tick-quantization round-trip.
108/// </summary>
11- /// <remarks>
12- /// Only <see cref="DateTime"/> and <see cref="DateTimeOffset"/> properties are
13- /// covered — see <see cref="SimulatedDbParameter"/> for the per-mapping
14- /// compatibility table explaining why the other date/time configurations
15- /// (<c>DateOnly → date</c>, <c>TimeOnly → time</c>, <c>TimeSpan → time</c>,
16- /// <c>DateTime → date</c>) are unreachable through EF Core until a bridge
17- /// adapter ships.
18- /// </remarks>
199[ TestClass ]
2010public class EFCoreDateTime
2111{
@@ -69,7 +59,7 @@ public void Insert_NullableDateTime_AcceptsValue()
6959 [ TestMethod ]
7060 public void Insert_LowerPrecisionColumn_RoundsHalfUp ( )
7161 {
72- // Updated is datetime2(3); 0.5ms above a millisecond boundary rounds to next ms.
62+ // Updated is datetime2(3); 0.5ms above ms boundary rounds to next ms.
7363 using var context = new TestDbContext ( TestDbContext . CreateEventsSimulation ( ) ) ;
7464 var updated = new DateTime ( 2026 , 5 , 4 , 13 , 45 , 30 , 100 ) . AddTicks ( 5_000 ) ;
7565 _ = context . Events . Add ( new Event { Id = 1 , CreatedAt = new DateTime ( 2026 , 5 , 4 ) , Updated = updated } ) ;
@@ -90,8 +80,7 @@ public void Where_FiltersByDateTimeEquality()
9080 new Event { Id = 3 , CreatedAt = new DateTime ( 2026 , 5 , 4 , 15 , 0 , 0 ) } ) ;
9181 _ = context . SaveChanges ( ) ;
9282
93- var match = context . Events . Where ( e => e . CreatedAt == target ) . Select ( e => e . Id ) . Single ( ) ;
94- Assert . AreEqual ( 2 , match ) ;
83+ Assert . AreEqual ( 2 , context . Events . Where ( e => e . CreatedAt == target ) . Select ( e => e . Id ) . Single ( ) ) ;
9584 }
9685
9786 [ TestMethod ]
@@ -146,7 +135,7 @@ public void Insert_NullableDateTimeOffset_AcceptsNullAndValue()
146135 [ TestMethod ]
147136 public void Insert_DateTimeOffset_LowerPrecisionColumn_RoundsHalfUp ( )
148137 {
149- // Cancelled is datetimeoffset(3); 0.5ms above a millisecond boundary rounds to next ms.
138+ // Cancelled is datetimeoffset(3); 0.5ms above ms boundary rounds to next ms.
150139 using var context = new TestDbContext ( TestDbContext . CreateEventsSimulation ( ) ) ;
151140 var cancelled = new DateTimeOffset ( 2026 , 5 , 4 , 13 , 45 , 30 , 100 , TimeSpan . FromHours ( - 7 ) ) . AddTicks ( 5_000 ) ;
152141 _ = context . Events . Add ( new Event { Id = 1 , CreatedAt = new DateTime ( 2026 , 5 , 4 ) , OccurredAt = DateTimeOffset . UnixEpoch , Cancelled = cancelled } ) ;
@@ -159,8 +148,7 @@ public void Insert_DateTimeOffset_LowerPrecisionColumn_RoundsHalfUp()
159148 [ TestMethod ]
160149 public void Where_FiltersByDateTimeOffsetEquality_CrossOffset ( )
161150 {
162- // The stored value and the parameter share a UTC instant but carry
163- // different offsets; equality should still match.
151+ // Stored value and parameter share UTC instant but carry different offsets; equality should match.
164152 using var context = new TestDbContext ( TestDbContext . CreateEventsSimulation ( ) ) ;
165153 var east = new DateTimeOffset ( 2026 , 5 , 4 , 20 , 45 , 30 , TimeSpan . FromHours ( 7 ) ) ;
166154 context . Events . AddRange (
@@ -169,30 +157,25 @@ public void Where_FiltersByDateTimeOffsetEquality_CrossOffset()
169157 _ = context . SaveChanges ( ) ;
170158
171159 var west = new DateTimeOffset ( 2026 , 5 , 4 , 6 , 45 , 30 , TimeSpan . FromHours ( - 7 ) ) ;
172- var match = context . Events . Where ( e => e . OccurredAt == west ) . Select ( e => e . Id ) . Single ( ) ;
173- Assert . AreEqual ( 1 , match ) ;
160+ Assert . AreEqual ( 1 , context . Events . Where ( e => e . OccurredAt == west ) . Select ( e => e . Id ) . Single ( ) ) ;
174161 }
175162
176163 [ TestMethod ]
177164 public void Insert_LegacyDateTime_RoundTripsAtTickGranularity ( )
178165 {
179- // Legacy datetime stores 1/300-second ticks. .997 input lands on
180- // tick 299 — preserved by EF Core's reader hydration since SqlClient
181- // reconstructs DateTime ticks deterministically from the stored unit.
166+ // Legacy datetime stores 1/300-second ticks; .997 input → tick 299 = 9_966_666 100-ns ticks past second.
182167 using var context = new TestDbContext ( TestDbContext . CreateEventsSimulation ( ) ) ;
183168
184169 var started = new DateTime ( 2026 , 5 , 4 , 13 , 45 , 30 , 997 ) ;
185170 _ = context . Events . Add ( new Event { Id = 1 , CreatedAt = new DateTime ( 2026 , 5 , 4 ) , Started = started } ) ;
186171 _ = context . SaveChanges ( ) ;
187172
188- // Round-trip preserves the tick-quantized value, not the raw ms.
189173 var read = context . Events . Select ( e => e . Started ) . First ( ) ;
190174 Assert . IsNotNull ( read ) ;
191175 Assert . AreEqual ( started . Date , read . Value . Date ) ;
192176 Assert . AreEqual ( started . Hour , read . Value . Hour ) ;
193177 Assert . AreEqual ( started . Minute , read . Value . Minute ) ;
194178 Assert . AreEqual ( started . Second , read . Value . Second ) ;
195- // .997 ms input → tick 299 → stored at 9_966_666 100-ns ticks past the second.
196179 Assert . AreEqual ( 9_966_666 , read . Value . Ticks % TimeSpan . TicksPerSecond ) ;
197180 }
198181
@@ -228,8 +211,7 @@ public void Where_FiltersByLegacyDateTimeEquality()
228211 new Event { Id = 3 , CreatedAt = new DateTime ( 2026 , 5 , 4 ) , Started = new DateTime ( 2026 , 5 , 4 , 15 , 0 , 0 ) } ) ;
229212 _ = context . SaveChanges ( ) ;
230213
231- var match = context . Events . Where ( e => e . Started == target ) . Select ( e => e . Id ) . Single ( ) ;
232- Assert . AreEqual ( 2 , match ) ;
214+ Assert . AreEqual ( 2 , context . Events . Where ( e => e . Started == target ) . Select ( e => e . Id ) . Single ( ) ) ;
233215 }
234216
235217 [ TestMethod ]
@@ -246,9 +228,7 @@ public void Insert_LegacyDateTime_AtMin_RoundTrips()
246228 [ TestMethod ]
247229 public void Where_DateTimeYearExtraction_TranslatesToDatepart ( )
248230 {
249- // EF Core translates .Year (and .Month / .Day / .Hour / etc.) to
250- // DATEPART(year, col). Common in real apps for "events this year"
251- // filters.
231+ // EF Core translates .Year to DATEPART(year, col).
252232 var simulation = TestDbContext . CreateEventsSimulation ( ) ;
253233 using ( var seed = new TestDbContext ( simulation ) )
254234 {
@@ -267,7 +247,6 @@ public void Where_DateTimeYearExtraction_TranslatesToDatepart()
267247 [ TestMethod ]
268248 public void Projection_DateTimeAddDays_TranslatesToDateadd ( )
269249 {
270- // EF Core translates .AddDays(N) to DATEADD(day, CAST(N AS int), col).
271250 var simulation = TestDbContext . CreateEventsSimulation ( ) ;
272251 using ( var seed = new TestDbContext ( simulation ) )
273252 {
@@ -281,7 +260,6 @@ public void Projection_DateTimeAddDays_TranslatesToDateadd()
281260 }
282261
283262 using var context = new TestDbContext ( simulation ) ;
284- var rolled = context . Events . Select ( e => e . CreatedAt . AddDays ( 7 ) ) . Single ( ) ;
285- Assert . AreEqual ( new DateTime ( 2024 , 6 , 8 ) , rolled ) ;
263+ Assert . AreEqual ( new DateTime ( 2024 , 6 , 8 ) , context . Events . Select ( e => e . CreatedAt . AddDays ( 7 ) ) . Single ( ) ) ;
286264 }
287265}
0 commit comments