From e00c07001452e732bf597499ebc03c6eb52acca0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:21:39 +0000 Subject: [PATCH 1/9] Initial plan From ffcbf22e1375d65f999dc5dd113623bfaf3d8403 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:28:20 +0000 Subject: [PATCH 2/9] Fix DateOnly and TimeOnly TryParse methods to throw for invalid DateTimeStyles Co-authored-by: tarekgh <10833894+tarekgh@users.noreply.github.com> --- .../src/System/DateOnly.cs | 44 +++++++++++++++--- .../src/System/TimeOnly.cs | 45 ++++++++++++++++--- .../System/DateOnlyTests.cs | 8 ++-- .../System/TimeOnlyTests.cs | 8 ++-- 4 files changed, 86 insertions(+), 19 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs index f24aa06296c2a4..066f1ff8878408 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -433,7 +433,19 @@ public static DateOnly ParseExact(string s, [StringSyntax(StringSyntaxAttribute. /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. /// When this method returns, contains the DateOnly value equivalent to the date contained in s, if the conversion succeeded, or MinValue if the conversion failed. The conversion fails if the s parameter is empty string, or does not contain a valid string representation of a date. This parameter is passed uninitialized. /// true if the s parameter was converted successfully; otherwise, false. - public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) => TryParseInternal(s, provider, style, out result) == ParseFailureKind.None; + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) + { + ParseFailureKind failureKind = TryParseInternal(s, provider, style, out result); + if (failureKind != ParseFailureKind.None) + { + if (failureKind == ParseFailureKind.Argument_InvalidDateStyles || failureKind == ParseFailureKind.Argument_BadFormatSpecifier) + { + ThrowOnError(failureKind, s); + } + return false; + } + return true; + } private static ParseFailureKind TryParseInternal(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) { @@ -482,8 +494,19 @@ private static ParseFailureKind TryParseInternal(ReadOnlySpan s, IFormatPr /// A bitwise combination of one or more enumeration values that indicate the permitted format of s. /// When this method returns, contains the DateOnly value equivalent to the date contained in s, if the conversion succeeded, or MinValue if the conversion failed. The conversion fails if the s is empty string, or does not contain a date that correspond to the pattern specified in format. This parameter is passed uninitialized. /// true if s was converted successfully; otherwise, false. - public static bool TryParseExact(ReadOnlySpan s, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) => - TryParseExactInternal(s, format, provider, style, out result) == ParseFailureKind.None; + public static bool TryParseExact(ReadOnlySpan s, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) + { + ParseFailureKind failureKind = TryParseExactInternal(s, format, provider, style, out result); + if (failureKind != ParseFailureKind.None) + { + if (failureKind == ParseFailureKind.Argument_InvalidDateStyles || failureKind == ParseFailureKind.Argument_BadFormatSpecifier) + { + ThrowOnError(failureKind, s); + } + return false; + } + return true; + } private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) { if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) @@ -546,8 +569,19 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, Read /// A bitwise combination of enumeration values that defines how to interpret the parsed date. A typical value to specify is None. /// When this method returns, contains the DateOnly value equivalent to the date contained in s, if the conversion succeeded, or MinValue if the conversion failed. The conversion fails if the s parameter is Empty, or does not contain a valid string representation of a date. This parameter is passed uninitialized. /// true if the s parameter was converted successfully; otherwise, false. - public static bool TryParseExact(ReadOnlySpan s, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) => - TryParseExactInternal(s, formats, provider, style, out result) == ParseFailureKind.None; + public static bool TryParseExact(ReadOnlySpan s, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) + { + ParseFailureKind failureKind = TryParseExactInternal(s, formats, provider, style, out result); + if (failureKind != ParseFailureKind.None) + { + if (failureKind == ParseFailureKind.Argument_InvalidDateStyles || failureKind == ParseFailureKind.Argument_BadFormatSpecifier) + { + ThrowOnError(failureKind, s); + } + return false; + } + return true; + } private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) { diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index 72e15d2faada6a..7fc6d2e02861c8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -602,8 +602,19 @@ public static TimeOnly ParseExact(string s, [StringSyntax(StringSyntaxAttribute. /// When this method returns, contains the TimeOnly value equivalent to the time contained in s, if the conversion succeeded, or MinValue if the conversion failed. The conversion fails if the s parameter is empty string, or does not contain a valid string representation of a date. This parameter is passed uninitialized. /// true if the s parameter was converted successfully; otherwise, false. /// - public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) => - TryParseInternal(s, provider, style, out result) == ParseFailureKind.None; + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) + { + ParseFailureKind failureKind = TryParseInternal(s, provider, style, out result); + if (failureKind != ParseFailureKind.None) + { + if (failureKind == ParseFailureKind.Argument_InvalidDateStyles || failureKind == ParseFailureKind.Argument_BadFormatSpecifier) + { + ThrowOnError(failureKind, s); + } + return false; + } + return true; + } private static ParseFailureKind TryParseInternal(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) @@ -652,8 +663,19 @@ private static ParseFailureKind TryParseInternal(ReadOnlySpan s, IFormatPr /// A bitwise combination of one or more enumeration values that indicate the permitted format of s. /// When this method returns, contains the TimeOnly value equivalent to the time contained in s, if the conversion succeeded, or MinValue if the conversion failed. The conversion fails if the s is empty string, or does not contain a time that correspond to the pattern specified in format. This parameter is passed uninitialized. /// true if s was converted successfully; otherwise, false. - public static bool TryParseExact(ReadOnlySpan s, [StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) => - TryParseExactInternal(s, format, provider, style, out result) == ParseFailureKind.None; + public static bool TryParseExact(ReadOnlySpan s, [StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) + { + ParseFailureKind failureKind = TryParseExactInternal(s, format, provider, style, out result); + if (failureKind != ParseFailureKind.None) + { + if (failureKind == ParseFailureKind.Argument_InvalidDateStyles || failureKind == ParseFailureKind.Argument_BadFormatSpecifier) + { + ThrowOnError(failureKind, s); + } + return false; + } + return true; + } private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { @@ -716,8 +738,19 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, Read /// A bitwise combination of enumeration values that defines how to interpret the parsed time. A typical value to specify is None. /// When this method returns, contains the TimeOnly value equivalent to the time contained in s, if the conversion succeeded, or MinValue if the conversion failed. The conversion fails if the s parameter is Empty, or does not contain a valid string representation of a time. This parameter is passed uninitialized. /// true if the s parameter was converted successfully; otherwise, false. - public static bool TryParseExact(ReadOnlySpan s, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) => - TryParseExactInternal(s, formats, provider, style, out result) == ParseFailureKind.None; + public static bool TryParseExact(ReadOnlySpan s, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) + { + ParseFailureKind failureKind = TryParseExactInternal(s, formats, provider, style, out result); + if (failureKind != ParseFailureKind.None) + { + if (failureKind == ParseFailureKind.Argument_InvalidDateStyles || failureKind == ParseFailureKind.Argument_BadFormatSpecifier) + { + ThrowOnError(failureKind, s); + } + return false; + } + return true; + } private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs index d32f4c391fc8e9..484162e892f236 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs @@ -349,13 +349,13 @@ public static void BasicFormatParseTest() Assert.Equal(dateOnly, parsedDateOnly); Assert.Equal(dateOnly, parsedDateOnly1); - Assert.False(DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out parsedDateOnly1)); + Assert.Throws(() => DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out parsedDateOnly1)); Assert.Throws(() => DateOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal)); - Assert.False(DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out parsedDateOnly1)); + Assert.Throws(() => DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out parsedDateOnly1)); Assert.Throws(() => DateOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal)); - Assert.False(DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out parsedDateOnly1)); + Assert.Throws(() => DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out parsedDateOnly1)); Assert.Throws(() => DateOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal)); - Assert.False(DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out parsedDateOnly1)); + Assert.Throws(() => DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out parsedDateOnly1)); Assert.Throws(() => DateOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault)); s = " " + s + " "; diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/TimeOnlyTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/TimeOnlyTests.cs index 75f14b5ab4f6bd..f2ab676c11967b 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/TimeOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/TimeOnlyTests.cs @@ -388,13 +388,13 @@ public static void BasicFormatParseTest() Assert.True(TimeOnly.TryParse(s.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedTimeOnly1)); Assert.Equal(parsedTimeOnly, parsedTimeOnly1); - Assert.False(TimeOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out parsedTimeOnly1)); + AssertExtensions.Throws("style", () => TimeOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out parsedTimeOnly1)); AssertExtensions.Throws("style", () => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal)); - Assert.False(TimeOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out parsedTimeOnly1)); + AssertExtensions.Throws("style", () => TimeOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out parsedTimeOnly1)); AssertExtensions.Throws("style", () => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal)); - Assert.False(TimeOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out parsedTimeOnly1)); + AssertExtensions.Throws("style", () => TimeOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out parsedTimeOnly1)); AssertExtensions.Throws("style", () => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal)); - Assert.False(TimeOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out parsedTimeOnly1)); + AssertExtensions.Throws("style", () => TimeOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out parsedTimeOnly1)); AssertExtensions.Throws("style", () => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault)); s = " " + s + " "; From 91093044aaeec7f1850c693ef3ded6ac7f93cb5d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:29:23 +0000 Subject: [PATCH 3/9] Add comprehensive tests for TryParseExact with invalid DateTimeStyles Co-authored-by: tarekgh <10833894+tarekgh@users.noreply.github.com> --- .../System/DateOnlyTests.cs | 34 +++++++++++++++++++ .../System/TimeOnlyTests.cs | 34 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs index 484162e892f236..686e142b118179 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs @@ -554,5 +554,39 @@ public static void TryFormatTest() Assert.Throws(() => $"{dateOnly:hh-ss}"); } } + + [Fact] + public static void TryParseExact_InvalidDateTimeStyles_ThrowsArgumentException() + { + // Test that TryParseExact throws ArgumentException for invalid DateTimeStyles + string validInput = "2064-07-01"; + string format = "yyyy-MM-dd"; + DateOnly result; + + // Test ReadOnlySpan overload with single format + Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); + Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); + Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); + Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); + + // Test string overload with single format + Assert.Throws(() => DateOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); + Assert.Throws(() => DateOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); + Assert.Throws(() => DateOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); + Assert.Throws(() => DateOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); + + // Test ReadOnlySpan overload with multiple formats + string[] formats = new[] { "yyyy-MM-dd", "MM/dd/yyyy" }; + Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); + Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); + Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); + Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); + + // Test string overload with multiple formats + Assert.Throws(() => DateOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); + Assert.Throws(() => DateOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); + Assert.Throws(() => DateOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); + Assert.Throws(() => DateOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/TimeOnlyTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/TimeOnlyTests.cs index f2ab676c11967b..1a2ec14759877d 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/TimeOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/TimeOnlyTests.cs @@ -620,5 +620,39 @@ public static void TryFormatTest() Assert.Throws(() => $"{timeOnly:dd-yyyy}"); } } + + [Fact] + public static void TryParseExact_InvalidDateTimeStyles_ThrowsArgumentException() + { + // Test that TryParseExact throws ArgumentException for invalid DateTimeStyles + string validInput = "14:30:00"; + string format = "HH:mm:ss"; + TimeOnly result; + + // Test ReadOnlySpan overload with single format + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); + + // Test string overload with single format + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); + + // Test ReadOnlySpan overload with multiple formats + string[] formats = new[] { "HH:mm:ss", "hh:mm:ss tt" }; + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); + + // Test string overload with multiple formats + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); + AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); + } } } From 8bf2e126ce054b44f9560713dafbb01ba65606ca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:56:05 +0000 Subject: [PATCH 4/9] Use AssertExtensions.Throws with style parameter for test consistency Co-authored-by: tarekgh <10833894+tarekgh@users.noreply.github.com> --- .../System/DateOnlyTests.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs index 686e142b118179..850e5ee714c77f 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs @@ -564,29 +564,29 @@ public static void TryParseExact_InvalidDateTimeStyles_ThrowsArgumentException() DateOnly result; // Test ReadOnlySpan overload with single format - Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); - Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); - Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); - Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput.AsSpan(), format.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); // Test string overload with single format - Assert.Throws(() => DateOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); - Assert.Throws(() => DateOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); - Assert.Throws(() => DateOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); - Assert.Throws(() => DateOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput, format, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); // Test ReadOnlySpan overload with multiple formats string[] formats = new[] { "yyyy-MM-dd", "MM/dd/yyyy" }; - Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); - Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); - Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); - Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput.AsSpan(), formats, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); // Test string overload with multiple formats - Assert.Throws(() => DateOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); - Assert.Throws(() => DateOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); - Assert.Throws(() => DateOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); - Assert.Throws(() => DateOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); + AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); } } } From 67645f9f7185f8ed73d9e3a75f28fb7dea8f6502 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:58:47 +0000 Subject: [PATCH 5/9] Fix DateOnly.TryParse tests to use AssertExtensions.Throws for consistency Co-authored-by: tarekgh <10833894+tarekgh@users.noreply.github.com> --- .../System.Runtime.Tests/System/DateOnlyTests.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs index 850e5ee714c77f..dc9d4f09a8d4a8 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs @@ -349,14 +349,14 @@ public static void BasicFormatParseTest() Assert.Equal(dateOnly, parsedDateOnly); Assert.Equal(dateOnly, parsedDateOnly1); - Assert.Throws(() => DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out parsedDateOnly1)); - Assert.Throws(() => DateOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal)); - Assert.Throws(() => DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out parsedDateOnly1)); - Assert.Throws(() => DateOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal)); - Assert.Throws(() => DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out parsedDateOnly1)); - Assert.Throws(() => DateOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal)); - Assert.Throws(() => DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out parsedDateOnly1)); - Assert.Throws(() => DateOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault)); + AssertExtensions.Throws("style", () => DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out parsedDateOnly1)); + AssertExtensions.Throws("style", () => DateOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal)); + AssertExtensions.Throws("style", () => DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out parsedDateOnly1)); + AssertExtensions.Throws("style", () => DateOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal)); + AssertExtensions.Throws("style", () => DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out parsedDateOnly1)); + AssertExtensions.Throws("style", () => DateOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal)); + AssertExtensions.Throws("style", () => DateOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out parsedDateOnly1)); + AssertExtensions.Throws("style", () => DateOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault)); s = " " + s + " "; parsedDateOnly = DateOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces); From 9b82b31e0041f6a5827f1c21aed45656b7f1d988 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:19:32 +0000 Subject: [PATCH 6/9] Remove unnecessary Argument_BadFormatSpecifier check from TryParse methods Co-authored-by: tarekgh <10833894+tarekgh@users.noreply.github.com> --- src/libraries/System.Private.CoreLib/src/System/DateOnly.cs | 2 +- src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs index 066f1ff8878408..b2859678d9b8f5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -438,7 +438,7 @@ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, Dat ParseFailureKind failureKind = TryParseInternal(s, provider, style, out result); if (failureKind != ParseFailureKind.None) { - if (failureKind == ParseFailureKind.Argument_InvalidDateStyles || failureKind == ParseFailureKind.Argument_BadFormatSpecifier) + if (failureKind == ParseFailureKind.Argument_InvalidDateStyles) { ThrowOnError(failureKind, s); } diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index 7fc6d2e02861c8..549b16f1abd5ea 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -607,7 +607,7 @@ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, Dat ParseFailureKind failureKind = TryParseInternal(s, provider, style, out result); if (failureKind != ParseFailureKind.None) { - if (failureKind == ParseFailureKind.Argument_InvalidDateStyles || failureKind == ParseFailureKind.Argument_BadFormatSpecifier) + if (failureKind == ParseFailureKind.Argument_InvalidDateStyles) { ThrowOnError(failureKind, s); } From 492c226a999929a4b93adcf6727be2ce2d44f7b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:46:08 +0000 Subject: [PATCH 7/9] Preserve Argument_BadFormatSpecifier from DateTimeParse and add tests for bad format specifiers Co-authored-by: tarekgh <10833894+tarekgh@users.noreply.github.com> --- .../src/System/DateOnly.cs | 13 +++++++++++-- .../src/System/TimeOnly.cs | 13 +++++++++++-- .../System/DateOnlyTests.cs | 18 ++++++++++++++++++ .../System/TimeOnlyTests.cs | 18 ++++++++++++++++++ 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs index b2859678d9b8f5..433353248cd60e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -537,7 +537,9 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, Read if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) { result = default; - return ParseFailureKind.Format_BadDateOnly; + return dtResult.failure == ParseFailureKind.Argument_BadFormatSpecifier + ? ParseFailureKind.Argument_BadFormatSpecifier + : ParseFailureKind.Format_BadDateOnly; } if ((dtResult.flags & ParseFlagsDateMask) != 0) @@ -592,6 +594,7 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, stri } DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider); + ParseFailureKind lastFailure = ParseFailureKind.Format_BadDateOnly; for (int i = 0; i < formats.Length; i++) { @@ -628,10 +631,16 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, stri result = new DateOnly(DayNumberFromDateTime(dtResult.parsedDate)); return ParseFailureKind.None; } + + // Preserve Argument_BadFormatSpecifier if encountered + if (dtResult.failure == ParseFailureKind.Argument_BadFormatSpecifier) + { + lastFailure = ParseFailureKind.Argument_BadFormatSpecifier; + } } result = default; - return ParseFailureKind.Format_BadDateOnly; + return lastFailure; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index 549b16f1abd5ea..204dd88181a5a3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -707,7 +707,9 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, Read if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) { result = default; - return ParseFailureKind.Format_BadTimeOnly; + return dtResult.failure == ParseFailureKind.Argument_BadFormatSpecifier + ? ParseFailureKind.Argument_BadFormatSpecifier + : ParseFailureKind.Format_BadTimeOnly; } if ((dtResult.flags & ParseFlagsTimeMask) != 0) @@ -761,6 +763,7 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, stri } DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider); + ParseFailureKind lastFailure = ParseFailureKind.Format_BadTimeOnly; for (int i = 0; i < formats.Length; i++) { @@ -797,10 +800,16 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, stri result = FromDateTime(dtResult.parsedDate); return ParseFailureKind.None; } + + // Preserve Argument_BadFormatSpecifier if encountered + if (dtResult.failure == ParseFailureKind.Argument_BadFormatSpecifier) + { + lastFailure = ParseFailureKind.Argument_BadFormatSpecifier; + } } result = default; - return ParseFailureKind.Format_BadTimeOnly; + return lastFailure; } /// diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs index dc9d4f09a8d4a8..4a52dd2bbc3869 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DateOnlyTests.cs @@ -588,5 +588,23 @@ public static void TryParseExact_InvalidDateTimeStyles_ThrowsArgumentException() AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); AssertExtensions.Throws("style", () => DateOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); } + + [Fact] + public static void TryParseExact_InvalidFormatSpecifier_ThrowsFormatException() + { + // Test that TryParseExact throws FormatException for null/empty format specifiers + string validInput = "2064-07-01"; + DateOnly result; + + // Test with array containing null format as first element + string?[] formatsWithNull = new string?[] { null, "yyyy-MM-dd", "MM/dd/yyyy" }; + Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), formatsWithNull, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)); + Assert.Throws(() => DateOnly.TryParseExact(validInput, formatsWithNull, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)); + + // Test with array containing empty format as first element + string[] formatsWithEmpty = new[] { "", "yyyy-MM-dd", "MM/dd/yyyy" }; + Assert.Throws(() => DateOnly.TryParseExact(validInput.AsSpan(), formatsWithEmpty, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)); + Assert.Throws(() => DateOnly.TryParseExact(validInput, formatsWithEmpty, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/TimeOnlyTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/TimeOnlyTests.cs index 1a2ec14759877d..d98c055b54eef4 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/TimeOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/TimeOnlyTests.cs @@ -654,5 +654,23 @@ public static void TryParseExact_InvalidDateTimeStyles_ThrowsArgumentException() AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result)); AssertExtensions.Throws("style", () => TimeOnly.TryParseExact(validInput, formats, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)); } + + [Fact] + public static void TryParseExact_InvalidFormatSpecifier_ThrowsFormatException() + { + // Test that TryParseExact throws FormatException for null/empty format specifiers + string validInput = "14:30:00"; + TimeOnly result; + + // Test with array containing null format as first element + string?[] formatsWithNull = new string?[] { null, "HH:mm:ss", "hh:mm:ss tt" }; + Assert.Throws(() => TimeOnly.TryParseExact(validInput.AsSpan(), formatsWithNull, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)); + Assert.Throws(() => TimeOnly.TryParseExact(validInput, formatsWithNull, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)); + + // Test with array containing empty format as first element + string[] formatsWithEmpty = new[] { "", "HH:mm:ss", "hh:mm:ss tt" }; + Assert.Throws(() => TimeOnly.TryParseExact(validInput.AsSpan(), formatsWithEmpty, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)); + Assert.Throws(() => TimeOnly.TryParseExact(validInput, formatsWithEmpty, CultureInfo.InvariantCulture, DateTimeStyles.None, out result)); + } } } From 57a5c63e47cf6684fba8971f6700282b6e150c19 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:40:39 +0000 Subject: [PATCH 8/9] Return immediately when Argument_BadFormatSpecifier is encountered Co-authored-by: tarekgh <10833894+tarekgh@users.noreply.github.com> --- .../System.Private.CoreLib/src/System/DateOnly.cs | 7 +++---- .../System.Private.CoreLib/src/System/TimeOnly.cs | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs index 433353248cd60e..e863e8a00f6e1b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -594,7 +594,6 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, stri } DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider); - ParseFailureKind lastFailure = ParseFailureKind.Format_BadDateOnly; for (int i = 0; i < formats.Length; i++) { @@ -632,15 +631,15 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, stri return ParseFailureKind.None; } - // Preserve Argument_BadFormatSpecifier if encountered if (dtResult.failure == ParseFailureKind.Argument_BadFormatSpecifier) { - lastFailure = ParseFailureKind.Argument_BadFormatSpecifier; + result = default; + return ParseFailureKind.Argument_BadFormatSpecifier; } } result = default; - return lastFailure; + return ParseFailureKind.Format_BadDateOnly; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index 204dd88181a5a3..5714ebc15d33c2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -763,7 +763,6 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, stri } DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider); - ParseFailureKind lastFailure = ParseFailureKind.Format_BadTimeOnly; for (int i = 0; i < formats.Length; i++) { @@ -801,15 +800,15 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, stri return ParseFailureKind.None; } - // Preserve Argument_BadFormatSpecifier if encountered if (dtResult.failure == ParseFailureKind.Argument_BadFormatSpecifier) { - lastFailure = ParseFailureKind.Argument_BadFormatSpecifier; + result = default; + return ParseFailureKind.Argument_BadFormatSpecifier; } } result = default; - return lastFailure; + return ParseFailureKind.Format_BadTimeOnly; } /// From 440212b98cc761bad6050a0f9bd979b346a0a110 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 17:15:37 +0000 Subject: [PATCH 9/9] Validate styles and formats upfront in public methods for consistency Co-authored-by: tarekgh <10833894+tarekgh@users.noreply.github.com> --- .../src/System/DateOnly.cs | 100 +++++++++++------- .../src/System/TimeOnly.cs | 100 +++++++++++------- 2 files changed, 118 insertions(+), 82 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs index e863e8a00f6e1b..1a38a7619b2b73 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -286,6 +286,11 @@ public int CompareTo(object? value) /// An object that is equivalent to the date contained in s, as specified by provider and styles. public static DateOnly Parse(ReadOnlySpan s, IFormatProvider? provider = default, DateTimeStyles style = DateTimeStyles.None) { + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + ParseFailureKind result = TryParseInternal(s, provider, style, out DateOnly dateOnly); if (result != ParseFailureKind.None) { @@ -309,6 +314,11 @@ public static DateOnly Parse(ReadOnlySpan s, IFormatProvider? provider = d /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. public static DateOnly ParseExact(ReadOnlySpan s, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] ReadOnlySpan format, IFormatProvider? provider = default, DateTimeStyles style = DateTimeStyles.None) { + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + ParseFailureKind result = TryParseExactInternal(s, format, provider, style, out DateOnly dateOnly); if (result != ParseFailureKind.None) @@ -339,6 +349,25 @@ public static DateOnly ParseExact(ReadOnlySpan s, [StringSyntax(StringSynt /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. public static DateOnly ParseExact(ReadOnlySpan s, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string[] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) { + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + + if (formats == null) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + + // Validate formats array for null/empty entries upfront + for (int i = 0; i < formats.Length; i++) + { + if (string.IsNullOrEmpty(formats[i])) + { + throw new FormatException(SR.Argument_BadFormatSpecifier); + } + } + ParseFailureKind result = TryParseExactInternal(s, formats, provider, style, out DateOnly dateOnly); if (result != ParseFailureKind.None) { @@ -435,26 +464,17 @@ public static DateOnly ParseExact(string s, [StringSyntax(StringSyntaxAttribute. /// true if the s parameter was converted successfully; otherwise, false. public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) { - ParseFailureKind failureKind = TryParseInternal(s, provider, style, out result); - if (failureKind != ParseFailureKind.None) + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) { - if (failureKind == ParseFailureKind.Argument_InvalidDateStyles) - { - ThrowOnError(failureKind, s); - } - return false; + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); } - return true; + + ParseFailureKind failureKind = TryParseInternal(s, provider, style, out result); + return failureKind == ParseFailureKind.None; } private static ParseFailureKind TryParseInternal(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) { - if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) - { - result = default; - return ParseFailureKind.Argument_InvalidDateStyles; - } - DateTimeResult dtResult = default; dtResult.Init(s); @@ -496,12 +516,17 @@ private static ParseFailureKind TryParseInternal(ReadOnlySpan s, IFormatPr /// true if s was converted successfully; otherwise, false. public static bool TryParseExact(ReadOnlySpan s, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) { + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + ParseFailureKind failureKind = TryParseExactInternal(s, format, provider, style, out result); if (failureKind != ParseFailureKind.None) { - if (failureKind == ParseFailureKind.Argument_InvalidDateStyles || failureKind == ParseFailureKind.Argument_BadFormatSpecifier) + if (failureKind == ParseFailureKind.Argument_BadFormatSpecifier) { - ThrowOnError(failureKind, s); + throw new FormatException(SR.Argument_BadFormatSpecifier); } return false; } @@ -509,12 +534,6 @@ public static bool TryParseExact(ReadOnlySpan s, [StringSyntax(StringSynta } private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) { - if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) - { - result = default; - return ParseFailureKind.Argument_InvalidDateStyles; - } - if (format.Length == 1) { switch (format[0] | 0x20) @@ -573,39 +592,38 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, Read /// true if the s parameter was converted successfully; otherwise, false. public static bool TryParseExact(ReadOnlySpan s, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) { - ParseFailureKind failureKind = TryParseExactInternal(s, formats, provider, style, out result); - if (failureKind != ParseFailureKind.None) + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + + if (formats == null) { - if (failureKind == ParseFailureKind.Argument_InvalidDateStyles || failureKind == ParseFailureKind.Argument_BadFormatSpecifier) + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + + // Validate formats array for null/empty entries upfront + for (int i = 0; i < formats.Length; i++) + { + if (string.IsNullOrEmpty(formats[i])) { - ThrowOnError(failureKind, s); + throw new FormatException(SR.Argument_BadFormatSpecifier); } - return false; } - return true; + + return TryParseExactInternal(s, formats, provider, style, out result) == ParseFailureKind.None; } private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) { - if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0 || formats == null) - { - result = default; - return ParseFailureKind.Argument_InvalidDateStyles; - } - DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider); - for (int i = 0; i < formats.Length; i++) + for (int i = 0; i < formats!.Length; i++) { DateTimeFormatInfo dtfiToUse = dtfi; string? format = formats[i]; - if (string.IsNullOrEmpty(format)) - { - result = default; - return ParseFailureKind.Argument_BadFormatSpecifier; - } - if (format.Length == 1) + if (format!.Length == 1) { switch (format[0] | 0x20) { diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index 5714ebc15d33c2..329270849f3149 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -455,6 +455,11 @@ public override int GetHashCode() /// public static TimeOnly Parse(ReadOnlySpan s, IFormatProvider? provider = default, DateTimeStyles style = DateTimeStyles.None) { + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + ParseFailureKind result = TryParseInternal(s, provider, style, out TimeOnly timeOnly); if (result != ParseFailureKind.None) { @@ -478,6 +483,11 @@ public static TimeOnly Parse(ReadOnlySpan s, IFormatProvider? provider = d /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. public static TimeOnly ParseExact(ReadOnlySpan s, [StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] ReadOnlySpan format, IFormatProvider? provider = default, DateTimeStyles style = DateTimeStyles.None) { + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + ParseFailureKind result = TryParseExactInternal(s, format, provider, style, out TimeOnly timeOnly); if (result != ParseFailureKind.None) { @@ -507,6 +517,25 @@ public static TimeOnly ParseExact(ReadOnlySpan s, [StringSyntax(StringSynt /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. public static TimeOnly ParseExact(ReadOnlySpan s, [StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] string[] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) { + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + + if (formats == null) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + + // Validate formats array for null/empty entries upfront + for (int i = 0; i < formats.Length; i++) + { + if (string.IsNullOrEmpty(formats[i])) + { + throw new FormatException(SR.Argument_BadFormatSpecifier); + } + } + ParseFailureKind result = TryParseExactInternal(s, formats, provider, style, out TimeOnly timeOnly); if (result != ParseFailureKind.None) { @@ -604,25 +633,16 @@ public static TimeOnly ParseExact(string s, [StringSyntax(StringSyntaxAttribute. /// public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { - ParseFailureKind failureKind = TryParseInternal(s, provider, style, out result); - if (failureKind != ParseFailureKind.None) + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) { - if (failureKind == ParseFailureKind.Argument_InvalidDateStyles) - { - ThrowOnError(failureKind, s); - } - return false; + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); } - return true; + + ParseFailureKind failureKind = TryParseInternal(s, provider, style, out result); + return failureKind == ParseFailureKind.None; } private static ParseFailureKind TryParseInternal(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { - if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) - { - result = default; - return ParseFailureKind.Argument_InvalidDateStyles; - } - DateTimeResult dtResult = default; dtResult.Init(s); @@ -665,12 +685,17 @@ private static ParseFailureKind TryParseInternal(ReadOnlySpan s, IFormatPr /// true if s was converted successfully; otherwise, false. public static bool TryParseExact(ReadOnlySpan s, [StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + ParseFailureKind failureKind = TryParseExactInternal(s, format, provider, style, out result); if (failureKind != ParseFailureKind.None) { - if (failureKind == ParseFailureKind.Argument_InvalidDateStyles || failureKind == ParseFailureKind.Argument_BadFormatSpecifier) + if (failureKind == ParseFailureKind.Argument_BadFormatSpecifier) { - ThrowOnError(failureKind, s); + throw new FormatException(SR.Argument_BadFormatSpecifier); } return false; } @@ -679,12 +704,6 @@ public static bool TryParseExact(ReadOnlySpan s, [StringSyntax(StringSynta private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { - if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) - { - result = default; - return ParseFailureKind.Argument_InvalidDateStyles; - } - if (format.Length == 1) { switch (format[0] | 0x20) @@ -742,39 +761,38 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, Read /// true if the s parameter was converted successfully; otherwise, false. public static bool TryParseExact(ReadOnlySpan s, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { - ParseFailureKind failureKind = TryParseExactInternal(s, formats, provider, style, out result); - if (failureKind != ParseFailureKind.None) + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + + if (formats == null) { - if (failureKind == ParseFailureKind.Argument_InvalidDateStyles || failureKind == ParseFailureKind.Argument_BadFormatSpecifier) + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + + // Validate formats array for null/empty entries upfront + for (int i = 0; i < formats.Length; i++) + { + if (string.IsNullOrEmpty(formats[i])) { - ThrowOnError(failureKind, s); + throw new FormatException(SR.Argument_BadFormatSpecifier); } - return false; } - return true; + + return TryParseExactInternal(s, formats, provider, style, out result) == ParseFailureKind.None; } private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { - if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0 || formats == null) - { - result = default; - return ParseFailureKind.Argument_InvalidDateStyles; - } - DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider); - for (int i = 0; i < formats.Length; i++) + for (int i = 0; i < formats!.Length; i++) { DateTimeFormatInfo dtfiToUse = dtfi; string? format = formats[i]; - if (string.IsNullOrEmpty(format)) - { - result = default; - return ParseFailureKind.Argument_BadFormatSpecifier; - } - if (format.Length == 1) + if (format!.Length == 1) { switch (format[0] | 0x20) {