File: src\libraries\System.Private.CoreLib\src\System\DateOnly.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Numerics;
using System.Runtime.Versioning;
 
namespace System
{
    /// <summary>
    /// Represents dates with values ranging from January 1, 0001 Anno Domini (Common Era) through December 31, 9999 A.D. (C.E.) in the Gregorian calendar.
    /// </summary>
    public readonly struct DateOnly
        : IComparable,
          IComparable<DateOnly>,
          IEquatable<DateOnly>,
          ISpanFormattable,
          ISpanParsable<DateOnly>,
          IUtf8SpanFormattable
    {
        private readonly uint _dayNumber;
 
        // Maps to Jan 1st year 1
        private const int MinDayNumber = 0;
 
        // Maps to December 31 year 9999.
        private const int MaxDayNumber = DateTime.DaysTo10000 - 1;
 
        private static uint DayNumberFromDateTime(DateTime dt) => (uint)((ulong)dt.Ticks / TimeSpan.TicksPerDay);
 
        internal DateTime GetEquivalentDateTime() => DateTime.CreateUnchecked(_dayNumber * TimeSpan.TicksPerDay);
 
        private DateOnly(uint dayNumber)
        {
            Debug.Assert(dayNumber <= MaxDayNumber);
            _dayNumber = dayNumber;
        }
 
        /// <summary>
        /// Gets the earliest possible date that can be created.
        /// </summary>
        public static DateOnly MinValue => new DateOnly(MinDayNumber);
 
        /// <summary>
        /// Gets the latest possible date that can be created.
        /// </summary>
        public static DateOnly MaxValue => new DateOnly(MaxDayNumber);
 
        /// <summary>
        /// Creates a new instance of the DateOnly structure to the specified year, month, and day.
        /// </summary>
        /// <param name="year">The year (1 through 9999).</param>
        /// <param name="month">The month (1 through 12).</param>
        /// <param name="day">The day (1 through the number of days in <paramref name="month" />).</param>
        public DateOnly(int year, int month, int day) => _dayNumber = DayNumberFromDateTime(new DateTime(year, month, day));
 
        /// <summary>
        /// Creates a new instance of the DateOnly structure to the specified year, month, and day for the specified calendar.
        /// </summary>
        /// <param name="year">The year (1 through the number of years in calendar).</param>
        /// <param name="month">The month (1 through the number of months in calendar).</param>
        /// <param name="day">The day (1 through the number of days in <paramref name="month"/>).</param>
        /// <param name="calendar">The calendar that is used to interpret year, month, and day.<paramref name="month"/>.</param>
        public DateOnly(int year, int month, int day, Calendar calendar) => _dayNumber = DayNumberFromDateTime(new DateTime(year, month, day, calendar));
 
        /// <summary>
        /// Creates a new instance of the DateOnly structure to the specified number of days.
        /// </summary>
        /// <param name="dayNumber">The number of days since January 1, 0001 in the Proleptic Gregorian calendar.</param>
        public static DateOnly FromDayNumber(int dayNumber)
        {
            if ((uint)dayNumber > MaxDayNumber)
            {
                ThrowHelper.ThrowArgumentOutOfRange_DayNumber(dayNumber);
            }
 
            return new DateOnly((uint)dayNumber);
        }
 
        /// <summary>
        /// Gets the year component of the date represented by this instance.
        /// </summary>
        public int Year => GetEquivalentDateTime().Year;
 
        /// <summary>
        /// Gets the month component of the date represented by this instance.
        /// </summary>
        public int Month => GetEquivalentDateTime().Month;
 
        /// <summary>
        /// Gets the day component of the date represented by this instance.
        /// </summary>
        public int Day => GetEquivalentDateTime().Day;
 
        /// <summary>
        /// Gets the day of the week represented by this instance.
        /// </summary>
        public DayOfWeek DayOfWeek => (DayOfWeek)((_dayNumber + 1) % 7);
 
        /// <summary>
        /// Gets the day of the year represented by this instance.
        /// </summary>
        public int DayOfYear => GetEquivalentDateTime().DayOfYear;
 
        /// <summary>
        /// Gets the number of days since January 1, 0001 in the Proleptic Gregorian calendar represented by this instance.
        /// </summary>
        public int DayNumber => (int)_dayNumber;
 
        /// <summary>
        /// Adds the specified number of days to the value of this instance.
        /// </summary>
        /// <param name="value">The number of days to add. To subtract days, specify a negative number.</param>
        /// <returns>An instance whose value is the sum of the date represented by this instance and the number of days represented by value.</returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown if the resulting value would be greater than <see cref="MaxValue"/>.
        /// </exception>
        public DateOnly AddDays(int value)
        {
            uint newDayNumber = _dayNumber + (uint)value;
            if (newDayNumber > MaxDayNumber)
            {
                ThrowOutOfRange();
            }
 
            return new DateOnly(newDayNumber);
 
            static void ThrowOutOfRange() => throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_AddValue);
        }
 
