88
99namespace CodeJam . Dates
1010{
11- // TODO: tests with lastDayOfMont / tests with DST
11+ // TODO: tests with lastDayOfMonth / tests with DST
1212 public static class DateTimeExtensionsTests
1313 {
14+ private static DateTime DateWithSameOddityDay ( DateTime date , int day )
15+ {
16+ // Ensure that day is as even as value
17+ var dateDay = date . WithDay ( day ) ;
18+ var totalDays = ( long ) ( dateDay - DateTime . MinValue ) . TotalDays ;
19+ if ( totalDays % 2 != day % 2 )
20+ dateDay = date . WithDay ( day + 1 ) ;
21+
22+ return dateDay ;
23+ }
24+
25+ private static DateTimeOffset DateWithSameOddityDay ( DateTimeOffset date , int day ) =>
26+ DateWithSameOddityDay ( date . Date , day ) ;
27+
28+ [ Test ]
29+ public static void TestDivideRoundToEvenNaive ( )
30+ {
31+ AreEqual ( DateTimeExtensions . DivideRoundToEvenNaive ( 6 , 2 ) , 3 ) ;
32+ AreEqual ( DateTimeExtensions . DivideRoundToEvenNaive ( 7 , 3 ) , 2 ) ;
33+ AreEqual ( DateTimeExtensions . DivideRoundToEvenNaive ( 11 , 3 ) , 4 ) ;
34+ AreEqual ( DateTimeExtensions . DivideRoundToEvenNaive ( 14 , 3 ) , 5 ) ;
35+
36+ AreEqual ( DateTimeExtensions . DivideRoundToEvenNaive ( 7 , 2 ) , 4 ) ;
37+ AreEqual ( DateTimeExtensions . DivideRoundToEvenNaive ( 9 , 2 ) , 4 ) ;
38+
39+ AreEqual ( DateTimeExtensions . DivideRoundToEvenNaive ( 14000 , 4000 ) , 4 ) ;
40+ AreEqual ( DateTimeExtensions . DivideRoundToEvenNaive ( 18000 , 4000 ) , 4 ) ;
41+ }
42+
43+ [ Test ]
44+ public static void TestDivideRoundToEvenNaiveRandom ( )
45+ {
46+ var rnd = TestTools . GetTestRandom ( ) ;
47+ for ( int i = 0 ; i < 100 ; i ++ )
48+ {
49+ var a = ( long ) rnd . Next ( 1 , int . MaxValue ) ;
50+ var b = ( long ) rnd . Next ( 1 , 10 ) ;
51+ var b2 = ( long ) rnd . Next ( ( int ) TimeSpan . TicksPerSecond , 200 * ( int ) TimeSpan . TicksPerSecond ) ;
52+
53+ var div1 = DateTimeExtensions . DivideRoundToEvenNaive ( a , b ) ;
54+ var div2 = ( long ) Math . Round ( 1m * a / b ) ;
55+ AreEqual ( div1 , div2 ) ;
56+
57+ div1 = DateTimeExtensions . DivideRoundToEvenNaive ( a , b2 ) ;
58+ div2 = ( long ) Math . Round ( 1m * a / b2 ) ;
59+ AreEqual ( div1 , div2 ) ;
60+ }
61+ }
62+
1463 [ Test ]
1564 public static void TestDateTimeExtensions ( )
1665 {
@@ -36,36 +85,17 @@ public static void TestDateTimeExtensions()
3685 AreEqual ( d . GetYearRange ( ) . CountOfDays ( ) , d . DaysInYear ( ) ) ;
3786 }
3887
39- [ Test ]
40- public static void TestIsUtc ( )
41- {
42- var now = DateTimeOffset . Now . ToOffset ( TimeSpan . FromHours ( 1 ) ) ;
43- var utcNow = DateTimeOffset . UtcNow ;
44- IsFalse ( now . IsUtc ( ) ) ;
45- IsTrue ( utcNow . IsUtc ( ) ) ;
46- IsTrue ( now . ToUniversalTime ( ) . IsUtc ( ) ) ;
47- }
48-
49- [ Test ]
50- public static void TestTruncateTime ( )
51- {
52- var now = DateTimeOffset . Now . ToOffset ( TimeSpan . FromHours ( 1 ) ) ;
53- var utcNow = DateTimeOffset . UtcNow ;
54- AreEqual ( now . TruncateTime ( ) , now - now . TimeOfDay ) ;
55- AreEqual ( utcNow . TruncateTime ( ) , utcNow - utcNow . TimeOfDay ) ;
56- }
57-
5888 [ Test ]
5989 public static void TestDateTimeOffsetExtensions ( )
6090 {
61- var d = DateTime . Today . FirstDayOfMonth ( ) . AddHours ( 12 ) ;
91+ var d = DateTime . Today . FirstDayOfMonth ( ) . AddHours ( 12 ) . ToOffset ( ) ;
6292
6393 AreEqual ( d . NextDay ( ) . PrevDay ( ) , d ) ;
6494 AreEqual ( d . NextMonth ( ) . PrevMonth ( ) , d ) ;
6595 AreEqual ( d . NextYear ( ) . PrevYear ( ) , d ) ;
6696 AreEqual ( d . LastDayOfMonth ( ) . Day , d . DaysInMonth ( ) ) ;
6797 AreEqual ( d . WithMonth ( 1 ) . WithDay ( 1 ) , d . FirstDayOfYear ( ) . AddHours ( 12 ) ) ;
68- AreEqual ( d . WithYearAndMonth ( 2000 , 1 ) . WithDay ( 1 ) , new DateTime ( 2000 , 1 , 1 ) . AddHours ( 12 ) ) ;
98+ AreEqual ( d . WithYearAndMonth ( 2000 , 1 ) . WithDay ( 1 ) , new DateTime ( 2000 , 1 , 1 ) . AddHours ( 12 ) . ToOffset ( ) ) ;
6999 AreEqual ( d . WithMonthAndDay ( 1 , 1 ) , d . FirstDayOfYear ( ) . AddHours ( 12 ) ) ;
70100
71101 AreEqual ( d . GetMonthRange ( ) . CountOfDays ( ) , d . DaysInMonth ( ) ) ;
@@ -81,7 +111,26 @@ public static void TestDateTimeOffsetExtensions()
81111 }
82112
83113 [ Test ]
84- public static void UseLastDayTests ( )
114+ public static void TestIsUtc ( )
115+ {
116+ var now = DateTimeOffset . Now . ToOffset ( TimeSpan . FromHours ( 1 ) ) ;
117+ var utcNow = DateTimeOffset . UtcNow ;
118+ IsFalse ( now . IsUtc ( ) ) ;
119+ IsTrue ( utcNow . IsUtc ( ) ) ;
120+ IsTrue ( now . ToUniversalTime ( ) . IsUtc ( ) ) ;
121+ }
122+
123+ [ Test ]
124+ public static void TestTruncateTime ( )
125+ {
126+ var now = DateTimeOffset . Now . ToOffset ( TimeSpan . FromHours ( 1 ) ) ;
127+ var utcNow = DateTimeOffset . UtcNow ;
128+ AreEqual ( now . TruncateTime ( ) , now - now . TimeOfDay ) ;
129+ AreEqual ( utcNow . TruncateTime ( ) , utcNow - utcNow . TimeOfDay ) ;
130+ }
131+
132+ [ Test ]
133+ public static void TestUseLastDayMode ( )
85134 {
86135 var d = new DateTime ( 2017 , 2 , 28 ) ;
87136 AreEqual ( 31 , d . AddMonths ( 1 , true ) . Day , "AddMonths" ) ;
@@ -116,5 +165,180 @@ public static void PreserveOriginalOffset(double offset)
116165 AreEqual ( timespan , d . FirstDayOfYear ( ) . Offset , "#3" ) ;
117166 AreEqual ( timespan , d . LastDayOfYear ( ) . Offset , "#4" ) ;
118167 }
168+
169+ [ Test ]
170+ public static void TestTruncate ( )
171+ {
172+ var date = new DateTime ( 1 , 2 , 3 , 4 , 55 , 6 , DateTimeKind . Local ) ;
173+ var dateOffset = date . ToOffset ( ) ;
174+
175+ AreEqual ( date . AddMilliseconds ( 500 ) . TruncateMilliseconds ( ) , date ) ;
176+ AreEqual ( date . AddMilliseconds ( 499 ) . TruncateMilliseconds ( ) , date ) ;
177+ AreEqual ( date . AddMilliseconds ( 1600 ) . TruncateMilliseconds ( ) , date . AddSeconds ( 1 ) ) ;
178+
179+ AreEqual ( date . Truncate ( TimeSpan . FromDays ( 2 ) ) , date . Date . AddDays ( - 1 ) ) ;
180+ AreEqual ( date . Truncate ( TimeSpan . FromHours ( 1 ) ) , date . Date . AddHours ( 4 ) ) ;
181+ AreEqual ( date . Truncate ( TimeSpan . FromMinutes ( 30 ) ) , date . Date . AddHours ( 4 ) . AddMinutes ( 30 ) ) ;
182+
183+ AreEqual ( dateOffset . AddMilliseconds ( 500 ) . TruncateMilliseconds ( ) , dateOffset ) ;
184+ AreEqual ( dateOffset . AddMilliseconds ( 499 ) . TruncateMilliseconds ( ) , dateOffset ) ;
185+ AreEqual ( dateOffset . AddMilliseconds ( 1600 ) . TruncateMilliseconds ( ) , dateOffset . AddSeconds ( 1 ) ) ;
186+
187+ AreEqual ( dateOffset . Truncate ( TimeSpan . FromDays ( 2 ) ) , dateOffset . TruncateTime ( ) . AddDays ( - 1 ) ) ;
188+ AreEqual ( dateOffset . Truncate ( TimeSpan . FromHours ( 1 ) ) , dateOffset . TruncateTime ( ) . AddHours ( 4 ) ) ;
189+ AreEqual ( dateOffset . Truncate ( TimeSpan . FromMinutes ( 30 ) ) , dateOffset . TruncateTime ( ) . AddHours ( 4 ) . AddMinutes ( 30 ) ) ;
190+ }
191+
192+ [ Test ]
193+ public static void TestCeiling ( )
194+ {
195+ var date = new DateTime ( 1 , 2 , 3 , 4 , 55 , 6 , DateTimeKind . Local ) ;
196+ var dateOffset = date . ToOffset ( ) ;
197+
198+ AreEqual ( date . AddMilliseconds ( 500 ) . CeilingMilliseconds ( ) , date . AddSeconds ( 1 ) ) ;
199+ AreEqual ( date . AddMilliseconds ( 499 ) . CeilingMilliseconds ( ) , date . AddSeconds ( 1 ) ) ;
200+ AreEqual ( date . AddMilliseconds ( 1600 ) . CeilingMilliseconds ( ) , date . AddSeconds ( 2 ) ) ;
201+
202+ AreEqual ( date . Ceiling ( TimeSpan . FromDays ( 2 ) ) , date . Date . AddDays ( 1 ) ) ;
203+ AreEqual ( date . Ceiling ( TimeSpan . FromHours ( 1 ) ) , date . Date . AddHours ( 5 ) ) ;
204+ AreEqual ( date . Ceiling ( TimeSpan . FromMinutes ( 30 ) ) , date . Date . AddHours ( 5 ) ) ;
205+
206+ AreEqual ( dateOffset . AddMilliseconds ( 500 ) . CeilingMilliseconds ( ) , dateOffset . AddSeconds ( 1 ) ) ;
207+ AreEqual ( dateOffset . AddMilliseconds ( 499 ) . CeilingMilliseconds ( ) , dateOffset . AddSeconds ( 1 ) ) ;
208+ AreEqual ( dateOffset . AddMilliseconds ( 1600 ) . CeilingMilliseconds ( ) , dateOffset . AddSeconds ( 2 ) ) ;
209+
210+ AreEqual ( dateOffset . Ceiling ( TimeSpan . FromDays ( 2 ) ) , dateOffset . TruncateTime ( ) . AddDays ( 1 ) ) ;
211+ AreEqual ( dateOffset . Ceiling ( TimeSpan . FromHours ( 1 ) ) , dateOffset . TruncateTime ( ) . AddHours ( 5 ) ) ;
212+ AreEqual ( dateOffset . Ceiling ( TimeSpan . FromMinutes ( 30 ) ) , dateOffset . TruncateTime ( ) . AddHours ( 5 ) ) ;
213+ }
214+
215+ [ Test ]
216+ public static void TestRound ( )
217+ {
218+ var date = new DateTime ( 1 , 2 , 3 , 4 , 55 , 6 , DateTimeKind . Local ) ;
219+ var dateOffset = date . ToOffset ( ) ;
220+
221+ AreEqual ( date . AddMilliseconds ( 500 ) . RoundMilliseconds ( ) , date ) ;
222+ AreEqual ( date . AddMilliseconds ( 499 ) . RoundMilliseconds ( ) , date ) ;
223+ AreEqual ( date . AddMilliseconds ( 1600 ) . RoundMilliseconds ( ) , date . AddSeconds ( 2 ) ) ;
224+
225+ AreEqual ( date . Round ( TimeSpan . FromDays ( 2 ) ) , date . Date . AddDays ( 1 ) ) ;
226+ AreEqual ( date . Round ( TimeSpan . FromHours ( 1 ) ) , date . Date . AddHours ( 5 ) ) ;
227+ AreEqual ( date . Round ( TimeSpan . FromMinutes ( 30 ) ) , date . Date . AddHours ( 5 ) ) ;
228+
229+ AreEqual ( dateOffset . AddMilliseconds ( 500 ) . RoundMilliseconds ( ) , dateOffset ) ;
230+ AreEqual ( dateOffset . AddMilliseconds ( 499 ) . RoundMilliseconds ( ) , dateOffset ) ;
231+ AreEqual ( dateOffset . AddMilliseconds ( 1600 ) . RoundMilliseconds ( ) , dateOffset . AddSeconds ( 2 ) ) ;
232+
233+ AreEqual ( dateOffset . Round ( TimeSpan . FromDays ( 2 ) ) , dateOffset . TruncateTime ( ) . AddDays ( 1 ) ) ;
234+ AreEqual ( dateOffset . Round ( TimeSpan . FromHours ( 1 ) ) , dateOffset . TruncateTime ( ) . AddHours ( 5 ) ) ;
235+ AreEqual ( dateOffset . Round ( TimeSpan . FromMinutes ( 30 ) ) , dateOffset . TruncateTime ( ) . AddHours ( 5 ) ) ;
236+ }
237+
238+ [ TestCase ( MidpointRounding . ToEven , 1 ) ]
239+ [ TestCase ( MidpointRounding . ToEven , 2 ) ]
240+ [ TestCase ( MidpointRounding . ToEven , 10 ) ]
241+ [ TestCase ( MidpointRounding . ToEven , 5 ) ]
242+ [ TestCase ( MidpointRounding . AwayFromZero , 1 ) ]
243+ [ TestCase ( MidpointRounding . AwayFromZero , 2 ) ]
244+ [ TestCase ( MidpointRounding . AwayFromZero , 10 ) ]
245+ [ TestCase ( MidpointRounding . AwayFromZero , 5 ) ]
246+ #if TARGETS_NET || LESSTHAN_NETCOREAPP30
247+ // Some MidpointRounding values are missing if targeting to these frameworks
248+ #else
249+ [ TestCase ( MidpointRounding . ToZero , 1 ) ]
250+ [ TestCase ( MidpointRounding . ToZero , 2 ) ]
251+ [ TestCase ( MidpointRounding . ToZero , 10 ) ]
252+ [ TestCase ( MidpointRounding . ToZero , 5 ) ]
253+ [ TestCase ( MidpointRounding . ToNegativeInfinity , 1 ) ]
254+ [ TestCase ( MidpointRounding . ToNegativeInfinity , 2 ) ]
255+ [ TestCase ( MidpointRounding . ToNegativeInfinity , 10 ) ]
256+ [ TestCase ( MidpointRounding . ToNegativeInfinity , 5 ) ]
257+ [ TestCase ( MidpointRounding . ToPositiveInfinity , 1 ) ]
258+ [ TestCase ( MidpointRounding . ToPositiveInfinity , 2 ) ]
259+ [ TestCase ( MidpointRounding . ToPositiveInfinity , 10 ) ]
260+ [ TestCase ( MidpointRounding . ToPositiveInfinity , 5 ) ]
261+ #endif
262+ public static void TestRounding ( MidpointRounding mode , int value )
263+ {
264+ var date = DateTime . Today . FirstDayOfMonth ( ) ;
265+ var dateDay = DateWithSameOddityDay ( date , value ) ;
266+ var dateHours = date . AddHours ( value ) ;
267+ var dateMinutes = date . AddMinutes ( value ) ;
268+ var dateSeconds = date . AddSeconds ( value ) ;
269+
270+ var minRoundOffset = ( int ) Math . Round ( value + 0.1 , mode ) - value ;
271+ var roundOffset = ( int ) Math . Round ( value + 0.5 , mode ) - value ;
272+ var maxRoundOffset = ( int ) Math . Round ( value + 0.9 , mode ) - value ;
273+
274+ AreEqual ( dateSeconds . AddMilliseconds ( 1 ) . RoundMilliseconds ( mode ) , dateSeconds . AddSeconds ( minRoundOffset ) ) ;
275+ AreEqual ( dateSeconds . AddMilliseconds ( 500 ) . RoundMilliseconds ( mode ) , dateSeconds . AddSeconds ( roundOffset ) ) ;
276+ AreEqual ( dateSeconds . AddMilliseconds ( 999 ) . RoundMilliseconds ( mode ) , dateSeconds . AddSeconds ( maxRoundOffset ) ) ;
277+
278+ AreEqual ( dateMinutes . AddSeconds ( 1 ) . Round ( TimeSpan . FromMinutes ( 1 ) , mode ) , dateMinutes . AddMinutes ( minRoundOffset ) ) ;
279+ AreEqual ( dateMinutes . AddSeconds ( 30 ) . Round ( TimeSpan . FromMinutes ( 1 ) , mode ) , dateMinutes . AddMinutes ( roundOffset ) ) ;
280+ AreEqual ( dateMinutes . AddSeconds ( 59 ) . Round ( TimeSpan . FromMinutes ( 1 ) , mode ) , dateMinutes . AddMinutes ( maxRoundOffset ) ) ;
281+
282+ AreEqual ( dateHours . AddMinutes ( 1 ) . Round ( TimeSpan . FromHours ( 1 ) , mode ) , dateHours . AddHours ( minRoundOffset ) ) ;
283+ AreEqual ( dateHours . AddMinutes ( 30 ) . Round ( TimeSpan . FromHours ( 1 ) , mode ) , dateHours . AddHours ( roundOffset ) ) ;
284+ AreEqual ( dateHours . AddMinutes ( 59 ) . Round ( TimeSpan . FromHours ( 1 ) , mode ) , dateHours . AddHours ( maxRoundOffset ) ) ;
285+
286+ AreEqual ( dateDay . AddHours ( 1 ) . Round ( TimeSpan . FromDays ( 1 ) , mode ) , dateDay . AddDays ( minRoundOffset ) ) ;
287+ AreEqual ( dateDay . AddHours ( 12 ) . Round ( TimeSpan . FromDays ( 1 ) , mode ) , dateDay . AddDays ( roundOffset ) ) ;
288+ AreEqual ( dateDay . AddHours ( 23 ) . Round ( TimeSpan . FromDays ( 1 ) , mode ) , dateDay . AddDays ( maxRoundOffset ) ) ;
289+ }
290+
291+ [ TestCase ( MidpointRounding . ToEven , 1 ) ]
292+ [ TestCase ( MidpointRounding . ToEven , 2 ) ]
293+ [ TestCase ( MidpointRounding . ToEven , 10 ) ]
294+ [ TestCase ( MidpointRounding . ToEven , 5 ) ]
295+ [ TestCase ( MidpointRounding . AwayFromZero , 1 ) ]
296+ [ TestCase ( MidpointRounding . AwayFromZero , 2 ) ]
297+ [ TestCase ( MidpointRounding . AwayFromZero , 10 ) ]
298+ [ TestCase ( MidpointRounding . AwayFromZero , 5 ) ]
299+ #if TARGETS_NET || LESSTHAN_NETCOREAPP30
300+ // Some MidpointRounding values are missing if targeting to these frameworks
301+ #else
302+ [ TestCase ( MidpointRounding . ToZero , 1 ) ]
303+ [ TestCase ( MidpointRounding . ToZero , 2 ) ]
304+ [ TestCase ( MidpointRounding . ToZero , 10 ) ]
305+ [ TestCase ( MidpointRounding . ToZero , 5 ) ]
306+ [ TestCase ( MidpointRounding . ToNegativeInfinity , 1 ) ]
307+ [ TestCase ( MidpointRounding . ToNegativeInfinity , 2 ) ]
308+ [ TestCase ( MidpointRounding . ToNegativeInfinity , 10 ) ]
309+ [ TestCase ( MidpointRounding . ToNegativeInfinity , 5 ) ]
310+ [ TestCase ( MidpointRounding . ToPositiveInfinity , 1 ) ]
311+ [ TestCase ( MidpointRounding . ToPositiveInfinity , 2 ) ]
312+ [ TestCase ( MidpointRounding . ToPositiveInfinity , 10 ) ]
313+ [ TestCase ( MidpointRounding . ToPositiveInfinity , 5 ) ]
314+ #endif
315+ public static void TestRoundingOffset ( MidpointRounding mode , int value )
316+ {
317+ var date = DateTime . Today . FirstDayOfMonth ( ) . ToOffset ( ) ;
318+ var dateDay = DateWithSameOddityDay ( date , value ) ;
319+ var dateHours = date . AddHours ( value ) ;
320+ var dateMinutes = date . AddMinutes ( value ) ;
321+ var dateSeconds = date . AddSeconds ( value ) ;
322+
323+ var minRoundOffset = ( int ) Math . Round ( value + 0.1 , mode ) - value ;
324+ var roundOffset = ( int ) Math . Round ( value + 0.5 , mode ) - value ;
325+ var maxRoundOffset = ( int ) Math . Round ( value + 0.9 , mode ) - value ;
326+
327+ AreEqual ( dateSeconds . AddMilliseconds ( 1 ) . RoundMilliseconds ( mode ) , dateSeconds . AddSeconds ( minRoundOffset ) ) ;
328+ AreEqual ( dateSeconds . AddMilliseconds ( 500 ) . RoundMilliseconds ( mode ) , dateSeconds . AddSeconds ( roundOffset ) ) ;
329+ AreEqual ( dateSeconds . AddMilliseconds ( 999 ) . RoundMilliseconds ( mode ) , dateSeconds . AddSeconds ( maxRoundOffset ) ) ;
330+
331+ AreEqual ( dateMinutes . AddSeconds ( 1 ) . Round ( TimeSpan . FromMinutes ( 1 ) , mode ) , dateMinutes . AddMinutes ( minRoundOffset ) ) ;
332+ AreEqual ( dateMinutes . AddSeconds ( 30 ) . Round ( TimeSpan . FromMinutes ( 1 ) , mode ) , dateMinutes . AddMinutes ( roundOffset ) ) ;
333+ AreEqual ( dateMinutes . AddSeconds ( 59 ) . Round ( TimeSpan . FromMinutes ( 1 ) , mode ) , dateMinutes . AddMinutes ( maxRoundOffset ) ) ;
334+
335+ AreEqual ( dateHours . AddMinutes ( 1 ) . Round ( TimeSpan . FromHours ( 1 ) , mode ) , dateHours . AddHours ( minRoundOffset ) ) ;
336+ AreEqual ( dateHours . AddMinutes ( 30 ) . Round ( TimeSpan . FromHours ( 1 ) , mode ) , dateHours . AddHours ( roundOffset ) ) ;
337+ AreEqual ( dateHours . AddMinutes ( 59 ) . Round ( TimeSpan . FromHours ( 1 ) , mode ) , dateHours . AddHours ( maxRoundOffset ) ) ;
338+
339+ AreEqual ( dateDay . AddHours ( 1 ) . Round ( TimeSpan . FromDays ( 1 ) , mode ) , dateDay . AddDays ( minRoundOffset ) ) ;
340+ AreEqual ( dateDay . AddHours ( 12 ) . Round ( TimeSpan . FromDays ( 1 ) , mode ) , dateDay . AddDays ( roundOffset ) ) ;
341+ AreEqual ( dateDay . AddHours ( 23 ) . Round ( TimeSpan . FromDays ( 1 ) , mode ) , dateDay . AddDays ( maxRoundOffset ) ) ;
342+ }
119343 }
120344}
0 commit comments