Skip to content
Merged
110 changes: 85 additions & 25 deletions src/libraries/System.Private.CoreLib/src/System/DateOnly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,11 @@ public int CompareTo(object? value)
/// <returns>An object that is equivalent to the date contained in s, as specified by provider and styles.</returns>
public static DateOnly Parse(ReadOnlySpan<char> 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)
{
Expand All @@ -309,6 +314,11 @@ public static DateOnly Parse(ReadOnlySpan<char> s, IFormatProvider? provider = d
/// <returns>An object that is equivalent to the date contained in s, as specified by format, provider, and style.</returns>
public static DateOnly ParseExact(ReadOnlySpan<char> s, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] ReadOnlySpan<char> 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)
Expand Down Expand Up @@ -339,6 +349,25 @@ public static DateOnly ParseExact(ReadOnlySpan<char> s, [StringSyntax(StringSynt
/// <returns>An object that is equivalent to the date contained in s, as specified by format, provider, and style.</returns>
public static DateOnly ParseExact(ReadOnlySpan<char> 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)
{
Expand Down Expand Up @@ -433,16 +462,19 @@ public static DateOnly ParseExact(string s, [StringSyntax(StringSyntaxAttribute.
/// <param name="style">A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None.</param>
/// <param name="result">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.</param>
/// <returns>true if the s parameter was converted successfully; otherwise, false.</returns>
public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) => TryParseInternal(s, provider, style, out result) == ParseFailureKind.None;

private static ParseFailureKind TryParseInternal(ReadOnlySpan<char> s, IFormatProvider? provider, DateTimeStyles style, out DateOnly result)
public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, DateTimeStyles style, out DateOnly result)
{
if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0)
{
result = default;
return ParseFailureKind.Argument_InvalidDateStyles;
throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style));
}

ParseFailureKind failureKind = TryParseInternal(s, provider, style, out result);
return failureKind == ParseFailureKind.None;
}

private static ParseFailureKind TryParseInternal(ReadOnlySpan<char> s, IFormatProvider? provider, DateTimeStyles style, out DateOnly result)
{
DateTimeResult dtResult = default;
dtResult.Init(s);

Expand Down Expand Up @@ -482,16 +514,26 @@ private static ParseFailureKind TryParseInternal(ReadOnlySpan<char> s, IFormatPr
/// <param name="style">A bitwise combination of one or more enumeration values that indicate the permitted format of s.</param>
/// <param name="result">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.</param>
/// <returns>true if s was converted successfully; otherwise, false.</returns>
public static bool TryParseExact(ReadOnlySpan<char> s, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] ReadOnlySpan<char> format, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) =>
TryParseExactInternal(s, format, provider, style, out result) == ParseFailureKind.None;
private static ParseFailureKind TryParseExactInternal(ReadOnlySpan<char> s, ReadOnlySpan<char> format, IFormatProvider? provider, DateTimeStyles style, out DateOnly result)
public static bool TryParseExact(ReadOnlySpan<char> s, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] ReadOnlySpan<char> format, IFormatProvider? provider, DateTimeStyles style, out DateOnly result)
{
if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0)
{
result = default;
return ParseFailureKind.Argument_InvalidDateStyles;
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_BadFormatSpecifier)
{
throw new FormatException(SR.Argument_BadFormatSpecifier);
}
return false;
}
return true;
}
private static ParseFailureKind TryParseExactInternal(ReadOnlySpan<char> s, ReadOnlySpan<char> format, IFormatProvider? provider, DateTimeStyles style, out DateOnly result)
{
if (format.Length == 1)
{
switch (format[0] | 0x20)
Expand All @@ -514,7 +556,9 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan<char> 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)
Expand Down Expand Up @@ -546,30 +590,40 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan<char> s, Read
/// <param name="style">A bitwise combination of enumeration values that defines how to interpret the parsed date. A typical value to specify is None.</param>
/// <param name="result">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.</param>
/// <returns>true if the s parameter was converted successfully; otherwise, false.</returns>
public static bool TryParseExact(ReadOnlySpan<char> s, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) =>
TryParseExactInternal(s, formats, provider, style, out result) == ParseFailureKind.None;

private static ParseFailureKind TryParseExactInternal(ReadOnlySpan<char> s, string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result)
public static bool TryParseExact(ReadOnlySpan<char> s, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result)
{
if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0 || formats == null)
if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0)
{
result = default;
return ParseFailureKind.Argument_InvalidDateStyles;
throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style));
}

DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider);
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++)
{
DateTimeFormatInfo dtfiToUse = dtfi;
string? format = formats[i];
if (string.IsNullOrEmpty(format))
if (string.IsNullOrEmpty(formats[i]))
{
result = default;
return ParseFailureKind.Argument_BadFormatSpecifier;
throw new FormatException(SR.Argument_BadFormatSpecifier);
}
}

return TryParseExactInternal(s, formats, provider, style, out result) == ParseFailureKind.None;
}

private static ParseFailureKind TryParseExactInternal(ReadOnlySpan<char> s, string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result)
{
DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider);

for (int i = 0; i < formats!.Length; i++)
{
DateTimeFormatInfo dtfiToUse = dtfi;
string? format = formats[i];

if (format.Length == 1)
if (format!.Length == 1)
{
switch (format[0] | 0x20)
{
Expand All @@ -594,6 +648,12 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan<char> s, stri
result = new DateOnly(DayNumberFromDateTime(dtResult.parsedDate));
return ParseFailureKind.None;
}

if (dtResult.failure == ParseFailureKind.Argument_BadFormatSpecifier)
{
result = default;
return ParseFailureKind.Argument_BadFormatSpecifier;
}
}

result = default;
Expand Down
Loading
Loading