        /// <summary>
        /// Adds the specified number of months to the value of this instance.
        /// </summary>
        /// <param name="value">A number of months. The months parameter can be negative or positive.</param>
        /// <returns>An object whose value is the sum of the date represented by this instance and months.</returns>
        public DateOnly AddMonths(int value) => new DateOnly(DayNumberFromDateTime(GetEquivalentDateTime().AddMonths(value)));
 
        /// <summary>
        /// Adds the specified number of years to the value of this instance.
        /// </summary>
        /// <param name="value">A number of years. The value parameter can be negative or positive.</param>
        /// <returns>An object whose value is the sum of the date represented by this instance and the number of years represented by value.</returns>
        public DateOnly AddYears(int value) => new DateOnly(DayNumberFromDateTime(GetEquivalentDateTime().AddYears(value)));
 
        /// <summary>
        /// Determines whether two specified instances of DateOnly are equal.
        /// </summary>
        /// <param name="left">The first object to compare.</param>
        /// <param name="right">The second object to compare.</param>
        /// <returns>true if left and right represent the same date; otherwise, false.</returns>
        public static bool operator ==(DateOnly left, DateOnly right) => left._dayNumber == right._dayNumber;
 
        /// <summary>
        /// Determines whether two specified instances of DateOnly are not equal.
        /// </summary>
        /// <param name="left">The first object to compare.</param>
        /// <param name="right">The second object to compare.</param>
        /// <returns>true if left and right do not represent the same date; otherwise, false.</returns>
        public static bool operator !=(DateOnly left, DateOnly right) => left._dayNumber != right._dayNumber;
 
        /// <summary>
        /// Determines whether one specified DateOnly is later than another specified DateOnly.
        /// </summary>
        /// <param name="left">The first object to compare.</param>
        /// <param name="right">The second object to compare.</param>
        /// <returns>true if left is later than right; otherwise, false.</returns>
        public static bool operator >(DateOnly left, DateOnly right) => left._dayNumber > right._dayNumber;
 
        /// <summary>
        /// Determines whether one specified DateOnly represents a date that is the same as or later than another specified DateOnly.
        /// </summary>
        /// <param name="left">The first object to compare.</param>
        /// <param name="right">The second object to compare.</param>
        /// <returns>true if left is the same as or later than right; otherwise, false.</returns>
        public static bool operator >=(DateOnly left, DateOnly right) => left._dayNumber >= right._dayNumber;
 
        /// <summary>
        /// Determines whether one specified DateOnly is earlier than another specified DateOnly.
        /// </summary>
        /// <param name="left">The first object to compare.</param>
        /// <param name="right">The second object to compare.</param>
        /// <returns>true if left is earlier than right; otherwise, false.</returns>
        public static bool operator <(DateOnly left, DateOnly right) => left._dayNumber < right._dayNumber;
 
        /// <summary>
        /// Determines whether one specified DateOnly represents a date that is the same as or earlier than another specified DateOnly.
        /// </summary>
        /// <param name="left">The first object to compare.</param>
        /// <param name="right">The second object to compare.</param>
        /// <returns>true if left is the same as or earlier than right; otherwise, false.</returns>
        public static bool operator <=(DateOnly left, DateOnly right) => left._dayNumber <= right._dayNumber;
 
        /// <summary>
        /// Deconstructs <see cref="DateOnly"/> by <see cref="Year"/>, <see cref="Month"/> and <see cref="Day"/>.
        /// </summary>
        /// <param name="year">
        /// Deconstructed parameter for <see cref="Year"/>.
        /// </param>
        /// <param name="month">
        /// Deconstructed parameter for <see cref="Month"/>.
        /// </param>
        /// <param name="day">
        /// Deconstructed parameter for <see cref="Day"/>.
        /// </param>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public void Deconstruct(out int year, out int month, out int day)
            => GetEquivalentDateTime().GetDate(out year, out month, out day);
 
