@@ -511,4 +511,250 @@ public void Parse_ComplexExpression_MultipleOperationsWithRounding()
511511 // Should not equal base time
512512 Assert . NotEqual ( _baseTime , result ) ;
513513 }
514+
515+ [ Fact ]
516+ public void Parse_WithTimeZoneInfo_Now_ReturnsCurrentTimeInSpecifiedTimezone ( )
517+ {
518+ var utcTimeZone = TimeZoneInfo . Utc ;
519+ const string expression = "now" ;
520+
521+ _logger . LogDebug ( "Testing Parse with TimeZoneInfo for expression: '{Expression}', TimeZone: {TimeZone}" ,
522+ expression , utcTimeZone . Id ) ;
523+
524+ var result = DateMath . Parse ( expression , utcTimeZone ) ;
525+
526+ _logger . LogDebug ( "Parse result: {Result}" , result ) ;
527+
528+ // Should be close to current UTC time
529+ var utcNow = DateTimeOffset . UtcNow ;
530+ Assert . True ( Math . Abs ( ( result - utcNow ) . TotalSeconds ) < 5 ,
531+ $ "Result { result } should be within 5 seconds of UTC now { utcNow } ") ;
532+ Assert . Equal ( TimeSpan . Zero , result . Offset ) ; // Should be UTC
533+ }
534+
535+ [ Theory ]
536+ [ InlineData ( "UTC" , 0 ) ]
537+ [ InlineData ( "US/Eastern" , - 5 ) ] // EST offset (not considering DST for this test)
538+ [ InlineData ( "US/Pacific" , - 8 ) ] // PST offset (not considering DST for this test)
539+ public void Parse_WithTimeZoneInfo_Now_ReturnsCorrectTimezone ( string timeZoneId , int expectedOffsetHours )
540+ {
541+ var timeZone = TimeZoneInfo . FindSystemTimeZoneById ( timeZoneId ) ;
542+ const string expression = "now" ;
543+
544+ _logger . LogDebug ( "Testing Parse with TimeZoneInfo for expression: '{Expression}', TimeZone: {TimeZone}" ,
545+ expression , timeZone . Id ) ;
546+
547+ var result = DateMath . Parse ( expression , timeZone ) ;
548+
549+ _logger . LogDebug ( "Parse result: {Result}, Expected offset hours: {ExpectedOffsetHours}" , result , expectedOffsetHours ) ;
550+
551+ // Note: This test might need adjustment for DST, but it demonstrates the concept
552+ Assert . Equal ( timeZone . GetUtcOffset ( DateTime . UtcNow ) , result . Offset ) ;
553+ }
554+
555+ [ Fact ]
556+ public void Parse_WithTimeZoneInfo_ExplicitDateWithoutTimezone_UsesSpecifiedTimezone ( )
557+ {
558+ var easternTimeZone = TimeZoneInfo . FindSystemTimeZoneById ( "US/Eastern" ) ;
559+ const string expression = "2023-06-15T14:30:00||" ;
560+
561+ _logger . LogDebug ( "Testing Parse with TimeZoneInfo for expression: '{Expression}', TimeZone: {TimeZone}" ,
562+ expression , easternTimeZone . Id ) ;
563+
564+ var result = DateMath . Parse ( expression , easternTimeZone ) ;
565+
566+ _logger . LogDebug ( "Parse result: {Result}" , result ) ;
567+
568+ Assert . Equal ( 2023 , result . Year ) ;
569+ Assert . Equal ( 6 , result . Month ) ;
570+ Assert . Equal ( 15 , result . Day ) ;
571+ Assert . Equal ( 14 , result . Hour ) ;
572+ Assert . Equal ( 30 , result . Minute ) ;
573+ Assert . Equal ( 0 , result . Second ) ;
574+
575+ // Should use the timezone offset from Eastern Time
576+ var expectedOffset = easternTimeZone . GetUtcOffset ( new DateTime ( 2023 , 6 , 15 , 14 , 30 , 0 ) ) ;
577+ Assert . Equal ( expectedOffset , result . Offset ) ;
578+ }
579+
580+ [ Fact ]
581+ public void Parse_WithTimeZoneInfo_ExplicitDateWithTimezone_PreservesOriginalTimezone ( )
582+ {
583+ var pacificTimeZone = TimeZoneInfo . FindSystemTimeZoneById ( "US/Pacific" ) ;
584+ const string expression = "2023-06-15T14:30:00+05:00||" ; // Explicit +05:00 timezone
585+
586+ _logger . LogDebug ( "Testing Parse with TimeZoneInfo for expression: '{Expression}', TimeZone: {TimeZone}" ,
587+ expression , pacificTimeZone . Id ) ;
588+
589+ var result = DateMath . Parse ( expression , pacificTimeZone ) ;
590+
591+ _logger . LogDebug ( "Parse result: {Result}" , result ) ;
592+
593+ Assert . Equal ( 2023 , result . Year ) ;
594+ Assert . Equal ( 6 , result . Month ) ;
595+ Assert . Equal ( 15 , result . Day ) ;
596+ Assert . Equal ( 14 , result . Hour ) ;
597+ Assert . Equal ( 30 , result . Minute ) ;
598+ Assert . Equal ( 0 , result . Second ) ;
599+
600+ // Should preserve the original +05:00 timezone, not use Pacific
601+ Assert . Equal ( TimeSpan . FromHours ( 5 ) , result . Offset ) ;
602+ }
603+
604+ [ Theory ]
605+ [ InlineData ( "now+1h" , 1 ) ]
606+ [ InlineData ( "now+6h" , 6 ) ]
607+ [ InlineData ( "now-2h" , - 2 ) ]
608+ [ InlineData ( "now+24h" , 24 ) ]
609+ public void Parse_WithTimeZoneInfo_HourOperations_ReturnsCorrectResult ( string expression , int hours )
610+ {
611+ var utcTimeZone = TimeZoneInfo . Utc ;
612+
613+ _logger . LogDebug ( "Testing Parse with TimeZoneInfo for expression: '{Expression}', TimeZone: {TimeZone}, Hours: {Hours}" ,
614+ expression , utcTimeZone . Id , hours ) ;
615+
616+ var result = DateMath . Parse ( expression , utcTimeZone ) ;
617+ var utcNow = DateTimeOffset . UtcNow ;
618+ var expected = utcNow . AddHours ( hours ) ;
619+
620+ _logger . LogDebug ( "Parse result: {Result}, Expected: approximately {Expected}" , result , expected ) ;
621+
622+ // Should be close to expected time (within 5 seconds to account for execution time)
623+ Assert . True ( Math . Abs ( ( result - expected ) . TotalSeconds ) < 5 ,
624+ $ "Result { result } should be within 5 seconds of expected { expected } ") ;
625+ Assert . Equal ( TimeSpan . Zero , result . Offset ) ; // Should be UTC
626+ }
627+
628+ [ Theory ]
629+ [ InlineData ( "now/d" , false ) ]
630+ [ InlineData ( "now/d" , true ) ]
631+ [ InlineData ( "now/h" , false ) ]
632+ [ InlineData ( "now/h" , true ) ]
633+ [ InlineData ( "now/M" , false ) ]
634+ [ InlineData ( "now/M" , true ) ]
635+ public void Parse_WithTimeZoneInfo_RoundingOperations_ReturnsCorrectResult ( string expression , bool isUpperLimit )
636+ {
637+ var centralTimeZone = TimeZoneInfo . FindSystemTimeZoneById ( "US/Central" ) ;
638+
639+ _logger . LogDebug ( "Testing Parse with TimeZoneInfo for expression: '{Expression}', TimeZone: {TimeZone}, IsUpperLimit: {IsUpperLimit}" ,
640+ expression , centralTimeZone . Id , isUpperLimit ) ;
641+
642+ var result = DateMath . Parse ( expression , centralTimeZone , isUpperLimit ) ;
643+
644+ _logger . LogDebug ( "Parse result: {Result}" , result ) ;
645+
646+ // Verify the result uses Central Time offset
647+ var expectedOffset = centralTimeZone . GetUtcOffset ( DateTime . UtcNow ) ;
648+ Assert . Equal ( expectedOffset , result . Offset ) ;
649+
650+ // Verify rounding behavior
651+ if ( expression . EndsWith ( "/d" ) )
652+ {
653+ if ( isUpperLimit )
654+ {
655+ Assert . Equal ( 23 , result . Hour ) ;
656+ Assert . Equal ( 59 , result . Minute ) ;
657+ Assert . Equal ( 59 , result . Second ) ;
658+ }
659+ else
660+ {
661+ Assert . Equal ( 0 , result . Hour ) ;
662+ Assert . Equal ( 0 , result . Minute ) ;
663+ Assert . Equal ( 0 , result . Second ) ;
664+ }
665+ }
666+ else if ( expression . EndsWith ( "/h" ) )
667+ {
668+ if ( isUpperLimit )
669+ {
670+ Assert . Equal ( 59 , result . Minute ) ;
671+ Assert . Equal ( 59 , result . Second ) ;
672+ }
673+ else
674+ {
675+ Assert . Equal ( 0 , result . Minute ) ;
676+ Assert . Equal ( 0 , result . Second ) ;
677+ }
678+ }
679+ }
680+
681+ [ Fact ]
682+ public void TryParse_WithTimeZoneInfo_ValidExpression_ReturnsTrue ( )
683+ {
684+ var mountainTimeZone = TimeZoneInfo . FindSystemTimeZoneById ( "US/Mountain" ) ;
685+ const string expression = "now+2d" ;
686+
687+ _logger . LogDebug ( "Testing TryParse with TimeZoneInfo for expression: '{Expression}', TimeZone: {TimeZone}" ,
688+ expression , mountainTimeZone . Id ) ;
689+
690+ bool success = DateMath . TryParse ( expression , mountainTimeZone , false , out DateTimeOffset result ) ;
691+
692+ _logger . LogDebug ( "TryParse success: {Success}, Result: {Result}" , success , result ) ;
693+
694+ Assert . True ( success ) ;
695+ Assert . NotEqual ( default ( DateTimeOffset ) , result ) ;
696+
697+ // Should use Mountain Time offset
698+ var expectedOffset = mountainTimeZone . GetUtcOffset ( DateTime . UtcNow ) ;
699+ Assert . Equal ( expectedOffset , result . Offset ) ;
700+ }
701+
702+ [ Fact ]
703+ public void TryParse_WithTimeZoneInfo_InvalidExpression_ReturnsFalse ( )
704+ {
705+ var utcTimeZone = TimeZoneInfo . Utc ;
706+ const string expression = "invalid_expression" ;
707+
708+ _logger . LogDebug ( "Testing TryParse with TimeZoneInfo for invalid expression: '{Expression}', TimeZone: {TimeZone}" ,
709+ expression , utcTimeZone . Id ) ;
710+
711+ bool success = DateMath . TryParse ( expression , utcTimeZone , false , out DateTimeOffset result ) ;
712+
713+ _logger . LogDebug ( "TryParse success: {Success}, Result: {Result}" , success , result ) ;
714+
715+ Assert . False ( success ) ;
716+ Assert . Equal ( default ( DateTimeOffset ) , result ) ;
717+ }
718+
719+ [ Fact ]
720+ public void Parse_WithTimeZoneInfo_ComplexExpression_WorksCorrectly ( )
721+ {
722+ var utcTimeZone = TimeZoneInfo . Utc ;
723+ const string expression = "now+1M-2d+3h/h" ;
724+
725+ _logger . LogDebug ( "Testing Parse with TimeZoneInfo for complex expression: '{Expression}', TimeZone: {TimeZone}" ,
726+ expression , utcTimeZone . Id ) ;
727+
728+ var result = DateMath . Parse ( expression , utcTimeZone , false ) ;
729+
730+ _logger . LogDebug ( "Parse result: {Result}" , result ) ;
731+
732+ // Should be UTC
733+ Assert . Equal ( TimeSpan . Zero , result . Offset ) ;
734+
735+ // Should be rounded to start of hour
736+ Assert . Equal ( 0 , result . Minute ) ;
737+ Assert . Equal ( 0 , result . Second ) ;
738+ Assert . Equal ( 0 , result . Millisecond ) ;
739+ }
740+
741+ [ Fact ]
742+ public void Parse_WithTimeZoneInfo_NullTimeZone_ThrowsArgumentNullException ( )
743+ {
744+ const string expression = "now" ;
745+
746+ _logger . LogDebug ( "Testing Parse with null TimeZoneInfo for expression: '{Expression}'" , expression ) ;
747+
748+ Assert . Throws < ArgumentNullException > ( ( ) => DateMath . Parse ( expression , ( TimeZoneInfo ) null ! ) ) ;
749+ }
750+
751+ [ Fact ]
752+ public void TryParse_WithTimeZoneInfo_NullTimeZone_ThrowsArgumentNullException ( )
753+ {
754+ const string expression = "now" ;
755+
756+ _logger . LogDebug ( "Testing TryParse with null TimeZoneInfo for expression: '{Expression}'" , expression ) ;
757+
758+ Assert . Throws < ArgumentNullException > ( ( ) => DateMath . TryParse ( expression , ( TimeZoneInfo ) null ! , false , out _ ) ) ;
759+ }
514760}
0 commit comments