@@ -68,8 +68,9 @@ public void CanParseNamedRanges(string input, DateTime start, DateTime end)
6868 Assert . Equal ( end , range . End ) ;
6969 }
7070
71- public static IEnumerable < object [ ] > Inputs => new [ ] {
72- new object [ ] { "today" , _now . StartOfDay ( ) , _now . EndOfDay ( ) } ,
71+ public static IEnumerable < object [ ] > Inputs =>
72+ [
73+ [ "today" , _now . StartOfDay ( ) , _now . EndOfDay ( ) ] ,
7374 [ "yesterday" , _now . SubtractDays ( 1 ) . StartOfDay ( ) , _now . SubtractDays ( 1 ) . EndOfDay ( ) ] ,
7475 [ "tomorrow" , _now . AddDays ( 1 ) . StartOfDay ( ) , _now . AddDays ( 1 ) . EndOfDay ( ) ] ,
7576 [ "last 5 minutes" , _now . SubtractMinutes ( 5 ) . StartOfMinute ( ) , _now ] ,
@@ -83,6 +84,272 @@ public void CanParseNamedRanges(string input, DateTime start, DateTime end)
8384 [ "next nov" , _now . AddYears ( 1 ) . StartOfMonth ( ) , _now . AddYears ( 1 ) . EndOfMonth ( ) ] ,
8485 [ "next jan" , _now . AddYears ( 1 ) . ChangeMonth ( 1 ) . StartOfMonth ( ) , _now . AddYears ( 1 ) . ChangeMonth ( 1 ) . EndOfMonth ( ) ] ,
8586 [ "jan-feb" , _now . ChangeMonth ( 1 ) . StartOfMonth ( ) , _now . ChangeMonth ( 2 ) . EndOfMonth ( ) ] ,
86- [ "now-this feb" , _now , _now . AddYears ( 1 ) . ChangeMonth ( 2 ) . EndOfMonth ( ) ]
87- } ;
87+ [ "now-this feb" , _now , _now . AddYears ( 1 ) . ChangeMonth ( 2 ) . EndOfMonth ( ) ] ,
88+
89+ // Date math expressions without brackets (testing if they work)
90+ [ "now-6h TO now" , _now . AddHours ( - 6 ) , _now ] ,
91+ [ "now-1d TO now" , _now . AddDays ( - 1 ) , _now ] ,
92+
93+ // Bracket notation with date math (currently failing - documenting expected behavior)
94+ [ "[now-6h TO now]" , _now . AddHours ( - 6 ) , _now ] ,
95+ [ "[now-1d TO now]" , _now . AddDays ( - 1 ) , _now ] ,
96+ [ "[now-30m TO now]" , _now . AddMinutes ( - 30 ) , _now ] ,
97+ [ "[now TO now+2h]" , _now , _now . AddHours ( 2 ) ] ,
98+ [ "[now-1h TO now+1h]" , _now . AddHours ( - 1 ) , _now . AddHours ( 1 ) ] ,
99+
100+ // Curly brace notation with date math (currently failing - documenting expected behavior)
101+ [ "{now-6h TO now}" , _now . AddHours ( - 6 ) , _now ] ,
102+ [ "{now-1d TO now}" , _now . AddDays ( - 1 ) , _now ] ,
103+
104+ // Mixed expressions with brackets (currently failing - documenting expected behavior)
105+ [ "[yesterday TO now]" , _now . SubtractDays ( 1 ) . StartOfDay ( ) , _now ] ,
106+ [ "[now-1w TO today]" , _now . AddDays ( - 7 ) , _now . EndOfDay ( ) ]
107+ ] ;
108+
109+ [ Fact ]
110+ public void Parse_Yesterday_ReturnsFullDayRange ( )
111+ {
112+ // Arrange
113+ var baseTime = new DateTime ( 2023 , 12 , 25 , 12 , 0 , 0 ) ;
114+ var expected = baseTime . AddDays ( - 1 ) . StartOfDay ( ) ;
115+
116+ // Act
117+ var range = DateTimeRange . Parse ( "yesterday" , baseTime ) ;
118+
119+ // Assert
120+ Assert . NotEqual ( DateTimeRange . Empty , range ) ;
121+ Assert . Equal ( expected , range . Start ) ;
122+ Assert . Equal ( expected . EndOfDay ( ) , range . End ) ;
123+ }
124+
125+ [ Fact ]
126+ public void Parse_Today_ReturnsFullDayRange ( )
127+ {
128+ // Arrange
129+ var baseTime = new DateTime ( 2023 , 12 , 25 , 12 , 0 , 0 ) ;
130+ var expected = baseTime . StartOfDay ( ) ;
131+
132+ // Act
133+ var range = DateTimeRange . Parse ( "today" , baseTime ) ;
134+
135+ // Assert
136+ Assert . NotEqual ( DateTimeRange . Empty , range ) ;
137+ Assert . Equal ( expected , range . Start ) ;
138+ Assert . Equal ( expected . EndOfDay ( ) , range . End ) ;
139+ }
140+
141+ [ Fact ]
142+ public void Parse_Tomorrow_ReturnsFullDayRange ( )
143+ {
144+ // Arrange
145+ var baseTime = new DateTime ( 2023 , 12 , 25 , 12 , 0 , 0 ) ;
146+ var expected = baseTime . AddDays ( 1 ) . StartOfDay ( ) ;
147+
148+ // Act
149+ var range = DateTimeRange . Parse ( "tomorrow" , baseTime ) ;
150+
151+ // Assert
152+ Assert . NotEqual ( DateTimeRange . Empty , range ) ;
153+ Assert . Equal ( expected , range . Start ) ;
154+ Assert . Equal ( expected . EndOfDay ( ) , range . End ) ;
155+ }
156+
157+ [ Fact ]
158+ public void Parse_LastFiveMinutes_ReturnsPastTimeRange ( )
159+ {
160+ // Arrange
161+ var baseTime = new DateTime ( 2023 , 12 , 25 , 12 , 0 , 0 ) ;
162+ var expectedStart = baseTime . AddMinutes ( - 5 ) . StartOfMinute ( ) ;
163+
164+ // Act
165+ var range = DateTimeRange . Parse ( "last 5 minutes" , baseTime ) ;
166+
167+ // Assert
168+ Assert . NotEqual ( DateTimeRange . Empty , range ) ;
169+ Assert . Equal ( expectedStart , range . Start ) ;
170+ Assert . Equal ( baseTime , range . End ) ;
171+ }
172+
173+ [ Fact ]
174+ public void Parse_NextTwoHours_ReturnsFutureTimeRange ( )
175+ {
176+ // Arrange
177+ var baseTime = new DateTime ( 2023 , 12 , 25 , 12 , 0 , 0 ) ;
178+ var expectedEnd = baseTime . AddHours ( 2 ) . EndOfHour ( ) ;
179+
180+ // Act
181+ var range = DateTimeRange . Parse ( "next 2 hours" , baseTime ) ;
182+
183+ // Assert
184+ Assert . NotEqual ( DateTimeRange . Empty , range ) ;
185+ Assert . Equal ( baseTime , range . Start ) ;
186+ Assert . Equal ( expectedEnd , range . End ) ;
187+ }
188+
189+ [ Fact ]
190+ public void Parse_BracketNotationWithDateMath_ParsesCorrectly ( )
191+ {
192+ // Arrange
193+ var baseTime = new DateTime ( 2023 , 12 , 25 , 12 , 0 , 0 ) ;
194+
195+ // Act
196+ var range = DateTimeRange . Parse ( "[now-6h TO now]" , baseTime ) ;
197+
198+ // Assert
199+ Assert . NotEqual ( DateTimeRange . Empty , range ) ;
200+ Assert . NotEqual ( DateTime . MinValue , range . Start ) ;
201+ Assert . NotEqual ( DateTime . MinValue , range . End ) ;
202+ Assert . True ( range . Start < range . End ) ;
203+
204+ // Verify 'now' resolves to base time
205+ var tolerance = TimeSpan . FromMinutes ( 1 ) ;
206+ Assert . True ( Math . Abs ( ( range . End - baseTime ) . TotalMinutes ) < tolerance . TotalMinutes ) ;
207+
208+ // Verify the 6-hour span
209+ var expectedStart = baseTime . AddHours ( - 6 ) ;
210+ Assert . True ( Math . Abs ( ( range . Start - expectedStart ) . TotalMinutes ) < tolerance . TotalMinutes ) ;
211+ }
212+
213+ [ Fact ]
214+ public void Parse_CurlyBraceNotationWithDateMath_ParsesCorrectly ( )
215+ {
216+ // Arrange
217+ var baseTime = new DateTime ( 2023 , 12 , 25 , 12 , 0 , 0 ) ;
218+
219+ // Act
220+ var range = DateTimeRange . Parse ( "{now-1d TO now}" , baseTime ) ;
221+
222+ // Assert
223+ Assert . NotEqual ( DateTimeRange . Empty , range ) ;
224+ Assert . NotEqual ( DateTime . MinValue , range . Start ) ;
225+ Assert . NotEqual ( DateTime . MinValue , range . End ) ;
226+ Assert . True ( range . Start < range . End ) ;
227+
228+ // Verify 'now' resolves to base time
229+ var tolerance = TimeSpan . FromMinutes ( 1 ) ;
230+ Assert . True ( Math . Abs ( ( range . End - baseTime ) . TotalMinutes ) < tolerance . TotalMinutes ) ;
231+
232+ // Verify the 1-day span
233+ var expectedStart = baseTime . AddDays ( - 1 ) ;
234+ Assert . True ( Math . Abs ( ( range . Start - expectedStart ) . TotalMinutes ) < tolerance . TotalMinutes ) ;
235+ }
236+
237+ [ Fact ]
238+ public void Parse_DateMathWithoutBrackets_ParsesCorrectly ( )
239+ {
240+ // Arrange
241+ var baseTime = new DateTime ( 2023 , 12 , 25 , 12 , 0 , 0 ) ;
242+
243+ // Act
244+ var range = DateTimeRange . Parse ( "now-6h TO now" , baseTime ) ;
245+
246+ // Assert
247+ Assert . NotEqual ( DateTimeRange . Empty , range ) ;
248+ Assert . NotEqual ( DateTime . MinValue , range . Start ) ;
249+ Assert . NotEqual ( DateTime . MinValue , range . End ) ;
250+ Assert . True ( range . Start < range . End ) ;
251+
252+ // Verify 'now' resolves to base time
253+ var tolerance = TimeSpan . FromMinutes ( 1 ) ;
254+ Assert . True ( Math . Abs ( ( range . End - baseTime ) . TotalMinutes ) < tolerance . TotalMinutes ) ;
255+ }
256+
257+ [ Fact ]
258+ public void Parse_MixedParsersInBracketNotation_ParsesCorrectly ( )
259+ {
260+ // Arrange
261+ var baseTime = new DateTime ( 2023 , 12 , 25 , 12 , 0 , 0 ) ;
262+
263+ // Act
264+ var range = DateTimeRange . Parse ( "[yesterday TO today]" , baseTime ) ;
265+
266+ // Assert
267+ Assert . NotEqual ( DateTimeRange . Empty , range ) ;
268+ Assert . NotEqual ( DateTime . MinValue , range . Start ) ;
269+ Assert . NotEqual ( DateTime . MinValue , range . End ) ;
270+ Assert . True ( range . Start < range . End ) ;
271+
272+ // Verify yesterday and today are parsed correctly
273+ var expectedStart = baseTime . AddDays ( - 1 ) . StartOfDay ( ) ;
274+ var expectedEnd = baseTime . StartOfDay ( ) . EndOfDay ( ) ;
275+ Assert . Equal ( expectedStart , range . Start ) ;
276+ Assert . Equal ( expectedEnd , range . End ) ;
277+ }
278+
279+ [ Fact ]
280+ public void Parse_DateMathStartAndEnd_ParsesCorrectly ( )
281+ {
282+ // Arrange
283+ var baseTime = new DateTime ( 2023 , 12 , 25 , 12 , 0 , 0 ) ;
284+
285+ // Act
286+ var range = DateTimeRange . Parse ( "now-1h TO now+1h" , baseTime ) ;
287+
288+ // Assert
289+ Assert . NotEqual ( DateTimeRange . Empty , range ) ;
290+ Assert . NotEqual ( DateTime . MinValue , range . Start ) ;
291+ Assert . NotEqual ( DateTime . MinValue , range . End ) ;
292+ Assert . True ( range . Start < range . End ) ;
293+
294+ // Verify the 2-hour span centered on base time
295+ var tolerance = TimeSpan . FromMinutes ( 1 ) ;
296+ var expectedStart = baseTime . AddHours ( - 1 ) ;
297+ var expectedEnd = baseTime . AddHours ( 1 ) ;
298+ Assert . True ( Math . Abs ( ( range . Start - expectedStart ) . TotalMinutes ) < tolerance . TotalMinutes ) ;
299+ Assert . True ( Math . Abs ( ( range . End - expectedEnd ) . TotalMinutes ) < tolerance . TotalMinutes ) ;
300+ }
301+
302+ [ Theory ]
303+ [ InlineData ( "now-6h TO now+6h" , 12 , "DateMath expressions should create 12-hour range" ) ]
304+ [ InlineData ( "now-1d TO now" , 24 , "DateMath expressions should create 24-hour range" ) ]
305+ [ InlineData ( "now TO now+30m" , 0.5 , "DateMath expressions should create 30-minute range" ) ]
306+ public void Parse_DateMathExpressions_CreatesCorrectTimeSpans ( string input , double expectedHours , string reason )
307+ {
308+ // Arrange
309+ var baseTime = new DateTime ( 2023 , 12 , 25 , 12 , 0 , 0 ) ;
310+
311+ // Act
312+ var range = DateTimeRange . Parse ( input , baseTime ) ;
313+
314+ // Assert
315+ Assert . NotEqual ( DateTimeRange . Empty , range ) ;
316+ var actualHours = ( range . End - range . Start ) . TotalHours ;
317+ Assert . True ( Math . Abs ( actualHours - expectedHours ) < 0.1 ,
318+ $ "{ reason } . Expected { expectedHours } hours, but got { actualHours } hours") ;
319+ }
320+
321+ [ Fact ]
322+ public void Parse_BracketNotationWithDateMath_PreservesTimeZoneInformation ( )
323+ {
324+ // Arrange
325+ var baseTime = new DateTimeOffset ( 2023 , 12 , 25 , 12 , 0 , 0 , TimeSpan . FromHours ( - 5 ) ) ;
326+ const string input = "[now-6h TO now]" ;
327+
328+ // Act
329+ var range = DateTimeRange . Parse ( input , baseTime ) ;
330+
331+ // Assert
332+ Assert . NotEqual ( DateTimeRange . Empty , range ) ;
333+ Assert . Equal ( baseTime . AddHours ( - 6 ) . DateTime , range . Start ) ;
334+ Assert . Equal ( baseTime . DateTime , range . End ) ;
335+ }
336+
337+ [ Theory ]
338+ [ InlineData ( "now+1INVALID" , "Invalid unit should not parse" ) ]
339+ [ InlineData ( "now+" , "Incomplete expression should not parse" ) ]
340+ [ InlineData ( "now++1d" , "Double operators should not parse" ) ]
341+ [ InlineData ( "[now+ TO now]" , "Invalid left side in bracket notation should not parse" ) ]
342+ public void Parse_InvalidDateMathExpressions_ReturnsEmptyRange ( string input , string reason )
343+ {
344+ // Arrange
345+ var baseTime = new DateTime ( 2023 , 12 , 25 , 12 , 0 , 0 ) ;
346+
347+ // Act
348+ var range = DateTimeRange . Parse ( input , baseTime ) ;
349+
350+ // Assert - Invalid expressions should result in empty range or fallback parsing
351+ // The behavior may vary based on parser priority and fallback mechanisms
352+ Assert . True ( range == DateTimeRange . Empty || range . Start != DateTime . MinValue ,
353+ $ "{ reason } . Input '{ input } ' should either return empty range or valid fallback parsing") ;
354+ }
88355}
0 commit comments