        /// <summary>
        /// Returns a DateTime that is set to the date of this DateOnly instance and the time of specified input time.
        /// </summary>
        /// <param name="time">The time of the day.</param>
        /// <returns>The DateTime instance composed of the date of the current DateOnly instance and the time specified by the input time.</returns>
        public DateTime ToDateTime(TimeOnly time) => DateTime.CreateUnchecked(_dayNumber * TimeSpan.TicksPerDay + time.Ticks);
 
        /// <summary>
        /// Returns a DateTime instance with the specified input kind that is set to the date of this DateOnly instance and the time of specified input time.
        /// </summary>
        /// <param name="time">The time of the day.</param>
        /// <param name="kind">One of the enumeration values that indicates whether ticks specifies a local time, Coordinated Universal Time (UTC), or neither.</param>
        /// <returns>The DateTime instance composed of the date of the current DateOnly instance and the time specified by the input time.</returns>
        public DateTime ToDateTime(TimeOnly time, DateTimeKind kind) => DateTime.SpecifyKind(ToDateTime(time), kind);
 
        /// <summary>
        /// Returns a DateOnly instance that is set to the date part of the specified dateTime.
        /// </summary>
        /// <param name="dateTime">The DateTime instance.</param>
        /// <returns>The DateOnly instance composed of the date part of the specified input time dateTime instance.</returns>
        public static DateOnly FromDateTime(DateTime dateTime) => new DateOnly(DayNumberFromDateTime(dateTime));
 
        /// <summary>
        /// Compares the value of this instance to a specified DateOnly value and returns an integer that indicates whether this instance is earlier than, the same as, or later than the specified DateTime value.
        /// </summary>
        /// <param name="value">The object to compare to the current instance.</param>
        /// <returns>Less than zero if this instance is earlier than value. Greater than zero if this instance is later than value. Zero if this instance is the same as value.</returns>
        public int CompareTo(DateOnly value) => _dayNumber.CompareTo(value._dayNumber);
 
        /// <summary>
        /// Compares the value of this instance to a specified object that contains a specified DateOnly value, and returns an integer that indicates whether this instance is earlier than, the same as, or later than the specified DateOnly value.
        /// </summary>
        /// <param name="value">A boxed object to compare, or null.</param>
        /// <returns>Less than zero if this instance is earlier than value. Greater than zero if this instance is later than value. Zero if this instance is the same as value.</returns>
        public int CompareTo(object? value)
        {
            if (value == null) return 1;
            if (value is not DateOnly dateOnly)
            {
                throw new ArgumentException(SR.Arg_MustBeDateOnly);
            }
 
            return CompareTo(dateOnly);
        }
 
        /// <summary>
        /// Returns a value indicating whether the value of this instance is equal to the value of the specified DateOnly instance.
        /// </summary>
        /// <param name="value">The object to compare to this instance.</param>
        /// <returns>true if the value parameter equals the value of this instance; otherwise, false.</returns>
        public bool Equals(DateOnly value) => _dayNumber == value._dayNumber;
 
        /// <summary>
        /// Returns a value indicating whether this instance is equal to a specified object.
        /// </summary>
        /// <param name="value">The object to compare to this instance.</param>
        /// <returns>true if value is an instance of DateOnly and equals the value of this instance; otherwise, false.</returns>
        public override bool Equals([NotNullWhen(true)] object? value) => value is DateOnly dateOnly && _dayNumber == dateOnly._dayNumber;
 
        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        public override int GetHashCode() => (int)_dayNumber;
 
        private const ParseFlags ParseFlagsDateMask = ParseFlags.HaveHour | ParseFlags.HaveMinute | ParseFlags.HaveSecond | ParseFlags.HaveTime | ParseFlags.TimeZoneUsed |
                                                      ParseFlags.TimeZoneUtc | ParseFlags.CaptureOffset | ParseFlags.UtcSortPattern;
 
        /// <summary>
        /// Converts a memory span that contains string representation of a date to its DateOnly equivalent by using culture-specific format information and a formatting style.
        /// </summary>
        /// <param name="s">The memory span that contains the string to parse.</param>
        /// <param name="provider">An object that supplies culture-specific format information about s.</param>
        /// <param name="style">A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None.</param>
        /// <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)
        {
            ParseFailureKind result = TryParseInternal(s, provider, style, out DateOnly dateOnly);
            if (result != ParseFailureKind.None)
            {
                ThrowOnError(result, s);
            }
 
            return dateOnly;
        }
 
