Skip to content

Commit d2e8963

Browse files
MaximysMaksim Golev
andauthored
Fix invalid datetimeoffset parsing (#87801)
Co-authored-by: Maksim Golev <[email protected]>
1 parent cfe30c0 commit d2e8963

File tree

2 files changed

+21
-11
lines changed

2 files changed

+21
-11
lines changed

src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ internal static class DateTimeParse
1313
{
1414
internal const int MaxDateTimeNumberDigits = 8;
1515

16+
internal const char TimeDelimiter = ':';
17+
internal const char TimeFractionDelimiterComma = ',';
18+
internal const char TimeFractionDelimiterDot = '.';
19+
1620
internal static DateTime ParseExact(ReadOnlySpan<char> s, ReadOnlySpan<char> format, DateTimeFormatInfo dtfi, DateTimeStyles style)
1721
{
1822
DateTimeResult result = default; // The buffer to store the parsing result.
@@ -521,7 +525,7 @@ private static bool ParseTimeZone(ref __DTString str, scoped ref TimeSpan result
521525
str.ConsumeSubString(sub);
522526
// See if we have minutes
523527
sub = str.GetSubString();
524-
if (sub.length == 1 && sub[0] == ':')
528+
if (sub.length == 1 && sub[0] == TimeDelimiter)
525529
{
526530
// Parsing "+8:00" or "+08:00"
527531
str.ConsumeSubString(sub);
@@ -642,7 +646,8 @@ private static bool Lex(DS dps, ref __DTString str, scoped ref DateTimeToken dto
642646
if (str.Index < str.Length - 1)
643647
{
644648
char nextCh = str.Value[str.Index];
645-
if (nextCh == '.')
649+
if ((nextCh == TimeFractionDelimiterDot)
650+
|| (nextCh == TimeFractionDelimiterComma))
646651
{
647652
// While ParseFraction can fail, it just means that there were no digits after
648653
// the dot. In this case ParseFraction just removes the dot. This is actually
@@ -2961,7 +2966,7 @@ private static bool ParseISO8601(scoped ref DateTimeRawInfo raw, ref __DTString
29612966
return false;
29622967
}
29632968
str.SkipWhiteSpaces();
2964-
if (!str.Match(':'))
2969+
if (!str.Match(TimeDelimiter))
29652970
{
29662971
result.SetBadDateTimeFailure();
29672972
return false;
@@ -2973,15 +2978,16 @@ private static bool ParseISO8601(scoped ref DateTimeRawInfo raw, ref __DTString
29732978
return false;
29742979
}
29752980
str.SkipWhiteSpaces();
2976-
if (str.Match(':'))
2981+
if (str.Match(TimeDelimiter))
29772982
{
29782983
str.SkipWhiteSpaces();
29792984
if (!ParseDigits(ref str, 2, out second))
29802985
{
29812986
result.SetBadDateTimeFailure();
29822987
return false;
29832988
}
2984-
if (str.Match('.'))
2989+
if ((str.Match(TimeFractionDelimiterDot))
2990+
|| (str.Match(TimeFractionDelimiterComma)))
29852991
{
29862992
if (!ParseFraction(ref str, out partSecond))
29872993
{

src/libraries/System.Runtime/tests/System/DateTimeOffsetTests.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -856,14 +856,18 @@ public static void Compare(DateTimeOffset dateTimeOffset1, DateTimeOffset dateTi
856856
}
857857
}
858858

859-
[Fact]
860-
public static void Parse_String()
859+
public static IEnumerable<object[]> Parse_TestData()
861860
{
862-
DateTimeOffset expected = DateTimeOffset.MaxValue;
863-
string expectedString = expected.ToString();
861+
yield return new object[] { "2021-04-23T13:04:17,307642270+02:00", new DateTimeOffset(637547798573076423, new TimeSpan(2, 0, 0)) };
862+
yield return new object[] { DateTimeOffset.MaxValue.ToString("O"), DateTimeOffset.MaxValue };
863+
}
864864

865-
DateTimeOffset result = DateTimeOffset.Parse(expectedString);
866-
Assert.Equal(expectedString, result.ToString());
865+
[Theory]
866+
[MemberData(nameof(Parse_TestData))]
867+
public static void Parse_String(string valueForParse, DateTimeOffset expectedValue)
868+
{
869+
DateTimeOffset actualValue = DateTimeOffset.Parse(valueForParse);
870+
Assert.Equal(expectedValue, actualValue);
867871
}
868872

869873
[Fact]

0 commit comments

Comments
 (0)