        private const string OFormat = "yyyy'-'MM'-'dd";
        private const string RFormat = "ddd, dd MMM yyyy";
 
        /// <summary>
        /// Converts the specified span representation of a date to its DateOnly equivalent using the specified format, culture-specific format information, and style.
        /// The format of the string representation must match the specified format exactly or an exception is thrown.
        /// </summary>
        /// <param name="s">A span containing the characters that represent a date to convert.</param>
        /// <param name="format">A span containing the characters that represent a format specifier that defines the required format of s.</param>
        /// <param name="provider">An object that supplies culture-specific formatting information about s.</param>
        /// <param name="style">A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None.</param>
        /// <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)
        {
            ParseFailureKind result = TryParseExactInternal(s, format, provider, style, out DateOnly dateOnly);
 
            if (result != ParseFailureKind.None)
            {
                ThrowOnError(result, s);
            }
 
            return dateOnly;
        }
 
        /// <summary>
        /// Converts the specified span representation of a date to its DateOnly equivalent using the specified array of formats.
        /// The format of the string representation must match at least one of the specified formats exactly or an exception is thrown.
        /// </summary>
        /// <param name="s">A span containing the characters that represent a date to convert.</param>
        /// <param name="formats">An array of allowable formats of s.</param>
        /// <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) => ParseExact(s, formats, null, DateTimeStyles.None);
 
        /// <summary>
        /// Converts the specified span representation of a date to its DateOnly equivalent using the specified array of formats, culture-specific format information, and style.
        /// The format of the string representation must match at least one of the specified formats exactly or an exception is thrown.
        /// </summary>
        /// <param name="s">A span containing the characters that represent a date to convert.</param>
        /// <param name="formats">An array of allowable formats of s.</param>
        /// <param name="provider">An object that supplies culture-specific formatting information about s.</param>
        /// <param name="style">A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None.</param>
        /// <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)
        {
            ParseFailureKind result = TryParseExactInternal(s, formats, provider, style, out DateOnly dateOnly);
            if (result != ParseFailureKind.None)
            {
                ThrowOnError(result, s);
            }
 
            return dateOnly;
        }
 
        /// <summary>
        /// Converts a string that contains string representation of a date to its DateOnly equivalent by using the conventions of the current culture.
        /// </summary>
        /// <param name="s">The string that contains the string to parse.</param>
        /// <returns>An object that is equivalent to the date contained in s.</returns>
        public static DateOnly Parse(string s) => Parse(s, null, DateTimeStyles.None);
 
        /// <summary>
        /// Converts a string that contains string representation of a date to its DateOnly equivalent by using culture-specific format information and a formatting style.
        /// </summary>
        /// <param name="s">The string that contains the string to parse.</param>
        /// <param name="provider">An object that supplies culture-specific format information about s.</param>
        /// <param name="style">A bitwise combination of the enumeration values that indicates the style elements that can be present in s for the parse operation to succeed, and that defines how to interpret the parsed date. A typical value to specify is None.</param>
        /// <returns>An object that is equivalent to the date contained in s, as specified by provider and styles.</returns>
        public static DateOnly Parse(string s, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None)
        {
            if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
            return Parse(s.AsSpan(), provider, style);
        }
 
        /// <summary>
        /// Converts the specified string representation of a date to its DateOnly equivalent using the specified format.
        /// The format of the string representation must match the specified format exactly or an exception is thrown.
        /// </summary>
        /// <param name="s">A string containing the characters that represent a date to convert.</param>
        /// <param name="format">A string that represent a format specifier that defines the required format of s.</param>
        /// <returns>An object that is equivalent to the date contained in s, as specified by format.</returns>
        public static DateOnly ParseExact(string s, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string format) => ParseExact(s, format, null, DateTimeStyles.None);
 
        /// <summary>
        /// Converts the specified string representation of a date to its DateOnly equivalent using the specified format, culture-specific format information, and style.
        /// The format of the string representation must match the specified format exactly or an exception is thrown.
        /// </summary>
        /// <param name="s">A string containing the characters that represent a date to convert.</param>
        /// <param name="format">A string containing the characters that represent a format specifier that defines the required format of s.</param>
        /// <param name="provider">An object that supplies culture-specific formatting information about s.</param>
        /// <param name="style">A bitwise combination of the enumeration values that provides additional information about s, about style elements that may be present in s, or about the conversion from s to a DateOnly value. A typical value to specify is None.</param>
        /// <returns>An object that is equivalent to the date contained in s, as specified by format, provider, and style.</returns>
        public static DateOnly ParseExact(string s, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string format, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None)
        {
            if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
            if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format);
            return ParseExact(s.AsSpan(), format.AsSpan(), provider, style);
        }
 
        /// <summary>
        /// Converts the specified span representation of a date to its DateOnly equivalent using the specified array of formats.
        /// The format of the string representation must match at least one of the specified formats exactly or an exception is thrown.
        /// </summary>
        /// <param name="s">A span containing the characters that represent a date to convert.</param>
        /// <param name="formats">An array of allowable formats of s.</param>
        /// <returns>An object that is equivalent to the date contained in s, as specified by format, provider, and style.</returns>
        public static DateOnly ParseExact(string s, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string[] formats) => ParseExact(s, formats, null, DateTimeStyles.None);
 
        /// <summary>
        /// Converts the specified string representation of a date to its DateOnly equivalent using the specified array of formats, culture-specific format information, and style.
        /// The format of the string representation must match at least one of the specified formats exactly or an exception is thrown.
        /// </summary>
        /// <param name="s">A string containing the characters that represent a date to convert.</param>
        /// <param name="formats">An array of allowable formats of s.</param>
        /// <param name="provider">An object that supplies culture-specific formatting information about s.</param>
        /// <param name="style">A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None.</param>
        /// <returns>An object that is equivalent to the date contained in s, as specified by format, provider, and style.</returns>
        public static DateOnly ParseExact(string s, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string[] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None)
        {
            if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
            return ParseExact(s.AsSpan(), formats, provider, style);
        }
 
        /// <summary>
        /// Converts the specified span representation of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded.
        /// </summary>
        /// <param name="s">A span containing the characters representing the date to convert.</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, out DateOnly result) => TryParse(s, null, DateTimeStyles.None, out result);
 
        /// <summary>
        /// Converts the specified span representation of a date to its DateOnly equivalent using the specified array of formats, culture-specific format information, and style. And returns a value that indicates whether the conversion succeeded.
        /// </summary>
        /// <param name="s">A string containing the characters that represent a date to convert.</param>
        /// <param name="provider">An object that supplies culture-specific formatting information about s.</param>
        /// <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)
        {
            if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0)
            {
                result = default;
                return ParseFailureKind.Argument_InvalidDateStyles;
            }
 
            DateTimeResult dtResult = default;
            dtResult.Init(s);
 
            if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult))
            {
                result = default;
                return ParseFailureKind.Format_BadDateOnly;
            }
 
            if ((dtResult.flags & ParseFlagsDateMask) != 0)
            {
                result = default;
                return ParseFailureKind.Format_DateTimeOnlyContainsNoneDateParts;
            }
 
            result = new DateOnly(DayNumberFromDateTime(dtResult.parsedDate));
            return ParseFailureKind.None;
        }
 
        /// <summary>
        /// Converts the specified span representation of a date to its DateOnly equivalent using the specified format and style.
        /// The format of the string representation must match the specified format exactly. The method returns a value that indicates whether the conversion succeeded.
        /// </summary>
        /// <param name="s">A span containing the characters representing a date to convert.</param>
        /// <param name="format">The required 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, out DateOnly result) => TryParseExact(s, format, null, DateTimeStyles.None, out result);
 
        /// <summary>
        /// Converts the specified span representation of a date to its DateOnly equivalent using the specified format, culture-specific format information, and style.
        /// The format of the string representation must match the specified format exactly. The method returns a value that indicates whether the conversion succeeded.
        /// </summary>
        /// <param name="s">A span containing the characters representing a date to convert.</param>
        /// <param name="format">The required format of s.</param>
        /// <param name="provider">An object that supplies culture-specific formatting information about s.</param>
        /// <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)
        {
            if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0)
            {
                result = default;
                return ParseFailureKind.Argument_InvalidDateStyles;
            }
 
            if (format.Length == 1)
            {
                switch (format[0] | 0x20)
                {
                    case 'o':
                        format = OFormat;
                        provider = DateTimeFormat.InvariantFormatInfo;
                        break;
 
                    case 'r':
                        format = RFormat;
                        provider = DateTimeFormat.InvariantFormatInfo;
                        break;
                }
            }
 
            DateTimeResult dtResult = default;
            dtResult.Init(s);
 
            if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult))
            {
                result = default;
                return ParseFailureKind.Format_BadDateOnly;
            }
 
            if ((dtResult.flags & ParseFlagsDateMask) != 0)
            {
                result = default;
                return ParseFailureKind.Format_DateTimeOnlyContainsNoneDateParts;
            }
 
            result = new DateOnly(DayNumberFromDateTime(dtResult.parsedDate));
 
            return ParseFailureKind.None;
        }
 
        /// <summary>
        /// Converts the specified char span of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded.
        /// </summary>
        /// <param name="s">The span containing the string to parse.</param>
        /// <param name="formats">An array of allowable formats 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 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, out DateOnly result) => TryParseExact(s, formats, null, DateTimeStyles.None, out result);
 
        /// <summary>
        /// Converts the specified char span of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded.
        /// </summary>
        /// <param name="s">The span containing the string to parse.</param>
        /// <param name="formats">An array of allowable formats of s.</param>
        /// <param name="provider">An object that supplies culture-specific formatting information about s.</param>
        /// <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)
        {
            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++)
            {
                DateTimeFormatInfo dtfiToUse = dtfi;
                string? format = formats[i];
                if (string.IsNullOrEmpty(format))
                {
                    result = default;
                    return ParseFailureKind.Argument_BadFormatSpecifier;
                }
 
                if (format.Length == 1)
                {
                    switch (format[0] | 0x20)
                    {
                        case 'o':
                            format = OFormat;
                            dtfiToUse = DateTimeFormat.InvariantFormatInfo;
                            break;
 
                        case 'r':
                            format = RFormat;
                            dtfiToUse = DateTimeFormat.InvariantFormatInfo;
                            break;
                    }
                }
 
                // Create a new result each time to ensure the runs are independent. Carry through
                // flags from the caller and return the result.
                DateTimeResult dtResult = default;
                dtResult.Init(s);
                if (DateTimeParse.TryParseExact(s, format, dtfiToUse, style, ref dtResult) && ((dtResult.flags & ParseFlagsDateMask) == 0))
                {
                    result = new DateOnly(DayNumberFromDateTime(dtResult.parsedDate));
                    return ParseFailureKind.None;
                }
            }
 
            result = default;
            return ParseFailureKind.Format_BadDateOnly;
        }
 
        /// <summary>
        /// Converts the specified string representation of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded.
        /// </summary>
        /// <param name="s">A string containing the characters representing the date to convert.</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([NotNullWhen(true)] string? s, out DateOnly result) => TryParse(s, null, DateTimeStyles.None, out result);
 
        /// <summary>
        /// Converts the specified string representation of a date to its DateOnly equivalent using the specified array of formats, culture-specific format information, and style. And returns a value that indicates whether the conversion succeeded.
        /// </summary>
        /// <param name="s">A string containing the characters that represent a date to convert.</param>
        /// <param name="provider">An object that supplies culture-specific formatting information about s.</param>
        /// <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([NotNullWhen(true)] string? s, IFormatProvider? provider, DateTimeStyles style, out DateOnly result)
        {
            if (s == null)
            {
                result = default;
                return false;
            }
 
            return TryParse(s.AsSpan(), provider, style, out result);
        }
 
        /// <summary>
        /// Converts the specified string representation of a date to its DateOnly equivalent using the specified format and style.
        /// The format of the string representation must match the specified format exactly. The method returns a value that indicates whether the conversion succeeded.
        /// </summary>
        /// <param name="s">A string containing the characters representing a date to convert.</param>
        /// <param name="format">The required 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([NotNullWhen(true)] string? s, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string? format, out DateOnly result) => TryParseExact(s, format, null, DateTimeStyles.None, out result);
 
        /// <summary>
        /// Converts the specified span representation of a date to its DateOnly equivalent using the specified format, culture-specific format information, and style.
        /// The format of the string representation must match the specified format exactly. The method returns a value that indicates whether the conversion succeeded.
        /// </summary>
        /// <param name="s">A span containing the characters representing a date to convert.</param>
        /// <param name="format">The required format of s.</param>
        /// <param name="provider">An object that supplies culture-specific formatting information about s.</param>
        /// <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([NotNullWhen(true)] string? s, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string? format, IFormatProvider? provider, DateTimeStyles style, out DateOnly result)
        {
            if (s == null || format == null)
            {
                result = default;
                return false;
            }
 
            return TryParseExact(s.AsSpan(), format.AsSpan(), provider, style, out result);
        }
 
        /// <summary>
        /// Converts the specified string of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded.
        /// </summary>
        /// <param name="s">The string containing date to parse.</param>
        /// <param name="formats">An array of allowable formats 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 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([NotNullWhen(true)] string? s, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string?[]? formats, out DateOnly result) => TryParseExact(s, formats, null, DateTimeStyles.None, out result);
 
        /// <summary>
        /// Converts the specified string of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded.
        /// </summary>
        /// <param name="s">The string containing the date to parse.</param>
        /// <param name="formats">An array of allowable formats of s.</param>
        /// <param name="provider">An object that supplies culture-specific formatting information about s.</param>
        /// <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([NotNullWhen(true)] string? s, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result)
        {
            if (s == null)
            {
                result = default;
                return false;
            }
 
            return TryParseExact(s.AsSpan(), formats, provider, style, out result);
        }
 
        private static void ThrowOnError(ParseFailureKind result, ReadOnlySpan<char> s)
        {
            Debug.Assert(result != ParseFailureKind.None);
            switch (result)
            {
                case ParseFailureKind.Argument_InvalidDateStyles: throw new ArgumentException(SR.Argument_InvalidDateStyles, "style");
                case ParseFailureKind.Argument_BadFormatSpecifier: throw new FormatException(SR.Argument_BadFormatSpecifier);
                case ParseFailureKind.Format_BadDateOnly: throw new FormatException(SR.Format(SR.Format_BadDateOnly, s.ToString()));
                default:
                    Debug.Assert(result == ParseFailureKind.Format_DateTimeOnlyContainsNoneDateParts);
                    throw new FormatException(SR.Format(SR.Format_DateTimeOnlyContainsNoneDateParts, s.ToString(), nameof(DateOnly)));
            }
        }
 
        /// <summary>
        /// Converts the value of the current DateOnly object to its equivalent long date string representation.
        /// </summary>
        /// <returns>A string that contains the long date string representation of the current DateOnly object.</returns>
        public string ToLongDateString() => ToString("D");
 
        /// <summary>
        /// Converts the value of the current DateOnly object to its equivalent short date string representation.
        /// </summary>
        /// <returns>A string that contains the short date string representation of the current DateOnly object.</returns>
        public string ToShortDateString() => ToString();
 
        /// <summary>
        /// Converts the value of the current DateOnly object to its equivalent string representation using the formatting conventions of the current culture.
        /// The DateOnly object will be formatted in short form.
        /// </summary>
        /// <returns>A string that contains the short date string representation of the current DateOnly object.</returns>
        public override string ToString() => DateTimeFormat.Format(GetEquivalentDateTime(), "d", null);
 
        /// <summary>
        /// Converts the value of the current DateOnly object to its equivalent string representation using the specified format and the formatting conventions of the current culture.
        /// </summary>
        /// <param name="format">A standard or custom date format string.</param>
        /// <returns>A string representation of value of the current DateOnly object as specified by format.</returns>
        public string ToString([StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string? format) => ToString(format, null);
 
        /// <summary>
        /// Converts the value of the current DateOnly object to its equivalent string representation using the specified culture-specific format information.
        /// </summary>
        /// <param name="provider">An object that supplies culture-specific formatting information.</param>
        /// <returns>A string representation of value of the current DateOnly object as specified by provider.</returns>
        public string ToString(IFormatProvider? provider) => DateTimeFormat.Format(GetEquivalentDateTime(), "d", provider);
 
        /// <summary>
        /// Converts the value of the current DateOnly object to its equivalent string representation using the specified culture-specific format information.
        /// </summary>
        /// <param name="format">A standard or custom date format string.</param>
        /// <param name="provider">An object that supplies culture-specific formatting information.</param>
        /// <returns>A string representation of value of the current DateOnly object as specified by format and provider.</returns>
        public string ToString([StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] string? format, IFormatProvider? provider)
        {
            if (string.IsNullOrEmpty(format))
            {
                format = "d";
            }
 
            if (format.Length == 1)
            {
                return (format[0] | 0x20) switch
                {
                    'o' => string.Create(10, this, (destination, value) =>
                           {
                               DateTimeFormat.TryFormatDateOnlyO(value, destination, out int charsWritten);
                               Debug.Assert(charsWritten == destination.Length);
                           }),
 
                    'r' => string.Create(16, this, (destination, value) =>
                           {
                               DateTimeFormat.TryFormatDateOnlyR(value, destination, out int charsWritten);
                               Debug.Assert(charsWritten == destination.Length);
                           }),
 
                    'm' or 'd' or 'y' => DateTimeFormat.Format(GetEquivalentDateTime(), format, provider),
 
                    _ => throw new FormatException(SR.Format_InvalidString),
                };
            }
 
            DateTimeFormat.IsValidCustomDateOnlyFormat(format.AsSpan(), throwOnError: true);
            return DateTimeFormat.Format(GetEquivalentDateTime(), format, provider);
        }
 
        /// <summary>
        /// Tries to format the value of the current DateOnly instance into the provided span of characters.
        /// </summary>
        /// <param name="destination">When this method returns, this instance's value formatted as a span of characters.</param>
        /// <param name="charsWritten">When this method returns, the number of characters that were written in destination.</param>
        /// <param name="format">A span containing the characters that represent a standard or custom format string that defines the acceptable format for destination.</param>
        /// <param name="provider">An optional object that supplies culture-specific formatting information for destination.</param>
        /// <returns>true if the formatting was successful; otherwise, false.</returns>
        public bool TryFormat(Span<char> destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] ReadOnlySpan<char> format = default, IFormatProvider? provider = null) =>
            TryFormatCore(destination, out charsWritten, format, provider);
 
        /// <inheritdoc cref="IUtf8SpanFormattable.TryFormat" />
        public bool TryFormat(Span<byte> utf8Destination, out int bytesWritten, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] ReadOnlySpan<char> format = default, IFormatProvider? provider = null) =>
            TryFormatCore(utf8Destination, out bytesWritten, format, provider);
 
        private bool TryFormatCore<TChar>(Span<TChar> destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] ReadOnlySpan<char> format, IFormatProvider? provider = null)
            where TChar : unmanaged, IUtfChar<TChar>
        {
            if (format.Length == 0)
            {
                format = "d";
            }
 
            if (format.Length == 1)
            {
                switch (format[0] | 0x20)
                {
                    case 'o':
                        return DateTimeFormat.TryFormatDateOnlyO(this, destination, out charsWritten);
 
                    case 'r':
                        return DateTimeFormat.TryFormatDateOnlyR(this, destination, out charsWritten);
 
                    case 'm':
                    case 'd':
                    case 'y':
                        return DateTimeFormat.TryFormat(GetEquivalentDateTime(), destination, out charsWritten, format, provider);
 
                    default:
                        ThrowHelper.ThrowFormatException_BadFormatSpecifier();
                        break;
                }
            }
 
            if (!DateTimeFormat.IsValidCustomDateOnlyFormat(format, throwOnError: false))
            {
                throw new FormatException(SR.Format(SR.Format_DateTimeOnlyContainsNoneDateParts, format.ToString(), nameof(DateOnly)));
            }
 
            return DateTimeFormat.TryFormat(GetEquivalentDateTime(), destination, out charsWritten, format, provider);
        }
 
        //
        // IParsable
        //
 
        /// <inheritdoc cref="IParsable{TSelf}.Parse(string, IFormatProvider?)" />
        public static DateOnly Parse(string s, IFormatProvider? provider) => Parse(s, provider, DateTimeStyles.None);
 
        /// <inheritdoc cref="IParsable{TSelf}.TryParse(string?, IFormatProvider?, out TSelf)" />
        public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out DateOnly result) => TryParse(s, provider, DateTimeStyles.None, out result);
 
        //
        // ISpanParsable
        //
 
        /// <inheritdoc cref="ISpanParsable{TSelf}.Parse(ReadOnlySpan{char}, IFormatProvider?)" />
        public static DateOnly Parse(ReadOnlySpan<char> s, IFormatProvider? provider) => Parse(s, provider, DateTimeStyles.None);
 
        /// <inheritdoc cref="ISpanParsable{TSelf}.TryParse(ReadOnlySpan{char}, IFormatProvider?, out TSelf)" />
        public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out DateOnly result) => TryParse(s, provider, DateTimeStyles.None, out result);
    }
}