From 85281430c04806722316a9a4823f2014cdafd239 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Thu, 8 Apr 2021 16:59:34 -0700 Subject: [PATCH 01/12] Introduce DateOnly and TimeOnly types --- .../src/Resources/Strings.resx | 81 +- .../System.Private.CoreLib.Shared.projitems | 2 + .../src/System/DateOnly.cs | 902 ++++++++++++++++ .../System/Globalization/DateTimeFormat.cs | 227 +++++ .../src/System/ThrowHelper.cs | 9 + .../src/System/TimeOnly.cs | 961 ++++++++++++++++++ .../System.Runtime/ref/System.Runtime.cs | 129 +++ .../tests/System.Runtime.Tests.csproj | 2 + .../tests/System/DateOnlyTests.cs | 518 ++++++++++ .../tests/System/TimeOnlyTests.cs | 466 +++++++++ 10 files changed, 3270 insertions(+), 27 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/DateOnly.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs create mode 100644 src/libraries/System.Runtime/tests/System/DateOnlyTests.cs create mode 100644 src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 807cca91a37ae5..9ed7364350bc7a 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -1,17 +1,17 @@  - @@ -478,6 +478,12 @@ Object must be of type Char. + + Object must be of type DateOnly. + + + Object must be of type TimeOnly. + Object must be of type DateTime. @@ -1123,6 +1129,9 @@ An undefined DateTimeStyles value is being used. + + The only allowed values for the styles are AllowWhiteSpaces, AllowTrailingWhite, AllowLeadingWhite, and AllowInnerWhite. + The DigitSubstitution property must be of a valid member of the DigitShapes enumeration. Valid entries include Context, NativeNational or None. @@ -1651,6 +1660,9 @@ Ticks must be between DateTime.MinValue.Ticks and DateTime.MaxValue.Ticks. + + Ticks must be between 0 and 863999999999. + Years value must be between +/-10000. @@ -1747,6 +1759,9 @@ Month must be between one and twelve. + + Day bumber must be between 0 and 3652058. + The Month parameter must be in the range 1 through 12. @@ -2173,6 +2188,18 @@ String '{0}' was not recognized as a valid DateTime. + + String '{0}' was not recognized as a valid DateOnly. + + + String '{0}' was not recognized as a valid TimeOnly. + + + String '{0}' contains parts which are not specific to the Date. + + + String '{0}' contains parts which are not specific to the TimeOnly. + The DateTime represented by the string '{0}' is not supported in calendar '{1}'. diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 9256e41f8a6332..165fd526f8a3fa 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -230,6 +230,7 @@ + @@ -1040,6 +1041,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs new file mode 100644 index 00000000000000..b352a42c79cbd5 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -0,0 +1,902 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Globalization; + +namespace System +{ + /// + /// 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. + /// + public readonly struct DateOnly : IComparable, IComparable, IEquatable, IFormattable + { + private readonly int _dayNumber; + + // Maps to Jan 1st year 1 + private const int MinDayNumber = 0; + + // Maps to December 31 year 9999. The value calculated from "new DateTime(9999, 12, 31).Ticks / TimeSpan.TicksPerDay" + private const int MaxDayNumber = 3652058; + + private static int DayNumberFromDateTime(DateTime dt) => (int)(dt.Ticks / TimeSpan.TicksPerDay); + + private DateTime GetEquivalentDateTime() => new DateTime(_dayNumber * TimeSpan.TicksPerDay); + + private DateOnly(int dayNumber) + { + Debug.Assert((uint)dayNumber <= 3652058); + _dayNumber = dayNumber; + } + + /// + /// Return the instance of the DateOnly structure representing the minimal possible date can be created. + /// + public static DateOnly MinValue { get; } = new DateOnly(MinDayNumber); + + /// + /// Return the instance of the DateOnly structure representing the maximal possible date can be created. + /// + public static DateOnly MaxValue { get; } = new DateOnly(MaxDayNumber); + + /// + /// Initializes a new instance of the DateOnly structure to the specified year, month, and day. + /// + /// The year (1 through 9999). + /// The month (1 through 12). + /// The day (1 through the number of days in month). + public DateOnly(int year, int month, int day) => _dayNumber = DayNumberFromDateTime(new DateTime(year, month, day)); + + /// + /// Initializes a new instance of the DateOnly structure to the specified year, month, and day for the specified calendar. + /// + /// The year (1 through the number of years in calendar). + /// The month (1 through the number of months in calendar). + /// The day (1 through the number of days in month).. + /// The calendar that is used to interpret year, month, and day.. + public DateOnly(int year, int month, int day, Calendar calendar) => _dayNumber = DayNumberFromDateTime(new DateTime(year, month, day, calendar)); + + /// + /// Initializes a new instance of the DateOnly structure to the specified number of days. + /// + /// The number of days since January 1, 0001 in the Gregorian calendar. + public static DateOnly FromDayNumber(int dayNumber) + { + if ((uint)dayNumber > MaxDayNumber) + { + ThrowHelper.ThrowArgumentOutOfRange_DayNumber(dayNumber); + } + + return new DateOnly(dayNumber); + } + + /// + /// Gets the year component of the date represented by this instance. + /// + public int Year => GetEquivalentDateTime().Year; + + /// + /// Gets the month component of the date represented by this instance. + /// + public int Month => GetEquivalentDateTime().Month; + + /// + /// Gets the day component of the date represented by this instance. + /// + public int Day => GetEquivalentDateTime().Day; + + /// + /// Gets the day of the week represented by this instance. + /// + public DayOfWeek DayOfWeek => GetEquivalentDateTime().DayOfWeek; + + /// + /// Gets the day of the year represented by this instance. + /// + public int DayOfYear => GetEquivalentDateTime().DayOfYear; + + /// + /// Gets the number if days since January 1, 0001 in the Gregorian calendar represented by this instance. + /// + public int DayNumber => _dayNumber; + + /// + /// Returns a new DateOnly that adds the specified number of days to the value of this instance. + /// + /// A number of days. The value parameter can be negative or positive. + /// An object whose value is the sum of the date represented by this instance and the number of days represented by value. + public DateOnly AddDays(int value) + { + int newDayNumber = _dayNumber + value; + if ((uint)newDayNumber > MaxDayNumber) + { + ThrowOutOfRange(); + } + + return new DateOnly(newDayNumber); + + static void ThrowOutOfRange() => throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_AddValue); + } + + /// + /// Returns a new DateOnly that adds the specified number of months to the value of this instance. + /// + /// A number of months. The months parameter can be negative or positive. + /// An object whose value is the sum of the date represented by this instance and months. + public DateOnly AddMonths(int value) => new DateOnly(DayNumberFromDateTime(GetEquivalentDateTime().AddMonths(value))); + + /// + /// Returns a new DateOnly that adds the specified number of years to the value of this instance. + /// + /// A number of years. The value parameter can be negative or positive. + /// An object whose value is the sum of the date represented by this instance and the number of years represented by value. + public DateOnly AddYears(int value) => new DateOnly(DayNumberFromDateTime(GetEquivalentDateTime().AddYears(value))); + + /// + /// Determines whether two specified instances of DateOnly are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// true if left and right represent the same date; otherwise, false. + public static bool operator ==(DateOnly left, DateOnly right) => left._dayNumber == right._dayNumber; + + /// + /// Determines whether one specified DateOnly is later than another specified DateTime. + /// + /// The first object to compare. + /// The second object to compare. + /// true if left is later than right; otherwise, false. + public static bool operator >(DateOnly left, DateOnly right) => left._dayNumber > right._dayNumber; + + /// + /// Determines whether one specified DateOnly represents a date that is the same as or later than another specified DateOnly. + /// + /// The first object to compare. + /// The second object to compare. + /// true if left is the same as or later than right; otherwise, false. + public static bool operator >=(DateOnly left, DateOnly right) => left._dayNumber >= right._dayNumber; + + /// + /// Determines whether two specified instances of DateOnly are not equal. + /// + /// The first object to compare. + /// The second object to compare. + /// true if left and right do not represent the same date; otherwise, false. + public static bool operator !=(DateOnly left, DateOnly right) => left._dayNumber != right._dayNumber; + + /// + /// Determines whether one specified DateOnly is earlier than another specified DateOnly. + /// + /// The first object to compare. + /// The second object to compare. + /// true if left is earlier than right; otherwise, false. + public static bool operator <(DateOnly left, DateOnly right) => left._dayNumber < right._dayNumber; + + /// + /// Determines whether one specified DateOnly represents a datethat is the same as or earlier than another specified DateOnly. + /// + /// The first object to compare. + /// The second object to compare. + /// true if left is the same as or earlier than right; otherwise, false. + public static bool operator <=(DateOnly left, DateOnly right) => left._dayNumber <= right._dayNumber; + + /// + /// Returns a DateTime that is set to the date of this DateOnly instance and the time of specified input time. + /// + /// The time of the day. + /// The DateTime instance composed of the date of the current DateOnly instance and teh time specified by the input time. + public DateTime ToDateTime(TimeOnly time) => new DateTime(_dayNumber * TimeSpan.TicksPerDay + time.Ticks); + + /// + /// 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. + /// + /// The time of the day. + /// One of the enumeration values that indicates whether ticks specifies a local time, Coordinated Universal Time (UTC), or neither. + /// The DateTime instance composed of the date of the current DateOnly instance and teh time specified by the input time. + public DateTime ToDateTime(TimeOnly time, DateTimeKind kind) => new DateTime(_dayNumber * TimeSpan.TicksPerDay + time.Ticks, kind); + + /// + /// Returns a DateOnly instance that is set to the date part of the specified dateTime. + /// + /// The DateTime instance. + /// The DateOnly instance composed of the date part of the specified input time dateTime instance. + public static DateOnly FromDateTime(DateTime dateTime) => new DateOnly(DayNumberFromDateTime(dateTime)); + + /// + /// 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. + /// + /// The object to compare to the current instance. + /// 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. + public int CompareTo(DateOnly value) + { + if (_dayNumber < value._dayNumber) return -1; + if (_dayNumber > value._dayNumber) return 1; + return 0; + } + + /// + /// 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. + /// + /// A boxed object to compare, or null. + /// 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. + public int CompareTo(object? value) + { + if (value == null) return 1; + if (!(value is DateOnly)) + { + throw new ArgumentException(SR.Arg_MustBeDateOnly); + } + + return CompareTo((DateOnly)value); + } + + /// + /// Returns a value indicating whether the value of this instance is equal to the value of the specified DateOnly instance. + /// + /// The object to compare to this instance. + /// true if the value parameter equals the value of this instance; otherwise, false. + public bool Equals(DateOnly value) => _dayNumber == value._dayNumber; + + /// + /// Returns a value indicating whether this instance is equal to a specified object. + /// + /// The object to compare to this instance. + /// true if value is an instance of DateOnly and equals the value of this instance; otherwise, false. + public override bool Equals(object? value) + { + if (value is DateOnly) + { + return _dayNumber == ((DateOnly)value)._dayNumber; + } + return false; + } + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() => _dayNumber; + + // Only Allowed DateTimeStyles: AllowWhiteSpaces, AllowTrailingWhite, AllowLeadingWhite, and AllowInnerWhite + + /// + /// Converts a memory span that contains string representation of a date to its DateOnly equivalent by using the conventions of the current culture. + /// + /// The memory span that contains the string to parse. + /// An object that is equivalent to the date contained in s. + public static DateOnly Parse(ReadOnlySpan s) => Parse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + + + private const ParseFlags ParseFlagsDateMask = ParseFlags.HaveHour | ParseFlags.HaveMinute | ParseFlags.HaveSecond | ParseFlags.HaveTime | ParseFlags.TimeZoneUsed | + ParseFlags.TimeZoneUtc | ParseFlags.CaptureOffset | ParseFlags.UtcSortPattern; + + /// + /// 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. + /// + /// The memory span that contains the string to parse. + /// An object that supplies culture-specific format information about s. + /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. + /// 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, DateTimeStyles style = DateTimeStyles.None) + { + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + + DateTimeResult result = default; // The buffer to store the parsing result. + result.Init(s); + + if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref result)) + { + throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_BadDateOnly)!, s.ToString())); + } + + if ((result.flags & ParseFlagsDateMask) != 0) + { + throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_DateOnlyContainsNoneDateParts)!, s.ToString())); + } + + return new DateOnly(DayNumberFromDateTime(result.parsedDate)); + } + + /// + /// Converts the specified span 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. + /// + /// A span containing the characters that represent a date to convert. + /// A span containing the characters that represent a format specifier that defines the required format of s. + /// An object that is equivalent to the date contained in s, as specified by format. + public static DateOnly ParseExact(ReadOnlySpan s, ReadOnlySpan format) => ParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + + private const string OFormat = "yyyy'-'MM'-'dd"; + private const string RFormat = "ddd, dd MMM yyyy"; + + /// + /// 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. + /// + /// A span containing the characters that represent a date to convert. + /// A span containing the characters that represent a format specifier that defines the required format of s. + /// An object that supplies culture-specific formatting information about s. + /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. + /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. + public static DateOnly ParseExact(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) + { + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + + if (format.Length == 1) + { + if (format[0] == 'o' || format[0] == 'O') + { + format = OFormat; + } + else if (format[0] == 'r' || format[0] == 'R') + { + format = RFormat; + } + } + + DateTimeResult result = default; + result.Init(s); + + if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref result)) + { + throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_BadDateOnly)!, s.ToString())); + } + + if ((result.flags & ParseFlagsDateMask) != 0) + { + throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_DateOnlyContainsNoneDateParts)!, s.ToString())); + } + + return new DateOnly(DayNumberFromDateTime(result.parsedDate)); + } + + /// + /// 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. + /// + /// A span containing the characters that represent a date to convert. + /// An array of allowable formats of s. + /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. + public static DateOnly ParseExact(ReadOnlySpan s, string [] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + + /// + /// 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. + /// + /// A span containing the characters that represent a date to convert. + /// An array of allowable formats of s. + /// An object that supplies culture-specific formatting information about s. + /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. + /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. + public static DateOnly ParseExact(ReadOnlySpan s, string [] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) + { + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + + if (formats == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.formats); + + DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider); + + for (int i = 0; i < formats.Length; i++) + { + string? format = formats[i]; + if (string.IsNullOrEmpty(format)) + { + throw new FormatException(SR.GetResourceString(SR.Argument_BadFormatSpecifier)); + } + + if (format.Length == 1) + { + if (format[0] == 'o' || format[0] == 'O') + { + format = OFormat; + } + else if (format[0] == 'r' || format[0] == 'R') + { + format = RFormat; + } + } + + // Create a new result each time to ensure the runs are independent. Carry through + // flags from the caller and return the result. + DateTimeResult result = default; // The buffer to store the parsing result. + result.Init(s); + if (DateTimeParse.TryParseExact(s, format, dtfi, style, ref result) && ((result.flags & ParseFlagsDateMask) == 0)) + { + return new DateOnly(DayNumberFromDateTime(result.parsedDate)); + } + } + + throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_BadDateOnly)!, s.ToString())); + } + + /// + /// Converts a string that contains string representation of a date to its DateOnly equivalent by using the conventions of the current culture. + /// + /// The string that contains the string to parse. + /// An object that is equivalent to the date contained in s. + public static DateOnly Parse(string s) => Parse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + + /// + /// Converts a string that contains string representation of a date to its DateOnly equivalent by using culture-specific format information and a formatting style. + /// + /// The string that contains the string to parse. + /// An object that supplies culture-specific format information about s. + /// 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. + /// An object that is equivalent to the date contained in s, as specified by provider and styles. + 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); + } + + /// + /// 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. + /// + /// A string containing the characters that represent a date to convert. + /// A string that represent a format specifier that defines the required format of s. + /// An object that is equivalent to the date contained in s, as specified by format. + public static DateOnly ParseExact(string s, string format) => ParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + + /// + /// 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. + /// + /// A string containing the characters that represent a date to convert. + /// A string containing the characters that represent a format specifier that defines the required format of s. + /// An object that supplies culture-specific formatting information about s. + /// 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. + /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. + public static DateOnly ParseExact(string s, 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); + } + + /// + /// 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. + /// + /// A span containing the characters that represent a date to convert. + /// An array of allowable formats of s. + /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. + public static DateOnly ParseExact(string s, string [] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + + /// + /// 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. + /// + /// A string containing the characters that represent a date to convert. + /// An array of allowable formats of s. + /// An object that supplies culture-specific formatting information about s. + /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. + /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. + public static DateOnly ParseExact(string s, string [] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) + { + if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + return ParseExact(s.AsSpan(), formats, provider, style); + } + + /// + /// Converts the specified span representation of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded. + /// + /// A span containing the characters representing the date to convert. + /// 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, out DateOnly result) => TryParse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + + /// + /// 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. + /// + /// A string containing the characters that represent a date to convert. + /// An object that supplies culture-specific formatting information about s. + /// 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) + { + result = default; + + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + return false; + } + + DateTimeResult dtResult = default; // The buffer to store the parsing result. + dtResult.Init(s); + + if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) + { + return false; + } + + if ((dtResult.flags & ParseFlagsDateMask) != 0) + { + return false; + } + + result = new DateOnly(DayNumberFromDateTime(dtResult.parsedDate)); + return true; + } + + /// + /// 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. + /// + /// A span containing the characters representing a date to convert. + /// The required 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, ReadOnlySpan format, out DateOnly result) => TryParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + + /// + /// 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. + /// + /// A span containing the characters representing a date to convert. + /// The required format of s. + /// An object that supplies culture-specific formatting information about s. + /// 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, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) + { + result = default; + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + return false; + } + + if (format.Length == 1) + { + if (format[0] == 'o' || format[0] == 'O') + { + format = OFormat; + } + else if (format[0] == 'r' || format[0] == 'R') + { + format = RFormat; + } + } + + DateTimeResult dtResult = default; // The buffer to store the parsing result. + dtResult.Init(s); + + if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) + { + return false; + } + + if ((dtResult.flags & ParseFlagsDateMask) != 0) + { + return false; + } + + result = new DateOnly(DayNumberFromDateTime(dtResult.parsedDate)); + return true; + } + + /// + /// Converts the specified char span of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded. + /// + /// The span containing the string to parse. + /// An array of allowable formats 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 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, string [] formats, out DateOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + + /// + /// Converts the specified char span of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded. + /// + /// The span containing the string to parse. + /// An array of allowable formats of s. + /// An object that supplies culture-specific formatting information about s. + /// 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, string [] formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) + { + result = default; + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0 || formats == null) + { + return false; + } + + DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider); + + for (int i = 0; i < formats.Length; i++) + { + string? format = formats[i]; + if (string.IsNullOrEmpty(format)) + { + return false; + } + + if (format.Length == 1) + { + if (format[0] == 'o' || format[0] == 'O') + { + format = OFormat; + } + else if (format[0] == 'r' || format[0] == 'R') + { + format = RFormat; + } + } + + // 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; // The buffer to store the parsing result. + dtResult.Init(s); + if (DateTimeParse.TryParseExact(s, format, dtfi, style, ref dtResult) && ((dtResult.flags & ParseFlagsDateMask) == 0)) + { + result = new DateOnly(DayNumberFromDateTime(dtResult.parsedDate)); + return true; + } + } + + return false; + } + + /// + /// Converts the specified string representation of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded. + /// + /// A string containing the characters representing the date to convert. + /// 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(string s, out DateOnly result) => TryParse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + + /// + /// 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. + /// + /// A string containing the characters that represent a date to convert. + /// An object that supplies culture-specific formatting information about s. + /// 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(string s, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) + { + result = default; + if (s == null || (style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + return false; + } + + DateTimeResult dtResult = default; // The buffer to store the parsing result. + dtResult.Init(s); + + if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) + { + return false; + } + + if ((dtResult.flags & ParseFlagsDateMask) != 0) + { + return false; + } + + result = new DateOnly(DayNumberFromDateTime(dtResult.parsedDate)); + return true; + } + + /// + /// 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. + /// + /// A string containing the characters representing a date to convert. + /// The required 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(string s, string format, out DateOnly result) => TryParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + + /// + /// 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. + /// + /// A span containing the characters representing a date to convert. + /// The required format of s. + /// An object that supplies culture-specific formatting information about s. + /// 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(string s, 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); + } + + /// + /// Converts the specified string of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded. + /// + /// The string containing date to parse. + /// An array of allowable formats 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 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(string s, string [] formats, out DateOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + + /// + /// Converts the specified string of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded. + /// + /// The string containing the date to parse. + /// An array of allowable formats of s. + /// An object that supplies culture-specific formatting information about s. + /// 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(string s, 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); + } + + /// + /// Converts the value of the current DateOnly object to its equivalent long date string representation. + /// + /// A string that contains the long date string representation of the current DateOnly object. + public string ToLongDateString() => ToString("D"); + + /// + /// Converts the value of the current DateOnly object to its equivalent short date string representation. + /// + /// A string that contains the short date string representation of the current DateOnly object. + public string ToShortDateString() => ToString(); + + /// + /// 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. + /// + /// A string that contains the short date string representation of the current DateOnly object. + public override string ToString() => ToString("d"); + + /// + /// 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. + /// + /// A standard or custom date format string. + /// A string representation of value of the current DateOnly object as specified by format. + public string ToString(string? format) => ToString(format, DateTimeFormatInfo.CurrentInfo); + + /// + /// Converts the value of the current DateOnly object to its equivalent string representation using the specified culture-specific format information. + /// + /// An object that supplies culture-specific formatting information. + /// A string representation of value of the current DateOnly object as specified by provider. + public string ToString(IFormatProvider? provider) => ToString("d", provider); + + /// + /// Converts the value of the current DateOnly object to its equivalent string representation using the specified culture-specific format information. + /// + /// A standard or custom date format string. + /// An object that supplies culture-specific formatting information. + /// A string representation of value of the current DateOnly object as specified by format and provider. + public string ToString(string? format, IFormatProvider? provider) + { + if (format == null || format.Length == 0) + { + format = "d"; + } + + if (format.Length == 1) + { + switch (format[0]) + { + case 'o': + case 'O': + { + Span destination = stackalloc char[10]; + bool b = DateTimeFormat.TryFormatDateOnlyO(Year, Month, Day, destination); + Debug.Assert(b); + + return destination.ToString(); + } + + case 'r': + case 'R': + { + Span destination = stackalloc char[16]; + bool b = DateTimeFormat.TryFormatDateOnlyR(DayOfWeek, Year, Month, Day, destination); + Debug.Assert(b); + + return destination.ToString(); + } + + case 'm': + case 'M': + case 'd': + case 'D': + case 'y': + case 'Y': + return DateTimeFormat.Format(GetEquivalentDateTime(), format, provider); + + default: + throw new FormatException(SR.Format_InvalidString); + } + } + + DateTimeFormat.IsValidCustomDateFormat(format.AsSpan(), allowThrow: true); + return DateTimeFormat.Format(GetEquivalentDateTime(), format, provider); + } + + /// + /// Tries to format the value of the current DateOnly instance into the provided span of characters. + /// + /// When this method returns, this instance's value formatted as a span of characters. + /// When this method returns, the number of characters that were written in destination. + /// A span containing the characters that represent a standard or custom format string that defines the acceptable format for destination. + /// An optional object that supplies culture-specific formatting information for destination. + /// true if the formatting was successful; otherwise, false. + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default(ReadOnlySpan), IFormatProvider? provider = null) + { + if (format.Length == 0) + { + format = "d".AsSpan(); + } + + if (format.Length == 1) + { + switch (format[0]) + { + case 'o': + case 'O': + if (!DateTimeFormat.TryFormatDateOnlyO(Year, Month, Day, destination)) + { + charsWritten = 0; + return false; + } + charsWritten = 10; + return true; + + case 'r': + case 'R': + + if (!DateTimeFormat.TryFormatDateOnlyR(DayOfWeek, Year, Month, Day, destination)) + { + charsWritten = 0; + return false; + } + charsWritten = 16; + return true; + + case 'm': + case 'M': + case 'd': + case 'D': + case 'y': + case 'Y': + return DateTimeFormat.TryFormat(GetEquivalentDateTime(), destination, out charsWritten, format, provider); + + default: + charsWritten = 0; + return false; + } + } + + if (!DateTimeFormat.IsValidCustomDateFormat(format, allowThrow: false)) + { + charsWritten = 0; + return false; + } + + return DateTimeFormat.TryFormat(GetEquivalentDateTime(), destination, out charsWritten, format, provider); + } + } +} \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs index 8f4d8c4d749d6e..1806d466688b97 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs @@ -1112,6 +1112,233 @@ private static StringBuilder FormatStringBuilder(DateTime dateTime, ReadOnlySpan return FormatCustomized(dateTime, format, dtfi, offset, result: null); } + internal static bool IsValidCustomDateFormat(ReadOnlySpan format, bool allowThrow) + { + int length = format.Length; + int i = 0; + + while (i < length) + { + switch (format[i]) + { + case '\\': + if (i == length - 1) + { + if (allowThrow) + { + throw new FormatException(SR.Format_InvalidString); + } + + return false; + } + i += 2; + break; + + case '\'': + case '"': + char quoteChar = format[i++]; + while (i < length && format[i] != quoteChar) + { + i++; + } + + if (i >= length) + { + if (allowThrow) + { + throw new FormatException(SR.Format(SR.Format_BadQuote, quoteChar)); + } + + return false; + } + + i++; + break; + + case ':': + case 't': + case 'f': + case 'F': + case 'h': + case 'H': + case 'm': + case 's': + case 'z': + case 'K': + // reject non-date formats + if (allowThrow) + { + throw new FormatException(SR.Format_InvalidString); + } + + return false; + + default: + i++; + break; + } + } + + return true; + } + + + internal static bool IsValidCustomTimeFormat(ReadOnlySpan format, bool allowThrow) + { + int length = format.Length; + int i = 0; + + while (i < length) + { + switch (format[i]) + { + case '\\': + if (i == length - 1) + { + if (allowThrow) + { + throw new FormatException(SR.Format_InvalidString); + } + + return false; + } + i += 2; + break; + + case '\'': + case '"': + char quoteChar = format[i++]; + while (i < length && format[i] != quoteChar) + { + i++; + } + + if (i >= length) + { + if (allowThrow) + { + throw new FormatException(SR.Format(SR.Format_BadQuote, quoteChar)); + } + + return false; + } + + i++; + break; + + case 'd': + case 'M': + case 'y': + case '/': + case 'z': + case 'k': + if (allowThrow) + { + throw new FormatException(SR.Format_InvalidString); + } + + return false; + + default: + i++; + break; + } + } + + return true; + } + + // 012345678901234567890123456789012 + // --------------------------------- + // 05:30:45.7680000 + internal static bool TryFormatTimeOnlyO(int hour, int minute, int second, long fraction, Span destination) + { + if (destination.Length < 16) + { + return false; + } + + WriteTwoDecimalDigits((uint)hour, destination, 0); + destination[2] = ':'; + WriteTwoDecimalDigits((uint)minute, destination, 3); + destination[5] = ':'; + WriteTwoDecimalDigits((uint)second, destination, 6); + destination[8] = '.'; + WriteDigits((uint)fraction, destination.Slice(9, 7)); + + return true; + } + + // 012345678901234567890123456789012 + // --------------------------------- + // 05:30:45 + internal static bool TryFormatTimeOnlyR(int hour, int minute, int second, Span destination) + { + if (destination.Length < 8) + { + return false; + } + + WriteTwoDecimalDigits((uint)hour, destination, 0); + destination[2] = ':'; + WriteTwoDecimalDigits((uint)minute, destination, 3); + destination[5] = ':'; + WriteTwoDecimalDigits((uint)second, destination, 6); + + return true; + } + + // Roundtrippable format. One of + // 012345678901234567890123456789012 + // --------------------------------- + // 2017-06-12 + internal static bool TryFormatDateOnlyO(int year, int month, int day, Span destination) + { + if (destination.Length < 10) + { + return false; + } + + WriteFourDecimalDigits((uint)year, destination, 0); + destination[4] = '-'; + WriteTwoDecimalDigits((uint)month, destination, 5); + destination[7] = '-'; + WriteTwoDecimalDigits((uint)day, destination, 8); + return true; + } + + // Rfc1123 + // 01234567890123456789012345678 + // ----------------------------- + // Tue, 03 Jan 2017 + internal static bool TryFormatDateOnlyR(DayOfWeek dayOfWeek, int year, int month, int day, Span destination) + { + if (destination.Length < 16) + { + return false; + } + + string dayAbbrev = InvariantAbbreviatedDayNames[(int)dayOfWeek]; + Debug.Assert(dayAbbrev.Length == 3); + + string monthAbbrev = InvariantAbbreviatedMonthNames[month - 1]; + Debug.Assert(monthAbbrev.Length == 3); + + destination[0] = dayAbbrev[0]; + destination[1] = dayAbbrev[1]; + destination[2] = dayAbbrev[2]; + destination[3] = ','; + destination[4] = ' '; + WriteTwoDecimalDigits((uint)day, destination, 5); + destination[7] = ' '; + destination[8] = monthAbbrev[0]; + destination[9] = monthAbbrev[1]; + destination[10] = monthAbbrev[2]; + destination[11] = ' '; + WriteFourDecimalDigits((uint)year, destination, 12); + return true; + } + // Roundtrippable format. One of // 012345678901234567890123456789012 // --------------------------------- diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index 117e60fdd06171..dd2cc2742975da 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -156,6 +156,12 @@ internal static void ThrowArgumentOutOfRange_Month(int month) throw new ArgumentOutOfRangeException(nameof(month), month, SR.ArgumentOutOfRange_Month); } + [DoesNotReturn] + internal static void ThrowArgumentOutOfRange_DayNumber(int dayNumber) + { + throw new ArgumentOutOfRangeException(nameof(dayNumber), dayNumber, SR.ArgumentOutOfRange_DayNumber); + } + [DoesNotReturn] internal static void ThrowArgumentOutOfRange_BadYearMonthDay() { @@ -639,6 +645,8 @@ private static string GetArgumentName(ExceptionArgument argument) return "start"; case ExceptionArgument.format: return "format"; + case ExceptionArgument.formats: + return "formats"; case ExceptionArgument.culture: return "culture"; case ExceptionArgument.comparer: @@ -960,6 +968,7 @@ internal enum ExceptionArgument pointer, start, format, + formats, culture, comparer, comparable, diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs new file mode 100644 index 00000000000000..eca0cc29358e67 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -0,0 +1,961 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Globalization; + +namespace System +{ + /// + /// Represents a time of day, as would be read from a clock, within the range 00:00:00 to 23:59:59.9999999 + /// + public readonly struct TimeOnly : IComparable, IComparable, IEquatable, IFormattable + { + // represent the number of ticks map to the time of the day. 1 ticks = 100-nanosecond in time measurements. + private readonly long _ticks; + + // MinTimeTicks is the ticks for the midnight time 00:00:00.000 AM + private const long MinTimeTicks = 0; + + // MaxTimeTicks is the max tick value for the time in the day. It is calculated using DateTime.Today.AddTicks(-1).TimeOfDay.Ticks. + private const long MaxTimeTicks = 863999999999; + + /// + /// Represents the smallest possible value of TimeOnly. + /// + public static TimeOnly MinValue { get; } = new TimeOnly((ulong) MinTimeTicks); + + /// + /// Represents the largest possible value of TimeOnly. + /// + public static TimeOnly MaxValue { get; } = new TimeOnly((ulong) MaxTimeTicks); + + /// + /// Initializes a new instance of the timeOnly structure to the specified hour and the minute. + /// + /// The hours (0 through 23). + /// The minutes (0 through 59). + public TimeOnly(int hour, int minute) : this((ulong)new DateTime(1, 1, 1, hour, minute, 0).TimeOfDay.Ticks) {} + + /// + /// Initializes a new instance of the timeOnly structure to the specified hour, minute, and second. + /// + /// The hours (0 through 23). + /// The minutes (0 through 59). + /// The seconds (0 through 59). + public TimeOnly(int hour, int minute, int second) : this((ulong)new DateTime(1, 1, 1, hour, minute, second).TimeOfDay.Ticks) {} + + /// + /// Initializes a new instance of the timeOnly structure to the specified hour, minute, second, and millisecond. + /// + /// The hours (0 through 23). + /// The minutes (0 through 59). + /// The seconds (0 through 59). + /// The millisecond (0 through 999). + public TimeOnly(int hour, int minute, int second, int millisecond) : this((ulong)new DateTime(1, 1, 1, hour, minute, second, millisecond).TimeOfDay.Ticks) {} + + /// + /// Initializes a new instance of the timeOnly structure to a specified number of ticks. + /// + /// A time expressed in the number of 100-nanosecond intervals that have elapsed since midnight 00:00:00.000 AM. + public TimeOnly(long ticks) + { + if ((ulong)ticks > MaxTimeTicks) + { + throw new ArgumentOutOfRangeException(nameof(ticks), SR.ArgumentOutOfRange_TimeOnlyBadTicks); + } + + _ticks = ticks; + } + + // exist to bypass the check in the public constructor. + internal TimeOnly(ulong ticks) => _ticks = (long)ticks; + + /// + /// Gets the hour component of the time represented by this instance. + /// + public int Hour => new TimeSpan(_ticks).Hours; + + /// + /// Gets the minute component of the time represented by this instance. + /// + public int Minute => new TimeSpan(_ticks).Minutes; + + /// + /// Gets the second component of the time represented by this instance. + /// + public int Second => new TimeSpan(_ticks).Seconds; + + /// + /// Gets the millisecond component of the time represented by this instance. + /// + public int Millisecond => new TimeSpan(_ticks).Milliseconds; + + /// + /// Gets the number of ticks that represent the time of this instance. + /// + public long Ticks => _ticks; + + private TimeOnly AddTicks(long ticks) => new TimeOnly((_ticks + TimeSpan.TicksPerDay + (ticks % TimeSpan.TicksPerDay)) % TimeSpan.TicksPerDay); + + private TimeOnly AddTicks(long ticks, out int wrappedDays) + { + wrappedDays = (int)(ticks / TimeSpan.TicksPerDay); + long newTicks = _ticks + ticks % TimeSpan.TicksPerDay; + if (newTicks < 0) + { + wrappedDays--; + newTicks += TimeSpan.TicksPerDay; + } + else + { + if (newTicks >= TimeSpan.TicksPerDay) + { + wrappedDays++; + newTicks -= TimeSpan.TicksPerDay; + } + } + + return new TimeOnly(newTicks); + } + + /// + /// Returns a new TimeOnly that adds the value of the specified TimeSpan to the value of this instance. + /// + /// A positive or negative time interval. + /// An object whose value is the sum of the time represented by this instance and the time interval represented by value. + public TimeOnly Add(TimeSpan value) => AddTicks(value.Ticks); + + /// + /// Returns a new TimeOnly that adds the value of the specified TimeSpan to the value of this instance. + /// If the added value circulate though the day, this method will out the number of the circulated days. + /// + /// A positive or negative time interval. + /// When this method returns, contains the number of circulated days resulted from this addition operation. + /// An object whose value is the sum of the time represented by this instance and the time interval represented by value. + public TimeOnly Add(TimeSpan value, out int wrappedDays) => AddTicks(value.Ticks, out wrappedDays); + + /// + /// Returns a new TimeOnly that adds the specified number of hours to the value of this instance. + /// + /// A number of whole and fractional hours. The value parameter can be negative or positive. + /// An object whose value is the sum of the time represented by this instance and the number of hours represented by value. + public TimeOnly AddHours(double value) => AddTicks((long)(value * TimeSpan.TicksPerHour)); + + /// + /// Returns a new TimeOnly that adds the specified number of hours to the value of this instance. + /// If the added value circulate though the day, this method will out the number of the circulated days. + /// + /// A number of whole and fractional hours. The value parameter can be negative or positive. + /// When this method returns, contains the number of circulated days resulted from this addition operation. + /// An object whose value is the sum of the time represented by this instance and the number of hours represented by value. + public TimeOnly AddHours(double value, out int wrappedDays)=> AddTicks((long)(value * TimeSpan.TicksPerHour), out wrappedDays); + + /// + /// Returns a new TimeOnly that adds the specified number of minutes to the value of this instance. + /// + /// A number of whole and fractional minutes. The value parameter can be negative or positive. + /// An object whose value is the sum of the time represented by this instance and the number of minutes represented by value. + public TimeOnly AddMinutes(double value) => AddTicks((long)(value * TimeSpan.TicksPerMinute)); + + /// + /// Returns a new TimeOnly that adds the specified number of minutes to the value of this instance. + /// If the added value circulate though the day, this method will out the number of the circulated days. + /// + /// A number of whole and fractional minutes. The value parameter can be negative or positive. + /// When this method returns, contains the number of circulated days resulted from this addition operation. + /// An object whose value is the sum of the time represented by this instance and the number of minutes represented by value. + public TimeOnly AddMinutes(double value, out int wrappedDays) => AddTicks((long)(value * TimeSpan.TicksPerMinute), out wrappedDays); + + /// + /// Determines if a time falls within the range provided. + /// Supports both "normal" ranges such as 10:00-12:00, and ranges that span midnight such as 23:00-01:00. + /// + /// The starting time of day, inclusive. + /// The ending time of day, exclusive. + /// True, if the time falls within the range, false otherwise. + public bool IsBetween(TimeOnly start, TimeOnly end) + { + long startTicks = start._ticks; + long endTicks = end._ticks; + + return startTicks <= endTicks + ? (startTicks <= _ticks && endTicks > _ticks) + : (startTicks <= _ticks || endTicks > _ticks); + } + + /// + /// Determines whether two specified instances of TimeOnly are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// true if left and right represent the same time; otherwise, false. + public static bool operator ==(TimeOnly left, TimeOnly right) => left._ticks == right._ticks; + + /// + /// Determines whether one specified TimeOnly is later than another specified TimeOnly. + /// + /// The first object to compare. + /// The second object to compare. + /// true if left is later than right; otherwise, false. + public static bool operator >(TimeOnly left, TimeOnly right) => left._ticks > right._ticks; + + /// + /// Determines whether one specified TimeOnly represents a time that is the same as or later than another specified TimeOnly. + /// + /// The first object to compare. + /// The second object to compare. + /// true if left is the same as or later than right; otherwise, false. + public static bool operator >=(TimeOnly left, TimeOnly right) => left._ticks >= right._ticks; + + /// + /// Determines whether two specified instances of TimeOnly are not equal. + /// + /// The first object to compare. + /// The second object to compare. + /// true if left and right do not represent the same time; otherwise, false. + public static bool operator !=(TimeOnly left, TimeOnly right) => left._ticks != right._ticks; + + /// + /// Determines whether one specified TimeOnly is earlier than another specified TimeOnly. + /// + /// The first object to compare. + /// The second object to compare. + /// true if left is earlier than right; otherwise, false. + public static bool operator <(TimeOnly left, TimeOnly right) => left._ticks < right._ticks; + + /// + /// Determines whether one specified TimeOnly represents a time that is the same as or earlier than another specified TimeOnly. + /// + /// The first object to compare. + /// The second object to compare. + /// true if left is the same as or earlier than right; otherwise, false. + public static bool operator <=(TimeOnly left, TimeOnly right) => left._ticks <= right._ticks; + + /// + /// Subtracts a specified time from another specified time and returns a time interval. + /// + /// The time value to subtract from (the minuend). + /// The time value to subtract (the subtrahend). + /// The absolute time interval between t1 and t2. + public static TimeSpan operator -(TimeOnly t1, TimeOnly t2) => new TimeSpan(Math.Abs(t1._ticks - t2._ticks)); + + /// + /// Constructs a TimeOnly object from a TimeSpan representing the time elapsed since midnight. + /// + /// The time interval measured since midnight. This value has to be positive and not exceeding the time of the day. + /// A TimeOnly object representing the time elapsed since midnight using the timeSpan value. + public static TimeOnly FromTimeSpan(TimeSpan timeSpan) => new TimeOnly(timeSpan._ticks); + + /// + /// Constructs a TimeOnly object from a DateTime representing the time of the day in this DateTime object. + /// + /// The time DateTime object to extract the time of the day from. + /// A TimeOnly object representing time of the day specified in the DateTime object. + public static TimeOnly FromDateTime(DateTime dateTime) => new TimeOnly(dateTime.TimeOfDay.Ticks); + + /// + /// Convert the current TimeOnly instance to a TimeSpan object. + /// + /// A TimeSpan object spanning to the time specified in the current TimeOnly object. + public TimeSpan ToTimeSpan() => new TimeSpan(_ticks); + + internal DateTime ToDateTime() => new DateTime(_ticks); + + /// + /// Compares the value of this instance to a specified TimeOnly value and indicates whether this instance is earlier than, the same as, or later than the specified TimeOnly value. + /// + /// The object to compare to the current instance. + /// + /// A signed number indicating the relative values of this instance and the value parameter. + /// Less than zero if this instance is earlier than value. + /// Zero if this instance is the same as value. + /// Greater than zero if this instance is later than value. + /// + public int CompareTo(TimeOnly value) + { + if (_ticks < value._ticks) return -1; + if (_ticks > value._ticks) return 1; + return 0; + } + + /// + /// Compares the value of this instance to a specified object that contains a specified TimeOnly value, and returns an integer that indicates whether this instance is earlier than, the same as, or later than the specified TimeOnly value. + /// + /// A boxed object to compare, or null. + /// + /// A signed number indicating the relative values of this instance and the value parameter. + /// Less than zero if this instance is earlier than value. + /// Zero if this instance is the same as value. + /// Greater than zero if this instance is later than value. + /// + public int CompareTo(object? value) + { + if (value == null) return 1; + if (!(value is TimeOnly)) + { + throw new ArgumentException(SR.Arg_MustBeTimeOnly); + } + + return CompareTo((TimeOnly)value); + } + + /// + /// Returns a value indicating whether the value of this instance is equal to the value of the specified TimeOnly instance. + /// + /// The object to compare to this instance. + /// true if the value parameter equals the value of this instance; otherwise, false. + public bool Equals(TimeOnly value) => _ticks == value._ticks; + + /// + /// Returns a value indicating whether this instance is equal to a specified object. + /// + /// The object to compare to this instance. + /// true if value is an instance of TimeOnly and equals the value of this instance; otherwise, false. + public override bool Equals(object? value) + { + if (value is TimeOnly) + { + return _ticks == ((TimeOnly)value)._ticks; + } + return false; + } + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + long ticks = _ticks; + return unchecked((int)ticks) ^ (int)(ticks >> 32); + } + + /// + /// Converts a memory span that contains string representation of a time to its TimeOnly equivalent by using the conventions of the current culture. + /// + /// The memory span that contains the string to parse. + /// An object that is equivalent to the time contained in s. + public static TimeOnly Parse(ReadOnlySpan s) => Parse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + + private const ParseFlags ParseFlagsTimeMask = ParseFlags.HaveYear | ParseFlags.HaveMonth | ParseFlags.HaveDay | ParseFlags.HaveDate | ParseFlags.TimeZoneUsed | + ParseFlags.TimeZoneUtc | ParseFlags.ParsedMonthName | ParseFlags.CaptureOffset | ParseFlags.UtcSortPattern; + + /// + /// Converts a memory span that contains string representation of a time to its TimeOnly equivalent by using culture-specific format information and a formatting style. + /// + /// The memory span that contains the string to parse. + /// An object that supplies culture-specific format information about s. + /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. + /// An object that is equivalent to the time contained in s, as specified by provider and styles. + public static TimeOnly Parse(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) + { + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + + DateTimeResult result = default; // The buffer to store the parsing result. + result.Init(s); + + if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref result)) + { + throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_BadTimeOnly)!, s.ToString())); + } + + if ((result.flags & ParseFlagsTimeMask) != 0) + { + throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_TimeOnlyContainsNoneTimeParts)!, s.ToString())); + } + + return new TimeOnly(result.parsedDate.TimeOfDay.Ticks); + } + + /// + /// Converts the specified span representation of a time to its TimeOnly equivalent using the specified format. + /// The format of the string representation must match the specified format exactly or an exception is thrown. + /// + /// A span containing the characters that represent a time to convert. + /// A span containing the characters that represent a format specifier that defines the required format of s. + /// An object that is equivalent to the time contained in s, as specified by format. + public static TimeOnly ParseExact(ReadOnlySpan s, ReadOnlySpan format) => ParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + + private const string OFormat = "HH':'mm':'ss'.'fffffff"; + private const string RFormat = "HH':'mm':'ss"; + + /// + /// Converts the specified span representation of a time to its TimeOnly 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. + /// + /// A span containing the characters that represent a time to convert. + /// A span containing the characters that represent a format specifier that defines the required format of s. + /// An object that supplies culture-specific formatting information about s. + /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. + /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. + public static TimeOnly ParseExact(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) + { + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + + if (format.Length == 1) + { + if (format[0] == 'o' || format[0] == 'O') + { + format = OFormat; + } + else if (format[0] == 'r' || format[0] == 'R') + { + format = RFormat; + } + } + + DateTimeResult result = default; // The buffer to store the parsing result. + result.Init(s); + + if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref result)) + { + throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_BadTimeOnly)!, s.ToString())); + } + + if ((result.flags & ParseFlagsTimeMask) != 0) + { + throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_TimeOnlyContainsNoneTimeParts)!, s.ToString())); + } + + return new TimeOnly(result.parsedDate.TimeOfDay.Ticks); + } + + /// + /// Converts the specified span representation of a time to its TimeOnly 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. + /// + /// A span containing the characters that represent a time to convert. + /// An array of allowable formats of s. + /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. + public static TimeOnly ParseExact(ReadOnlySpan s, string [] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + + /// + /// Converts the specified span representation of a time to its TimeOnly 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. + /// + /// A span containing the characters that represent a time to convert. + /// An array of allowable formats of s. + /// An object that supplies culture-specific formatting information about s. + /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. + /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. + public static TimeOnly ParseExact(ReadOnlySpan s, string [] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) + { + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + } + + if (formats == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.formats); + + DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider); + + for (int i = 0; i < formats.Length; i++) + { + string? format = formats[i]; + if (string.IsNullOrEmpty(format)) + { + throw new FormatException(SR.GetResourceString(SR.Argument_BadFormatSpecifier)); + } + + if (format.Length == 1) + { + if (format[0] == 'o' || format[0] == 'O') + { + format = "HH':'mm':'ss'.'fffffff"; + } + else if (format[0] == 'r' || format[0] == 'R') + { + format = "HH':'mm':'ss"; + } + } + + // Create a new result each time to ensure the runs are independent. Carry through + // flags from the caller and return the result. + DateTimeResult result = default; // The buffer to store the parsing result. + result.Init(s); + if (DateTimeParse.TryParseExact(s, format, dtfi, style, ref result) && ((result.flags & ParseFlagsTimeMask) == 0)) + { + return new TimeOnly(result.parsedDate.TimeOfDay.Ticks); + } + } + + throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_BadTimeOnly)!, s.ToString())); + } + + /// + /// Converts a string that contains string representation of a time to its TimeOnly equivalent by using the conventions of the current culture. + /// + /// The string that contains the string to parse. + /// An object that is equivalent to the time contained in s. + public static TimeOnly Parse(string s) => Parse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + + /// + /// Converts a string that contains string representation of a time to its TimeOnly equivalent by using culture-specific format information and a formatting style. + /// + /// The string that contains the string to parse. + /// An object that supplies culture-specific format information about s. + /// 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. + /// An object that is equivalent to the time contained in s, as specified by provider and styles. + public static TimeOnly Parse(string s, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) + { + if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + return Parse(s.AsSpan(), provider, style); + } + + /// + /// Converts the specified string representation of a time to its TimeOnly equivalent using the specified format. + /// The format of the string representation must match the specified format exactly or an exception is thrown. + /// + /// A string containing the characters that represent a time to convert. + /// A string that represent a format specifier that defines the required format of s. + /// An object that is equivalent to the time contained in s, as specified by format. + public static TimeOnly ParseExact(string s, string format) => ParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + + /// + /// Converts the specified string representation of a time to its TimeOnly 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. + /// + /// A string containing the characters that represent a time to convert. + /// A string containing the characters that represent a format specifier that defines the required format of s. + /// An object that supplies culture-specific formatting information about s. + /// 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 TimeOnly value. A typical value to specify is None. + /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. + public static TimeOnly ParseExact(string s, 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); + } + + /// + /// Converts the specified span representation of a time to its TimeOnly 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. + /// + /// A span containing the characters that represent a time to convert. + /// An array of allowable formats of s. + /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. + public static TimeOnly ParseExact(string s, string [] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + + /// + /// Converts the specified string representation of a time to its TimeOnly 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. + /// + /// A string containing the characters that represent a time to convert. + /// An array of allowable formats of s. + /// An object that supplies culture-specific formatting information about s. + /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. + /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. + public static TimeOnly ParseExact(string s, string [] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) + { + if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + return ParseExact(s.AsSpan(), formats, provider, style); + } + + /// + /// Converts the specified span representation of a time to its TimeOnly equivalent and returns a value that indicates whether the conversion succeeded. + /// + /// A span containing the characters representing the time to convert. + /// 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 time. This parameter is passed uninitialized. + /// true if the s parameter was converted successfully; otherwise, false. + public static bool TryParse(ReadOnlySpan s, out TimeOnly result) => TryParse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + + /// + /// Converts the specified span representation of a time to its TimeOnly equivalent using the specified array of formats, culture-specific format information, and style. And returns a value that indicates whether the conversion succeeded. + /// + /// A string containing the characters that represent a time to convert. + /// An object that supplies culture-specific formatting information about s. + /// 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 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) + { + result = default; + + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + return false; + } + + DateTimeResult dtResult = default; // The buffer to store the parsing result. + dtResult.Init(s); + + if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) + { + return false; + } + + if ((dtResult.flags & ParseFlagsTimeMask) != 0) + { + return false; + } + + result = new TimeOnly(dtResult.parsedDate.TimeOfDay.Ticks); + + return true; + } + + /// + /// Converts the specified span representation of a time to its TimeOnly 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. + /// + /// A span containing the characters representing a time to convert. + /// The required 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, ReadOnlySpan format, out TimeOnly result) => TryParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + + /// + /// Converts the specified span representation of a time to its TimeOnly 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. + /// + /// A span containing the characters representing a time to convert. + /// The required format of s. + /// An object that supplies culture-specific formatting information about s. + /// 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, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) + { + result = default; + + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + { + return false; + } + + if (format.Length == 1) + { + if (format[0] == 'o' || format[0] == 'O') + { + format = OFormat; + } + else if (format[0] == 'r' || format[0] == 'R') + { + format = RFormat; + } + } + + DateTimeResult dtResult = default; // The buffer to store the parsing result. + dtResult.Init(s); + + if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) + { + return false; + } + + if ((dtResult.flags & ParseFlagsTimeMask) != 0) + { + return false; + } + + result = new TimeOnly(dtResult.parsedDate.TimeOfDay.Ticks); + + return true; + } + + /// + /// Converts the specified char span of a time to its TimeOnly equivalent and returns a value that indicates whether the conversion succeeded. + /// + /// The span containing the string to parse. + /// An array of allowable formats 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 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, string [] formats, out TimeOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + + /// + /// Converts the specified char span of a time to its TimeOnly equivalent and returns a value that indicates whether the conversion succeeded. + /// + /// The span containing the string to parse. + /// An array of allowable formats of s. + /// An object that supplies culture-specific formatting information about s. + /// 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, string [] formats, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) + { + result = default; + if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0 || formats == null) + { + return false; + } + + DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider); + + for (int i = 0; i < formats.Length; i++) + { + string? format = formats[i]; + if (string.IsNullOrEmpty(format)) + { + return false; + } + + if (format.Length == 1) + { + if (format[0] == 'o' || format[0] == 'O') + { + format = OFormat; + } + else if (format[0] == 'r' || format[0] == 'R') + { + format = RFormat; + } + } + + // 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, dtfi, style, ref dtResult) && ((dtResult.flags & ParseFlagsTimeMask) == 0)) + { + result = new TimeOnly(dtResult.parsedDate.TimeOfDay.Ticks); + return true; + } + } + + return false; + } + + /// + /// Converts the specified string representation of a time to its TimeOnly equivalent and returns a value that indicates whether the conversion succeeded. + /// + /// A string containing the characters representing the time to convert. + /// 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 time. This parameter is passed uninitialized. + /// true if the s parameter was converted successfully; otherwise, false. + public static bool TryParse(string s, out TimeOnly result) => TryParse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + + /// + /// Converts the specified string representation of a time to its TimeOnly equivalent using the specified array of formats, culture-specific format information, and style. And returns a value that indicates whether the conversion succeeded. + /// + /// A string containing the characters that represent a time to convert. + /// An object that supplies culture-specific formatting information about s. + /// 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 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 time. This parameter is passed uninitialized. + /// true if the s parameter was converted successfully; otherwise, false. + public static bool TryParse(string s, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) + { + result = default; + if (s == null) + { + return false; + } + + return TryParse(s.AsSpan(), provider, style, out result); + } + + /// + /// Converts the specified string representation of a time to its TimeOnly 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. + /// + /// A string containing the characters representing a time to convert. + /// The required 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(string s, string format, out TimeOnly result) => TryParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + + /// + /// Converts the specified span representation of a time to its TimeOnly 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. + /// + /// A span containing the characters representing a time to convert. + /// The required format of s. + /// An object that supplies culture-specific formatting information about s. + /// 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(string s, string format, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) + { + if (s == null || format == null) + { + result = default; + return false; + } + + return TryParseExact(s.AsSpan(), format.AsSpan(), provider, style, out result); + } + + /// + /// Converts the specified string of a time to its TimeOnly equivalent and returns a value that indicates whether the conversion succeeded. + /// + /// The string containing time to parse. + /// An array of allowable formats 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 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(string s, string [] formats, out TimeOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + + /// + /// Converts the specified string of a time to its TimeOnly equivalent and returns a value that indicates whether the conversion succeeded. + /// + /// The string containing the time to parse. + /// An array of allowable formats of s. + /// An object that supplies culture-specific formatting information about s. + /// 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 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(string s, string [] formats, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) + { + if (s == null) + { + result = default; + return false; + } + + return TryParseExact(s.AsSpan(), formats, provider, style, out result); + } + + + // Acceptable formats: + // Sortable date/time pattern s Time part only + // RFC1123 pattern r/R Time part only like o + // round-trip date/time pattern o/O Time part only + // Short time pattern t + // Long time pattern T + + /// + /// Converts the value of the current TimeOnly object to its equivalent long date string representation. + /// + /// A string that contains the long time string representation of the current TimeOnly object. + public string ToLongTimeString() => ToString("T"); + + /// + /// Converts the value of the current TimeOnly object to its equivalent short time string representation. + /// + /// A string that contains the short time string representation of the current TimeOnly object. + public string ToShortTimeString() => ToString(); + + /// + /// Converts the value of the current TimeOnly object to its equivalent string representation using the formatting conventions of the current culture. + /// The TimeOnly object will be formatted in short form. + /// + /// A string that contains the short time string representation of the current TimeOnly object. + public override string ToString() => ToString("t"); + + /// + /// Converts the value of the current TimeOnly object to its equivalent string representation using the specified format and the formatting conventions of the current culture. + /// + /// A standard or custom time format string. + /// A string representation of value of the current TimeOnly object as specified by format. + public string ToString(string? format) => ToString(format, DateTimeFormatInfo.CurrentInfo); + + /// + /// Converts the value of the current TimeOnly object to its equivalent string representation using the specified culture-specific format information. + /// + /// An object that supplies culture-specific formatting information. + /// A string representation of value of the current TimeOnly object as specified by provider. + public string ToString(IFormatProvider? provider) => ToString("t", provider); + + /// + /// Converts the value of the current TimeOnly object to its equivalent string representation using the specified culture-specific format information. + /// + /// A standard or custom time format string. + /// An object that supplies culture-specific formatting information. + /// A string representation of value of the current TimeOnly object as specified by format and provider. + public string ToString(string? format, IFormatProvider? provider) + { + if (format == null || format.Length == 0) + { + format = "t"; + } + + if (format.Length == 1) + { + switch (format[0]) + { + case 'o': + case 'O': + { + Span destination = stackalloc char[16]; + bool b = DateTimeFormat.TryFormatTimeOnlyO(Hour, Minute, Second, _ticks % TimeSpan.TicksPerSecond, destination); + Debug.Assert(b); + + return destination.ToString(); + } + + case 'r': + case 'R': + { + Span destination = stackalloc char[8]; + bool b = DateTimeFormat.TryFormatTimeOnlyR(Hour, Minute, Second, destination); + Debug.Assert(b); + + return destination.ToString(); + } + + case 't': + case 'T': + return DateTimeFormat.Format(ToDateTime(), format, provider); + + default: + throw new FormatException(SR.Format_InvalidString); + } + } + + DateTimeFormat.IsValidCustomTimeFormat(format.AsSpan(), allowThrow: true); + return DateTimeFormat.Format(ToDateTime(), format, provider); + } + + /// + /// Tries to format the value of the current TimeOnly instance into the provided span of characters. + /// + /// When this method returns, this instance's value formatted as a span of characters. + /// When this method returns, the number of characters that were written in destination. + /// A span containing the characters that represent a standard or custom format string that defines the acceptable format for destination. + /// An optional object that supplies culture-specific formatting information for destination. + /// true if the formatting was successful; otherwise, false. + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default(ReadOnlySpan), IFormatProvider? provider = null) + { + if (format.Length == 0) + { + format = "t".AsSpan(); + } + + if (format.Length == 1) + { + switch (format[0]) + { + case 'o': + case 'O': + if (!DateTimeFormat.TryFormatTimeOnlyO(Hour, Minute, Second, _ticks % TimeSpan.TicksPerSecond, destination)) + { + charsWritten = 0; + return false; + } + charsWritten = 16; + return true; + + case 'r': + case 'R': + if (!DateTimeFormat.TryFormatTimeOnlyR(Hour, Minute, Second, destination)) + { + charsWritten = 0; + return false; + } + charsWritten = 8; + return true; + + case 't': + case 'T': + return DateTimeFormat.TryFormat(ToDateTime(), destination, out charsWritten, format, provider); + + default: + charsWritten = 0; + return false; + } + } + + if (!DateTimeFormat.IsValidCustomTimeFormat(format, allowThrow: false)) + { + charsWritten = 0; + return false; + } + + return DateTimeFormat.TryFormat(ToDateTime(), destination, out charsWritten, format, provider); + } + } +} \ No newline at end of file diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 6292cc6b7e7db0..8c577fbf442f23 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -1294,6 +1294,68 @@ public static partial class Convert public static bool TryToBase64Chars(System.ReadOnlySpan bytes, System.Span chars, out int charsWritten, System.Base64FormattingOptions options = System.Base64FormattingOptions.None) { throw null; } } public delegate TOutput Converter(TInput input); + public readonly struct DateOnly : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable + { + public static DateOnly MinValue { get { throw null; } } + public static DateOnly MaxValue { get { throw null; } } + public DateOnly(int year, int month, int day) { throw null; } + public DateOnly(int year, int month, int day, System.Globalization.Calendar calendar) { throw null; } + public static DateOnly FromDayNumber(int dayNumber) { throw null; } + public int Year { get { throw null; } } + public int Month { get { throw null; } } + public int Day { get { throw null; } } + public System.DayOfWeek DayOfWeek { get { throw null; } } + public int DayOfYear { get { throw null; } } + public int DayNumber { get { throw null; } } + public System.DateOnly AddDays(int value) { throw null; } + public System.DateOnly AddMonths(int value) { throw null; } + public System.DateOnly AddYears(int value) { throw null; } + public static bool operator ==(System.DateOnly left, System.DateOnly right) { throw null; } + public static bool operator >(System.DateOnly left, System.DateOnly right) { throw null; } + public static bool operator >=(System.DateOnly left, System.DateOnly right) { throw null; } + public static bool operator !=(System.DateOnly left, System.DateOnly right) { throw null; } + public static bool operator <(System.DateOnly left, System.DateOnly right) { throw null; } + public static bool operator <=(System.DateOnly left, System.DateOnly right) { throw null; } + public System.DateTime ToDateTime(System.TimeOnly time) { throw null; } + public System.DateTime ToDateTime(System.TimeOnly time, System.DateTimeKind kind) { throw null; } + public static System.DateOnly FromDateTime(System.DateTime dateTime) { throw null; } + public int CompareTo(System.DateOnly value) { throw null; } + public int CompareTo(object? value) { throw null; } + public bool Equals(System.DateOnly value) { throw null; } + public override bool Equals(object? value) { throw null; } + public override int GetHashCode() { throw null; } + public static System.DateOnly Parse(System.ReadOnlySpan s) { throw null; } + public static System.DateOnly Parse(System.ReadOnlySpan s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.DateOnly ParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format) { throw null; } + public static System.DateOnly ParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.DateOnly ParseExact(System.ReadOnlySpan s, string [] formats) { throw null; } + public static System.DateOnly ParseExact(System.ReadOnlySpan s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.DateOnly Parse(string s) { throw null; } + public static System.DateOnly Parse(string s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.DateOnly ParseExact(string s, string format) { throw null; } + public static System.DateOnly ParseExact(string s, string format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.DateOnly ParseExact(string s, string [] formats) { throw null; } + public static System.DateOnly ParseExact(string s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, out System.DateOnly result) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.DateOnly result) { throw null; } + public static bool TryParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, out System.DateOnly result) { throw null; } + public static bool TryParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.DateOnly result) { throw null; } + public static bool TryParseExact(System.ReadOnlySpan s, string [] formats, out System.DateOnly result) { throw null; } + public static bool TryParseExact(System.ReadOnlySpan s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.DateOnly result) { throw null; } + public static bool TryParse(string s, out System.DateOnly result) { throw null; } + public static bool TryParse(string s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.DateOnly result) { throw null; } + public static bool TryParseExact(string s, string format, out System.DateOnly result) { throw null; } + public static bool TryParseExact(string s, string format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.DateOnly result) { throw null; } + public static bool TryParseExact(string s, string [] formats, out System.DateOnly result) { throw null; } + public static bool TryParseExact(string s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.DateOnly result) { throw null; } + public string ToLongDateString() { throw null; } + public string ToShortDateString() { throw null; } + public override string ToString() { throw null; } + public string ToString(string? format) { throw null; } + public string ToString(System.IFormatProvider? provider) { throw null; } + public string ToString(string? format, System.IFormatProvider? provider) { throw null; } + public bool TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + } public readonly partial struct DateTime : System.IComparable, System.IComparable, System.IConvertible, System.IEquatable, System.IFormattable, System.Runtime.Serialization.ISerializable { private readonly int _dummyPrimitive; @@ -3738,6 +3800,73 @@ public partial class ThreadStaticAttribute : System.Attribute { public ThreadStaticAttribute() { } } + public readonly struct TimeOnly : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable + { + public static System.TimeOnly MinValue { get { throw null; } } + public static System.TimeOnly MaxValue { get { throw null; } } + public TimeOnly(int hour, int minute) { throw null; } + public TimeOnly(int hour, int minute, int second) { throw null; } + public TimeOnly(int hour, int minute, int second, int millisecond) { throw null; } + public TimeOnly(long ticks) { throw null; } + public int Hour { get { throw null; } } + public int Minute { get { throw null; } } + public int Second { get { throw null; } } + public int Millisecond { get { throw null; } } + public long Ticks { get { throw null; } } + public System.TimeOnly Add(System.TimeSpan value) { throw null; } + public System.TimeOnly Add(System.TimeSpan value, out int wrappedDays) { throw null; } + public System.TimeOnly AddHours(double value) { throw null; } + public System.TimeOnly AddHours(double value, out int wrappedDays) { throw null; } + public System.TimeOnly AddMinutes(double value) { throw null; } + public System.TimeOnly AddMinutes(double value, out int wrappedDays) { throw null; } + public bool IsBetween(System.TimeOnly start, System.TimeOnly end) { throw null; } + public static bool operator ==(System.TimeOnly left, System.TimeOnly right) { throw null; } + public static bool operator >(System.TimeOnly left, System.TimeOnly right) { throw null; } + public static bool operator >=(System.TimeOnly left, System.TimeOnly right) { throw null; } + public static bool operator !=(System.TimeOnly left, System.TimeOnly right) { throw null; } + public static bool operator <(System.TimeOnly left, System.TimeOnly right) { throw null; } + public static bool operator <=(System.TimeOnly left, System.TimeOnly right) { throw null; } + public static System.TimeSpan operator -(System.TimeOnly t1, System.TimeOnly t2) { throw null; } + public static System.TimeOnly FromTimeSpan(System.TimeSpan timeSpan) { throw null; } + public static System.TimeOnly FromDateTime(System.DateTime dateTime) { throw null; } + public System.TimeSpan ToTimeSpan() { throw null; } + public int CompareTo(System.TimeOnly value) { throw null; } + public int CompareTo(object? value) { throw null; } + public bool Equals(System.TimeOnly value) { throw null; } + public override bool Equals(object? value) { throw null; } + public override int GetHashCode() { throw null; } + public static System.TimeOnly Parse(System.ReadOnlySpan s) { throw null; } + public static System.TimeOnly Parse(System.ReadOnlySpan s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.TimeOnly ParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format) { throw null; } + public static System.TimeOnly ParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.TimeOnly ParseExact(System.ReadOnlySpan s, string [] formats) { throw null; } + public static System.TimeOnly ParseExact(System.ReadOnlySpan s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.TimeOnly Parse(string s) { throw null; } + public static System.TimeOnly Parse(string s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.TimeOnly ParseExact(string s, string format) { throw null; } + public static System.TimeOnly ParseExact(string s, string format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.TimeOnly ParseExact(string s, string [] formats) { throw null; } + public static System.TimeOnly ParseExact(string s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, out System.TimeOnly result) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.TimeOnly result) { throw null; } + public static bool TryParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, out System.TimeOnly result) { throw null; } + public static bool TryParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.TimeOnly result) { throw null; } + public static bool TryParseExact(System.ReadOnlySpan s, string [] formats, out System.TimeOnly result) { throw null; } + public static bool TryParseExact(System.ReadOnlySpan s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.TimeOnly result) { throw null; } + public static bool TryParse(string s, out System.TimeOnly result) { throw null; } + public static bool TryParse(string s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.TimeOnly result) { throw null; } + public static bool TryParseExact(string s, string format, out System.TimeOnly result) { throw null; } + public static bool TryParseExact(string s, string format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.TimeOnly result) { throw null; } + public static bool TryParseExact(string s, string [] formats, out System.TimeOnly result) { throw null; } + public static bool TryParseExact(string s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.TimeOnly result) { throw null; } + public string ToLongTimeString() { throw null; } + public string ToShortTimeString() { throw null; } + public override string ToString() { throw null; } + public string ToString(string? format) { throw null; } + public string ToString(System.IFormatProvider? provider) { throw null; } + public string ToString(string? format, System.IFormatProvider? provider) { throw null; } + public bool TryFormat(System.Span destination, out int charsWritten, System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } + } public partial class TimeoutException : System.SystemException { public TimeoutException() { } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index e4b0b3580f5853..d9272e44addeae 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -59,6 +59,7 @@ + @@ -127,6 +128,7 @@ + diff --git a/src/libraries/System.Runtime/tests/System/DateOnlyTests.cs b/src/libraries/System.Runtime/tests/System/DateOnlyTests.cs new file mode 100644 index 00000000000000..56078042056a2b --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/DateOnlyTests.cs @@ -0,0 +1,518 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Globalization; +using Xunit; + +namespace System.Tests +{ + public class DateOnlyTests + { + [Fact] + public static void MinMaxValuesTest() + { + DateOnly date = DateOnly.MinValue; + Assert.Equal(0, date.DayNumber); + Assert.Equal(1, date.Year); + Assert.Equal(1, date.Month); + Assert.Equal(1, date.Day); + + date = DateOnly.MaxValue; + Assert.Equal(3652058, date.DayNumber); + Assert.Equal(9999, date.Year); + Assert.Equal(12, date.Month); + Assert.Equal(31, date.Day); + } + + public static IEnumerable Constructor_TestData() + { + yield return new object[] { 1, 1, 1, null }; + yield return new object[] { 9999, 12, 31, null }; + yield return new object[] { 2001, 4, 7, null }; + yield return new object[] { 1, 1, 1, new HijriCalendar() }; + yield return new object[] { 1, 1, 1, new JapaneseCalendar() }; + } + + [Theory] + [MemberData(nameof(Constructor_TestData))] + public static void ConstructorsTest(int year, int month, int day, Calendar calendar) + { + if (calendar == null) + { + DateOnly dateOnly = new DateOnly(year, month, day); + Assert.Equal(year, dateOnly.Year); + Assert.Equal(month, dateOnly.Month); + Assert.Equal(day, dateOnly.Day); + } + else + { + DateTime dt = calendar.ToDateTime(year, month, day, 0, 0, 0, 0); + DateOnly dateOnly = new DateOnly(year, month, day, calendar); + Assert.Equal(dt.Year, dateOnly.Year); + Assert.Equal(dt.Month, dateOnly.Month); + Assert.Equal(dt.Day, dateOnly.Day); + } + } + + [Fact] + public static void ConstructorsNegativeCasesTest() + { + Assert.Throws(() => new DateOnly(10000, 1, 1)); + Assert.Throws(() => new DateOnly(-2021, 1, 1)); + Assert.Throws(() => new DateOnly(2020, 13, 1)); + Assert.Throws(() => new DateOnly(2020, -1, 1)); + Assert.Throws(() => new DateOnly(2020, 0, 1)); + Assert.Throws(() => new DateOnly(2020, 1, 0)); + Assert.Throws(() => new DateOnly(2020, 1, 32)); + Assert.Throws(() => new DateOnly(2020, 1, -1)); + Assert.Throws(() => new DateOnly(2003, 2, 29)); + } + + [Fact] + public static void FromDayNumberTest() + { + DateOnly dateOnly = DateOnly.FromDayNumber(DateOnly.MinValue.DayNumber); + Assert.Equal(1, dateOnly.Year); + Assert.Equal(1, dateOnly.Month); + Assert.Equal(1, dateOnly.Day); + Assert.Equal(DateOnly.MinValue.DayNumber, dateOnly.DayNumber); + + dateOnly = DateOnly.FromDayNumber(DateOnly.MaxValue.DayNumber); + Assert.Equal(9999, dateOnly.Year); + Assert.Equal(12, dateOnly.Month); + Assert.Equal(31, dateOnly.Day); + Assert.Equal(DateOnly.MaxValue.DayNumber, dateOnly.DayNumber); + + DateTime dt = DateTime.Today; + int dayNumber = (int) (dt.Ticks / TimeSpan.TicksPerDay); + dateOnly = DateOnly.FromDayNumber(dayNumber); + Assert.Equal(dt.Year, dateOnly.Year); + Assert.Equal(dt.Month, dateOnly.Month); + Assert.Equal(dt.Day, dateOnly.Day); + Assert.Equal(dayNumber, dateOnly.DayNumber); + + Assert.Throws(() => DateOnly.FromDayNumber(-1)); + Assert.Throws(() => DateOnly.FromDayNumber(DateOnly.MaxValue.DayNumber + 1)); + } + + [Fact] + public static void DayOfWeekAndDayOfYearTest() + { + DateTime dt = DateTime.Today; + DateOnly dateOnly = DateOnly.FromDayNumber((int) (dt.Ticks / TimeSpan.TicksPerDay)); + Assert.Equal(dt.DayOfWeek, dateOnly.DayOfWeek); + Assert.Equal(dt.DayOfYear, dateOnly.DayOfYear); + } + + + [Fact] + public static void AddDaysTest() + { + DateOnly dateOnly = DateOnly.MinValue.AddDays(1); + Assert.Equal(1, dateOnly.DayNumber); + dateOnly = dateOnly.AddDays(1); + Assert.Equal(2, dateOnly.DayNumber); + dateOnly = dateOnly.AddDays(100); + Assert.Equal(102, dateOnly.DayNumber); + + dateOnly = DateOnly.MaxValue.AddDays(-1); + Assert.Equal(DateOnly.MaxValue.DayNumber - 1, dateOnly.DayNumber); + dateOnly = dateOnly.AddDays(-1); + Assert.Equal(DateOnly.MaxValue.DayNumber - 2, dateOnly.DayNumber); + dateOnly = dateOnly.AddDays(-100); + Assert.Equal(DateOnly.MaxValue.DayNumber - 102, dateOnly.DayNumber); + + Assert.Throws(() => DateOnly.MinValue.AddDays(-1)); + Assert.Throws(() => DateOnly.MaxValue.AddDays(1)); + } + + [Fact] + public static void AddMonthsTest() + { + DateOnly dateOnly = new DateOnly(2021, 1, 31); + for (int i = 1; i < 12; i++) + { + Assert.Equal(i, dateOnly.Month); + dateOnly = dateOnly.AddMonths(1); + } + + for (int i = 12; i > 1; i--) + { + Assert.Equal(i, dateOnly.Month); + dateOnly = dateOnly.AddMonths(-1); + } + + DateTime dt = DateTime.Today; + dateOnly = DateOnly.FromDayNumber((int) (dt.Ticks / TimeSpan.TicksPerDay)); + + Assert.Equal(dt.Year, dateOnly.Year); + Assert.Equal(dt.Month, dateOnly.Month); + Assert.Equal(dt.Day, dateOnly.Day); + + dt = dt.AddMonths(1); + dateOnly = dateOnly.AddMonths(1); + Assert.Equal(dt.Month, dateOnly.Month); + + dt = dt.AddMonths(50); + dateOnly = dateOnly.AddMonths(50); + Assert.Equal(dt.Month, dateOnly.Month); + + dt = dt.AddMonths(-150); + dateOnly = dateOnly.AddMonths(-150); + Assert.Equal(dt.Month, dateOnly.Month); + + Assert.Throws(() => DateOnly.MinValue.AddMonths(-1)); + Assert.Throws(() => DateOnly.MaxValue.AddMonths(1)); + } + + [Fact] + public static void AddYearsTest() + { + DateOnly dateOnly = new DateOnly(2021, 1, 31); + for (int i = 2021; i < 2040; i++) + { + Assert.Equal(i, dateOnly.Year); + dateOnly = dateOnly.AddYears(1); + } + + for (int i = dateOnly.Year; i > 2020; i--) + { + Assert.Equal(i, dateOnly.Year); + dateOnly = dateOnly.AddYears(-1); + } + + DateTime dt = DateTime.Today; + dateOnly = DateOnly.FromDayNumber((int) (dt.Ticks / TimeSpan.TicksPerDay)); + + Assert.Equal(dt.Year, dateOnly.Year); + Assert.Equal(dt.Month, dateOnly.Month); + Assert.Equal(dt.Day, dateOnly.Day); + + dt = dt.AddYears(1); + dateOnly = dateOnly.AddYears(1); + Assert.Equal(dt.Year, dateOnly.Year); + + dt = dt.AddYears(50); + dateOnly = dateOnly.AddYears(50); + Assert.Equal(dt.Year, dateOnly.Year); + + dt = dt.AddYears(-150); + dateOnly = dateOnly.AddYears(-150); + Assert.Equal(dt.Year, dateOnly.Year); + + Assert.Throws(() => DateOnly.MinValue.AddYears(-1)); + Assert.Throws(() => DateOnly.MaxValue.AddYears(1)); + } + + [Fact] + public static void OperatorsTest() + { + Assert.True(DateOnly.MinValue != DateOnly.MaxValue); + Assert.True(DateOnly.MinValue < DateOnly.MaxValue); + Assert.True(DateOnly.MinValue <= DateOnly.MaxValue); + Assert.True(DateOnly.MaxValue > DateOnly.MinValue); + Assert.True(DateOnly.MaxValue >= DateOnly.MinValue); + + DateOnly dateOnly1 = new DateOnly(2021, 10, 10); + DateOnly dateOnly2 = new DateOnly(2021, 10, 11); + DateOnly dateOnly3 = new DateOnly(2021, 10, 10); + + Assert.True(dateOnly1 == dateOnly3); + Assert.True(dateOnly1 >= dateOnly3); + Assert.True(dateOnly1 <= dateOnly3); + Assert.True(dateOnly1 != dateOnly2); + Assert.True(dateOnly1 < dateOnly2); + Assert.True(dateOnly1 <= dateOnly2); + Assert.True(dateOnly2 > dateOnly1); + Assert.True(dateOnly2 >= dateOnly1); + } + + [Fact] + public static void DateTimeConversionTest() + { + DateTime dt = DateTime.Today; + DateOnly dateOnly = DateOnly.FromDateTime(dt); + Assert.Equal(dt.Year, dateOnly.Year); + Assert.Equal(dt.Month, dateOnly.Month); + Assert.Equal(dt.Day, dateOnly.Day); + + dt = dateOnly.ToDateTime(new TimeOnly(1, 10, 20)); + Assert.Equal(dateOnly.Year, dt.Year); + Assert.Equal(dateOnly.Month, dt.Month); + Assert.Equal(dateOnly.Day, dt.Day); + + Assert.Equal(1, dt.Hour); + Assert.Equal(10, dt.Minute); + Assert.Equal(20, dt.Second); + Assert.Equal(DateTimeKind.Unspecified, dt.Kind); + + + dt = dateOnly.ToDateTime(new TimeOnly(23, 59, 59), DateTimeKind.Utc); + Assert.Equal(dateOnly.Year, dt.Year); + Assert.Equal(dateOnly.Month, dt.Month); + Assert.Equal(dateOnly.Day, dt.Day); + + Assert.Equal(23, dt.Hour); + Assert.Equal(59, dt.Minute); + Assert.Equal(59, dt.Second); + Assert.Equal(DateTimeKind.Utc, dt.Kind); + + dt = dateOnly.ToDateTime(new TimeOnly(23, 59, 59), DateTimeKind.Local); + Assert.Equal(DateTimeKind.Local, dt.Kind); + + dateOnly = DateOnly.FromDateTime(dt); + Assert.Equal(dt.Year, dateOnly.Year); + Assert.Equal(dt.Month, dateOnly.Month); + Assert.Equal(dt.Day, dateOnly.Day); + } + + [Fact] + public static void ComparisonsTest() + { + DateOnly dateOnly1 = DateOnly.FromDateTime(DateTime.Today); + DateOnly dateOnly2 = DateOnly.FromDateTime(DateTime.Today); + DateOnly dateOnly3 = dateOnly1.AddYears(-10); + + Assert.Equal(0, dateOnly1.CompareTo(dateOnly2)); + Assert.True(dateOnly1.Equals(dateOnly2)); + Assert.True(dateOnly1.Equals((object)dateOnly2)); + Assert.Equal(0, dateOnly2.CompareTo(dateOnly1)); + Assert.True(dateOnly2.Equals(dateOnly1)); + Assert.True(dateOnly2.Equals((object)dateOnly1)); + Assert.Equal(1, dateOnly1.CompareTo(dateOnly3)); + Assert.False(dateOnly1.Equals(dateOnly3)); + Assert.False(dateOnly1.Equals((object)dateOnly3)); + Assert.Equal(-1, dateOnly3.CompareTo(dateOnly1)); + Assert.False(dateOnly3.Equals(dateOnly1)); + Assert.False(dateOnly3.Equals((object)dateOnly1)); + + Assert.Equal(0, dateOnly1.CompareTo((object)dateOnly2)); + Assert.Equal(0, dateOnly2.CompareTo((object)dateOnly1)); + Assert.Equal(1, dateOnly1.CompareTo((object)dateOnly3)); + Assert.Equal(-1, dateOnly3.CompareTo((object)dateOnly1)); + + Assert.Equal(1, dateOnly1.CompareTo(null)); + + Assert.Throws(() => dateOnly1.CompareTo(new object())); + Assert.False(dateOnly3.Equals(new object())); + } + + [Fact] + public static void GetHashCodeTest() + { + Assert.Equal(DateOnly.MinValue.DayNumber, DateOnly.MinValue.GetHashCode()); + Assert.Equal(DateOnly.MaxValue.DayNumber, DateOnly.MaxValue.GetHashCode()); + DateOnly dateOnly = DateOnly.FromDateTime(DateTime.Today); + Assert.Equal(dateOnly.DayNumber, dateOnly.GetHashCode()); + } + + // Arabic cultures uses zero width characters in the date formatting which cause a problem with the DateTime parsing in general. + // We still test these cultures parsing but with ParseExact instead. + internal static bool IsNotArabicCulture => !CultureInfo.CurrentCulture.Name.StartsWith("ar", StringComparison.OrdinalIgnoreCase); + + [ConditionalFact(nameof(IsNotArabicCulture))] + public static void BasicFormatParseTest() + { + DateOnly dateOnly = DateOnly.FromDateTime(DateTime.Today); + string s = dateOnly.ToString(); + DateOnly parsedDateOnly = DateOnly.Parse(s); + Assert.True(DateOnly.TryParse(s, out DateOnly parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + parsedDateOnly = DateOnly.Parse(s.AsSpan()); + Assert.True(DateOnly.TryParse(s.AsSpan(), out parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + + s = dateOnly.ToString(CultureInfo.InvariantCulture); + parsedDateOnly = DateOnly.Parse(s, CultureInfo.InvariantCulture); + Assert.True(DateOnly.TryParse(s.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + parsedDateOnly = DateOnly.Parse(s.AsSpan(), CultureInfo.InvariantCulture); + Assert.True(DateOnly.TryParse(s.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + + Assert.False(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.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal)); + Assert.False(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.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault)); + + s = " " + s + " "; + parsedDateOnly = DateOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces); + Assert.Equal(dateOnly, parsedDateOnly); + parsedDateOnly = DateOnly.Parse(s.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces); + Assert.Equal(dateOnly, parsedDateOnly); + } + + [ConditionalFact(nameof(IsNotArabicCulture))] + public static void FormatParseTest() + { + string [] patterns = new string[] { CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern, CultureInfo.CurrentCulture.DateTimeFormat.LongDatePattern, "d", "D", "o", "r" }; + + DateOnly dateOnly = DateOnly.FromDateTime(DateTime.Today); + + foreach (string format in patterns) + { + string formattedDate = dateOnly.ToString(format); + DateOnly parsedDateOnly = DateOnly.Parse(formattedDate); + Assert.True(DateOnly.TryParse(formattedDate, out DateOnly parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + parsedDateOnly = DateOnly.Parse(formattedDate.AsSpan()); + Assert.True(DateOnly.TryParse(formattedDate.AsSpan(), out parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + + parsedDateOnly = DateOnly.Parse(formattedDate, CultureInfo.CurrentCulture); + Assert.True(DateOnly.TryParse(formattedDate, CultureInfo.CurrentCulture, DateTimeStyles.None, out parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + parsedDateOnly = DateOnly.Parse(formattedDate.AsSpan(), CultureInfo.CurrentCulture); + Assert.True(DateOnly.TryParse(formattedDate.AsSpan(), CultureInfo.CurrentCulture, DateTimeStyles.None, out parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + + parsedDateOnly = DateOnly.ParseExact(formattedDate, format); + Assert.True(DateOnly.TryParseExact(formattedDate, format, out parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + parsedDateOnly = DateOnly.ParseExact(formattedDate.AsSpan(), format.AsSpan()); + Assert.True(DateOnly.TryParseExact(formattedDate.AsSpan(), format.AsSpan(), out parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + + parsedDateOnly = DateOnly.ParseExact(formattedDate, format, CultureInfo.CurrentCulture); + Assert.True(DateOnly.TryParseExact(formattedDate, format, CultureInfo.CurrentCulture, DateTimeStyles.None, out parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + parsedDateOnly = DateOnly.ParseExact(formattedDate.AsSpan(), format.AsSpan(), CultureInfo.CurrentCulture); + Assert.True(DateOnly.TryParseExact(formattedDate.AsSpan(), format.AsSpan(), CultureInfo.CurrentCulture, DateTimeStyles.None, out parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + + parsedDateOnly = DateOnly.ParseExact(formattedDate, patterns); + Assert.True(DateOnly.TryParseExact(formattedDate, patterns, out parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + parsedDateOnly = DateOnly.ParseExact(formattedDate.AsSpan(), patterns); + Assert.True(DateOnly.TryParseExact(formattedDate.AsSpan(), patterns, out parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + + parsedDateOnly = DateOnly.ParseExact(formattedDate, patterns, CultureInfo.CurrentCulture); + Assert.True(DateOnly.TryParseExact(formattedDate, patterns, CultureInfo.CurrentCulture, DateTimeStyles.None, out parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + parsedDateOnly = DateOnly.ParseExact(formattedDate.AsSpan(), patterns, CultureInfo.CurrentCulture); + Assert.True(DateOnly.TryParseExact(formattedDate.AsSpan(), patterns, CultureInfo.CurrentCulture, DateTimeStyles.None, out parsedDateOnly1)); + Assert.Equal(dateOnly, parsedDateOnly); + Assert.Equal(dateOnly, parsedDateOnly1); + } + } + + [Fact] + public static void OAndRFormatsTest() + { + DateOnly dateOnly = DateOnly.FromDateTime(DateTime.Today); + string formattedDate = dateOnly.ToString("o"); + Assert.Equal(10, formattedDate.Length); + Assert.Equal('-', formattedDate[4]); + Assert.Equal('-', formattedDate[7]); + DateOnly parsedDateOnly = DateOnly.Parse(formattedDate); + Assert.Equal(dateOnly, parsedDateOnly); + parsedDateOnly = DateOnly.Parse(formattedDate.AsSpan()); + Assert.Equal(dateOnly, parsedDateOnly); + parsedDateOnly = DateOnly.ParseExact(formattedDate, "O"); + Assert.Equal(dateOnly, parsedDateOnly); + parsedDateOnly = DateOnly.ParseExact(formattedDate.AsSpan(), "O".AsSpan()); + Assert.Equal(dateOnly, parsedDateOnly); + + formattedDate = dateOnly.ToString("r"); + Assert.Equal(16, formattedDate.Length); + Assert.Equal(',', formattedDate[3]); + Assert.Equal(' ', formattedDate[4]); + Assert.Equal(' ', formattedDate[7]); + Assert.Equal(' ', formattedDate[11]); + parsedDateOnly = DateOnly.Parse(formattedDate); + Assert.Equal(dateOnly, parsedDateOnly); + parsedDateOnly = DateOnly.Parse(formattedDate.AsSpan()); + Assert.Equal(dateOnly, parsedDateOnly); + parsedDateOnly = DateOnly.ParseExact(formattedDate, "R"); + Assert.Equal(dateOnly, parsedDateOnly); + parsedDateOnly = DateOnly.ParseExact(formattedDate.AsSpan(), "R".AsSpan()); + Assert.Equal(dateOnly, parsedDateOnly); + } + + [Fact] + public static void InvalidFormatsTest() + { + DateTime dt = DateTime.Now; + string formatted = dt.ToString(); + Assert.Throws(() => DateOnly.Parse(formatted)); + Assert.Throws(() => DateOnly.Parse(formatted.AsSpan())); + Assert.False(DateOnly.TryParse(formatted, out DateOnly dateOnly)); + Assert.False(DateOnly.TryParse(formatted.AsSpan(), out dateOnly)); + formatted = dt.ToString("t"); + Assert.Throws(() => DateOnly.Parse(formatted)); + Assert.Throws(() => DateOnly.Parse(formatted.AsSpan())); + Assert.False(DateOnly.TryParse(formatted, out dateOnly)); + Assert.False(DateOnly.TryParse(formatted.AsSpan(), out dateOnly)); + } + + [Fact] + public static void CustomFormattingTest() + { + DateOnly dateOnly = DateOnly.FromDateTime(DateTime.Today); + string format = "dd, ddd 'dash' MMMM \"dash\" yyyy"; + string formatted = dateOnly.ToString(format); + DateOnly parsedDateOnly = DateOnly.ParseExact(formatted, format); + Assert.Equal(dateOnly, parsedDateOnly); + parsedDateOnly = DateOnly.ParseExact(formatted.AsSpan(), format.AsSpan()); + Assert.Equal(dateOnly, parsedDateOnly); + + Assert.Throws(() => dateOnly.ToString("dd-MM-yyyy hh")); + Assert.Throws(() => dateOnly.ToString("dd-MM-yyyy m")); + Assert.Throws(() => dateOnly.ToString("dd-MM-yyyy s")); + Assert.Throws(() => dateOnly.ToString("dd-MM-yyyy z")); + } + + [Fact] + public static void AllCulturesTest() + { + DateOnly dateOnly = DateOnly.FromDateTime(DateTime.Today); + foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.SpecificCultures)) + { + string formatted = dateOnly.ToString("d", ci); + DateOnly parsedDateOnly = DateOnly.ParseExact(formatted, "d", ci); + Assert.Equal(dateOnly, parsedDateOnly); + + formatted = dateOnly.ToString("D", ci); + parsedDateOnly = DateOnly.ParseExact(formatted, "D", ci); + Assert.Equal(dateOnly, parsedDateOnly); + } + } + + [Fact] + public static void TryFormatTest() + { + Span buffer = stackalloc char[100]; + DateOnly dateOnly = DateOnly.FromDateTime(DateTime.Today); + + Assert.True(dateOnly.TryFormat(buffer, out int charsWritten)); + Assert.True(dateOnly.TryFormat(buffer, out charsWritten, "o")); + Assert.Equal(10, charsWritten); + Assert.True(dateOnly.TryFormat(buffer, out charsWritten, "R")); + Assert.Equal(16, charsWritten); + Assert.False(dateOnly.TryFormat(buffer.Slice(0, 3), out charsWritten)); + Assert.False(dateOnly.TryFormat(buffer.Slice(0, 3), out charsWritten, "r")); + Assert.False(dateOnly.TryFormat(buffer.Slice(0, 3), out charsWritten, "O")); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs new file mode 100644 index 00000000000000..b067ca9b2803b5 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs @@ -0,0 +1,466 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Globalization; +using Xunit; + +namespace System.Tests +{ + public class TimeOnlyTests + { + [Fact] + public static void MinMaxValuesTest() + { + Assert.Equal(0, TimeOnly.MinValue.Ticks); + Assert.Equal(0, TimeOnly.MinValue.Hour); + Assert.Equal(0, TimeOnly.MinValue.Minute); + Assert.Equal(0, TimeOnly.MinValue.Second); + Assert.Equal(0, TimeOnly.MinValue.Millisecond); + + Assert.Equal(DateTime.Today.AddTicks(-1).TimeOfDay.Ticks, TimeOnly.MaxValue.Ticks); // ticks should be 863999999999; + Assert.Equal(23, TimeOnly.MaxValue.Hour); + Assert.Equal(59, TimeOnly.MaxValue.Minute); + Assert.Equal(59, TimeOnly.MaxValue.Second); + Assert.Equal(999, TimeOnly.MaxValue.Millisecond); + } + + [Fact] + public static void ConstructorsTest() + { + TimeOnly to = new TimeOnly(14, 35); + Assert.Equal(14, to.Hour); + Assert.Equal(35, to.Minute); + Assert.Equal(0, to.Second); + Assert.Equal(0, to.Millisecond); + Assert.Equal(new DateTime(1, 1, 1, to.Hour, to.Minute, to.Second, to.Millisecond).Ticks, to.Ticks); + + to = new TimeOnly(10, 20, 30); + Assert.Equal(10, to.Hour); + Assert.Equal(20, to.Minute); + Assert.Equal(30, to.Second); + Assert.Equal(0, to.Millisecond); + Assert.Equal(new DateTime(1, 1, 1, to.Hour, to.Minute, to.Second, to.Millisecond).Ticks, to.Ticks); + + to = new TimeOnly(23, 59, 59, 999); + Assert.Equal(23, to.Hour); + Assert.Equal(59, to.Minute); + Assert.Equal(59, to.Second); + Assert.Equal(999, to.Millisecond); + Assert.Equal(new DateTime(1, 1, 1, to.Hour, to.Minute, to.Second, to.Millisecond).Ticks, to.Ticks); + + DateTime dt = DateTime.Now; + to = new TimeOnly(dt.TimeOfDay.Ticks); + Assert.Equal(dt.Hour, to.Hour); + Assert.Equal(dt.Minute, to.Minute); + Assert.Equal(dt.Second, to.Second); + Assert.Equal(dt.Millisecond, to.Millisecond); + + Assert.Throws(() => new TimeOnly(24, 10)); + Assert.Throws(() => new TimeOnly(-1, 10)); + Assert.Throws(() => new TimeOnly(10, 60)); + Assert.Throws(() => new TimeOnly(10, -2)); + Assert.Throws(() => new TimeOnly(10, 10, 60)); + Assert.Throws(() => new TimeOnly(10, 10, -3)); + Assert.Throws(() => new TimeOnly(10, 10, 10, 1000)); + Assert.Throws(() => new TimeOnly(10, 10, 10, -4)); + Assert.Throws(() => new TimeOnly(TimeOnly.MaxValue.Ticks + 1)); + Assert.Throws(() => new TimeOnly(-1)); + } + + [Fact] + public static void AddTest() + { + TimeOnly to = new TimeOnly(1, 10, 20, 900); + to = to.Add(new TimeSpan(TimeSpan.TicksPerMillisecond)); + Assert.Equal(901, to.Millisecond); + to = to.Add(new TimeSpan(TimeSpan.TicksPerSecond)); + Assert.Equal(21, to.Second); + to = to.Add(new TimeSpan(TimeSpan.TicksPerMinute)); + Assert.Equal(11, to.Minute); + to = to.Add(new TimeSpan(TimeSpan.TicksPerHour)); + Assert.Equal(2, to.Hour); + + to = TimeOnly.MinValue.Add(new TimeSpan(-1), out int wrappedDays); + Assert.Equal(23, to.Hour); + Assert.Equal(59, to.Minute); + Assert.Equal(59, to.Second); + Assert.Equal(999, to.Millisecond); + Assert.Equal(-1, wrappedDays); + + to = TimeOnly.MinValue.Add(new TimeSpan(48, 0, 0), out wrappedDays); + Assert.Equal(0, to.Hour); + Assert.Equal(0, to.Minute); + Assert.Equal(0, to.Second); + Assert.Equal(0, to.Millisecond); + Assert.Equal(2, wrappedDays); + to = to.Add(new TimeSpan(1, 0, 0), out wrappedDays); + Assert.Equal(0, wrappedDays); + + to = TimeOnly.MinValue.AddHours(1.5); + Assert.Equal(1, to.Hour); + Assert.Equal(30, to.Minute); + Assert.Equal(0, to.Second); + Assert.Equal(0, to.Millisecond); + to = to.AddHours(1.5, out wrappedDays); + Assert.Equal(3, to.Hour); + Assert.Equal(0, to.Minute); + Assert.Equal(0, to.Second); + Assert.Equal(0, to.Millisecond); + Assert.Equal(0, wrappedDays); + to = to.AddHours(-28, out wrappedDays); + Assert.Equal(23, to.Hour); + Assert.Equal(0, to.Minute); + Assert.Equal(-2, wrappedDays); + to = to.AddHours(1, out wrappedDays); + Assert.Equal(1, wrappedDays); + Assert.Equal(0, to.Hour); + Assert.Equal(0, to.Minute); + + to = to.AddMinutes(190.5); + Assert.Equal(3, to.Hour); + Assert.Equal(10, to.Minute); + Assert.Equal(30, to.Second); + + to = to.AddMinutes(-4 * 60, out wrappedDays); + Assert.Equal(23, to.Hour); + Assert.Equal(10, to.Minute); + Assert.Equal(30, to.Second); + Assert.Equal(-1, wrappedDays); + + to = to.AddMinutes(60.5, out wrappedDays); + Assert.Equal(0, to.Hour); + Assert.Equal(11, to.Minute); + Assert.Equal(0, to.Second); + Assert.Equal(1, wrappedDays); + } + + [Fact] + public static void IsBetweenTest() + { + TimeOnly to1 = new TimeOnly(14, 30); + TimeOnly to2 = new TimeOnly(2, 0); + TimeOnly to3 = new TimeOnly(12, 0); + + Assert.True(to3.IsBetween(to2, to1)); + Assert.True(to1.IsBetween(to3, to2)); + + Assert.True(to3.IsBetween(to3, to1)); + Assert.True(to1.IsBetween(to1, to2)); + + Assert.False(to1.IsBetween(to3, to1)); + Assert.False(to2.IsBetween(to3, to2)); + + Assert.True(to1.IsBetween(to3, to1.Add(new TimeSpan(1)))); + Assert.True(to2.IsBetween(to3, to2.Add(new TimeSpan(1)))); + } + + [Fact] + public static void CompareOperatosTest() + { + TimeOnly to1 = new TimeOnly(14, 30); + TimeOnly to2 = new TimeOnly(14, 30); + TimeOnly to3 = new TimeOnly(14, 30, 1); + + Assert.True(to1 == to2); + Assert.True(to1 >= to2); + Assert.True(to1 <= to2); + + Assert.True(to1 != to3); + Assert.True(to1 < to3); + Assert.True(to1 <= to3); + + Assert.True(to3 > to1); + Assert.True(to3 >= to1); + + Assert.False(to1 == to3); + Assert.False(to1 > to3); + Assert.False(to3 < to1); + Assert.False(to1 != to2); + } + + [Fact] + public static void SubtractOperatorTest() + { + TimeOnly to1 = new TimeOnly(10, 30, 40); + TimeOnly to2 = new TimeOnly(14, 0); + + Assert.Equal(new TimeSpan(3, 29, 20), to1 - to2); + Assert.Equal(new TimeSpan(3, 29, 20), to2 - to1); + Assert.Equal(TimeSpan.Zero, to1 - to1); + } + + [Fact] + public static void FromToTimeSpanTest() + { + Assert.Equal(TimeOnly.MinValue, TimeOnly.FromTimeSpan(TimeSpan.Zero)); + Assert.Equal(TimeSpan.Zero, TimeOnly.MinValue.ToTimeSpan()); + + Assert.Equal(new TimeOnly(10, 20, 30), TimeOnly.FromTimeSpan(new TimeSpan(10, 20, 30))); + Assert.Equal(new TimeSpan(14, 10, 50), new TimeOnly(14, 10, 50).ToTimeSpan()); + + Assert.Equal(TimeOnly.MaxValue, TimeOnly.FromTimeSpan(TimeOnly.MaxValue.ToTimeSpan())); + + Assert.Throws(() => TimeOnly.FromTimeSpan(new TimeSpan(24, 0, 0))); + Assert.Throws(() => TimeOnly.FromTimeSpan(new TimeSpan(-1, 0, 0))); + } + + [Fact] + public static void FromDateTimeTest() + { + DateTime dt = DateTime.Now; + TimeOnly timeOnly = TimeOnly.FromDateTime(dt); + + Assert.Equal(dt.Hour, timeOnly.Hour); + Assert.Equal(dt.Minute, timeOnly.Minute); + Assert.Equal(dt.Second, timeOnly.Second); + Assert.Equal(dt.Millisecond, timeOnly.Millisecond); + Assert.Equal(dt.TimeOfDay.Ticks, timeOnly.Ticks); + } + + [Fact] + public static void ComparisonsTest() + { + TimeOnly timeOnly1 = TimeOnly.FromDateTime(DateTime.Now); + TimeOnly timeOnly2 = timeOnly1.Add(new TimeSpan(1)); + TimeOnly timeOnly3 = new TimeOnly(timeOnly1.Ticks); + + Assert.Equal(-1, timeOnly1.CompareTo(timeOnly2)); + Assert.Equal(1, timeOnly2.CompareTo(timeOnly1)); + Assert.Equal(-1, timeOnly1.CompareTo(timeOnly2)); + Assert.Equal(0, timeOnly1.CompareTo(timeOnly3)); + + Assert.Equal(-1, timeOnly1.CompareTo((object)timeOnly2)); + Assert.Equal(1, timeOnly2.CompareTo((object)timeOnly1)); + Assert.Equal(-1, timeOnly1.CompareTo((object)timeOnly2)); + Assert.Equal(0, timeOnly1.CompareTo((object)timeOnly3)); + + Assert.True(timeOnly1.Equals(timeOnly3)); + Assert.True(timeOnly1.Equals((object)timeOnly3)); + Assert.False(timeOnly2.Equals(timeOnly3)); + Assert.False(timeOnly2.Equals((object)timeOnly3)); + + Assert.False(timeOnly2.Equals(null)); + Assert.False(timeOnly2.Equals(new object())); + } + + [Fact] + public static void GetHashCodeTest() + { + TimeOnly timeOnly1 = TimeOnly.FromDateTime(DateTime.Now); + TimeOnly timeOnly2 = timeOnly1.Add(new TimeSpan(1)); + TimeOnly timeOnly3 = new TimeOnly(timeOnly1.Ticks); + + Assert.True(timeOnly1.GetHashCode() == timeOnly3.GetHashCode()); + Assert.False(timeOnly1.GetHashCode() == timeOnly2.GetHashCode()); + } + + // Arabic cultures uses zero width characters in the date formatting which cause a problem with the DateTime parsing in general. + // We still test these cultures parsing but with ParseExact instead. + internal static bool IsNotArabicCulture => !CultureInfo.CurrentCulture.Name.StartsWith("ar", StringComparison.OrdinalIgnoreCase); + + [ConditionalFact(nameof(IsNotArabicCulture))] + public static void BasicFormatParseTest() + { + string pattern = "hh:mm:ss tt"; + DateTime dt = DateTime.Now; + TimeOnly timeOnly = new TimeOnly(dt.Hour, dt.Minute, dt.Second); + string s = timeOnly.ToString(pattern); + TimeOnly parsedTimeOnly = TimeOnly.Parse(s); + Assert.True(TimeOnly.TryParse(s, out TimeOnly parsedTimeOnly1)); + Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly, parsedTimeOnly1); + parsedTimeOnly = TimeOnly.Parse(s.AsSpan()); + Assert.True(TimeOnly.TryParse(s.AsSpan(), out parsedTimeOnly1)); + Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly, parsedTimeOnly1); + + s = timeOnly.ToString(pattern, CultureInfo.InvariantCulture); + parsedTimeOnly = TimeOnly.Parse(s, CultureInfo.InvariantCulture); + Assert.True(TimeOnly.TryParse(s.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedTimeOnly1)); + Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly, parsedTimeOnly1); + parsedTimeOnly = TimeOnly.Parse(s.AsSpan(), CultureInfo.InvariantCulture); + 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)); + Assert.Throws(() => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal)); + Assert.False(TimeOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out parsedTimeOnly1)); + Assert.Throws(() => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal)); + Assert.False(TimeOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out parsedTimeOnly1)); + Assert.Throws(() => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal)); + Assert.False(TimeOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out parsedTimeOnly1)); + Assert.Throws(() => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault)); + + s = " " + s + " "; + parsedTimeOnly = TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces); + Assert.Equal(timeOnly, parsedTimeOnly); + parsedTimeOnly = TimeOnly.Parse(s.AsSpan(), CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces); + Assert.Equal(timeOnly, parsedTimeOnly); + } + + [ConditionalFact(nameof(IsNotArabicCulture))] + public static void FormatParseTest() + { + string [] patterns = new string[] { CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern, CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern, "t", "T", "o", "r" }; + + TimeOnly timeOnly = TimeOnly.FromDateTime(DateTime.Now); + + foreach (string format in patterns) + { + string formattedTime = timeOnly.ToString(format); + timeOnly = TimeOnly.Parse(formattedTime); + + Assert.True(TimeOnly.TryParse(formattedTime, out TimeOnly parsedTimeOnly1)); + Assert.Equal(timeOnly, parsedTimeOnly1); + TimeOnly parsedTimeOnly = TimeOnly.Parse(formattedTime.AsSpan()); + Assert.True(TimeOnly.TryParse(formattedTime.AsSpan(), out parsedTimeOnly1)); + Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly, parsedTimeOnly1); + + parsedTimeOnly = TimeOnly.Parse(formattedTime, CultureInfo.CurrentCulture); + Assert.True(TimeOnly.TryParse(formattedTime, CultureInfo.CurrentCulture, DateTimeStyles.None, out parsedTimeOnly1)); + Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly, parsedTimeOnly1); + parsedTimeOnly = TimeOnly.Parse(formattedTime.AsSpan(), CultureInfo.CurrentCulture); + Assert.True(TimeOnly.TryParse(formattedTime.AsSpan(), CultureInfo.CurrentCulture, DateTimeStyles.None, out parsedTimeOnly1)); + Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly, parsedTimeOnly1); + + parsedTimeOnly = TimeOnly.ParseExact(formattedTime, format); + Assert.True(TimeOnly.TryParseExact(formattedTime, format, out parsedTimeOnly1)); + Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly, parsedTimeOnly1); + parsedTimeOnly = TimeOnly.ParseExact(formattedTime.AsSpan(), format.AsSpan()); + Assert.True(TimeOnly.TryParseExact(formattedTime.AsSpan(), format.AsSpan(), out parsedTimeOnly1)); + Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly, parsedTimeOnly1); + + parsedTimeOnly = TimeOnly.ParseExact(formattedTime, format, CultureInfo.CurrentCulture); + Assert.True(TimeOnly.TryParseExact(formattedTime, format, CultureInfo.CurrentCulture, DateTimeStyles.None, out parsedTimeOnly1)); + Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly, parsedTimeOnly1); + parsedTimeOnly = TimeOnly.ParseExact(formattedTime.AsSpan(), format.AsSpan(), CultureInfo.CurrentCulture); + Assert.True(TimeOnly.TryParseExact(formattedTime.AsSpan(), format.AsSpan(), CultureInfo.CurrentCulture, DateTimeStyles.None, out parsedTimeOnly1)); + Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly, parsedTimeOnly1); + + parsedTimeOnly = TimeOnly.ParseExact(formattedTime, patterns); + Assert.True(TimeOnly.TryParseExact(formattedTime, patterns, out parsedTimeOnly1)); + Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly, parsedTimeOnly1); + parsedTimeOnly = TimeOnly.ParseExact(formattedTime.AsSpan(), patterns); + Assert.True(TimeOnly.TryParseExact(formattedTime.AsSpan(), patterns, out parsedTimeOnly1)); + Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly, parsedTimeOnly1); + + parsedTimeOnly = TimeOnly.ParseExact(formattedTime, patterns, CultureInfo.CurrentCulture); + Assert.True(TimeOnly.TryParseExact(formattedTime, patterns, CultureInfo.CurrentCulture, DateTimeStyles.None, out parsedTimeOnly1)); + Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly, parsedTimeOnly1); + parsedTimeOnly = TimeOnly.ParseExact(formattedTime.AsSpan(), patterns, CultureInfo.CurrentCulture); + Assert.True(TimeOnly.TryParseExact(formattedTime.AsSpan(), patterns, CultureInfo.CurrentCulture, DateTimeStyles.None, out parsedTimeOnly1)); + Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly, parsedTimeOnly1); + } + } + + [Fact] + public static void OAndRFormatsTest() + { + TimeOnly timeOnly = TimeOnly.FromDateTime(DateTime.Today); + string formattedDate = timeOnly.ToString("o"); + Assert.Equal(16, formattedDate.Length); + Assert.Equal(':', formattedDate[2]); + Assert.Equal(':', formattedDate[5]); + Assert.Equal('.', formattedDate[8]); + TimeOnly parsedTimeOnly = TimeOnly.Parse(formattedDate); + Assert.Equal(timeOnly, parsedTimeOnly); + parsedTimeOnly = TimeOnly.Parse(formattedDate.AsSpan()); + Assert.Equal(timeOnly, parsedTimeOnly); + parsedTimeOnly = TimeOnly.ParseExact(formattedDate, "O"); + Assert.Equal(timeOnly, parsedTimeOnly); + parsedTimeOnly = TimeOnly.ParseExact(formattedDate.AsSpan(), "O".AsSpan()); + Assert.Equal(timeOnly, parsedTimeOnly); + + formattedDate = timeOnly.ToString("r"); + Assert.Equal(8, formattedDate.Length); + Assert.Equal(':', formattedDate[2]); + Assert.Equal(':', formattedDate[5]); + parsedTimeOnly = TimeOnly.Parse(formattedDate); + Assert.Equal(timeOnly, parsedTimeOnly); + parsedTimeOnly = TimeOnly.Parse(formattedDate.AsSpan()); + Assert.Equal(timeOnly, parsedTimeOnly); + parsedTimeOnly = TimeOnly.ParseExact(formattedDate, "R"); + Assert.Equal(timeOnly, parsedTimeOnly); + parsedTimeOnly = TimeOnly.ParseExact(formattedDate.AsSpan(), "R".AsSpan()); + Assert.Equal(timeOnly, parsedTimeOnly); + } + + [Fact] + public static void InvalidFormatsTest() + { + DateTime dt = DateTime.Now; + string formatted = dt.ToString(); + Assert.Throws(() => TimeOnly.Parse(formatted)); + Assert.Throws(() => TimeOnly.Parse(formatted.AsSpan())); + Assert.False(TimeOnly.TryParse(formatted, out TimeOnly timeOnly)); + Assert.False(TimeOnly.TryParse(formatted.AsSpan(), out timeOnly)); + formatted = dt.ToString("d"); + Assert.Throws(() => TimeOnly.Parse(formatted)); + Assert.Throws(() => TimeOnly.Parse(formatted.AsSpan())); + Assert.False(TimeOnly.TryParse(formatted, out timeOnly)); + Assert.False(TimeOnly.TryParse(formatted.AsSpan(), out timeOnly)); + } + + [Fact] + public static void CustomFormattingTest() + { + TimeOnly timeOnly = TimeOnly.FromDateTime(DateTime.Now); + string format = "hh 'dash' mm \"dash\" ss'....'fffffff tt"; + string formatted = timeOnly.ToString(format); + TimeOnly parsedTimeOnly = TimeOnly.ParseExact(formatted, format); + Assert.Equal(timeOnly, parsedTimeOnly); + parsedTimeOnly = TimeOnly.ParseExact(formatted.AsSpan(), format.AsSpan()); + Assert.Equal(timeOnly, parsedTimeOnly); + + Assert.Throws(() => timeOnly.ToString("hh:mm:ss dd")); + Assert.Throws(() => timeOnly.ToString("hh:mm:ss MM")); + Assert.Throws(() => timeOnly.ToString("hh:mm:ss yy")); + } + + [Fact] + public static void AllCulturesTest() + { + TimeOnly timeOnly = new TimeOnly((DateTime.Now.TimeOfDay.Ticks / TimeSpan.TicksPerMinute) * TimeSpan.TicksPerMinute); + foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.SpecificCultures)) + { + string formatted = timeOnly.ToString("t", ci); + TimeOnly parsedTimeOnly = TimeOnly.ParseExact(formatted, "t", ci); + Assert.Equal(timeOnly, parsedTimeOnly); + + formatted = timeOnly.ToString("T", ci); + parsedTimeOnly = TimeOnly.ParseExact(formatted, "T", ci); + Assert.Equal(timeOnly, parsedTimeOnly); + } + } + + [Fact] + public static void TryFormatTest() + { + Span buffer = stackalloc char[100]; + TimeOnly timeOnly = TimeOnly.FromDateTime(DateTime.Now); + + Assert.True(timeOnly.TryFormat(buffer, out int charsWritten)); + Assert.True(timeOnly.TryFormat(buffer, out charsWritten, "o")); + Assert.Equal(16, charsWritten); + Assert.True(timeOnly.TryFormat(buffer, out charsWritten, "R")); + Assert.Equal(8, charsWritten); + Assert.False(timeOnly.TryFormat(buffer.Slice(0, 3), out charsWritten)); + Assert.False(timeOnly.TryFormat(buffer.Slice(0, 3), out charsWritten, "r")); + Assert.False(timeOnly.TryFormat(buffer.Slice(0, 3), out charsWritten, "O")); + } + + } +} From b06a8cade20f19c5d3aff361e95c3b913886ab26 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Thu, 8 Apr 2021 18:58:26 -0700 Subject: [PATCH 02/12] Feedback 1 --- .../src/Resources/Strings.resx | 5 ++++ .../src/System/DateOnly.cs | 24 +++++++++---------- .../src/System/TimeOnly.cs | 22 ++++++++--------- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 9ed7364350bc7a..5fbc493fd21001 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -480,9 +480,11 @@ Object must be of type DateOnly. + {Locked="DateOnly"} Object must be of type TimeOnly. + {Locked="TimeOnly"} Object must be of type DateTime. @@ -2190,15 +2192,18 @@ String '{0}' was not recognized as a valid DateOnly. + {Locked="DateOnly"} String '{0}' was not recognized as a valid TimeOnly. + {Locked="TimeOnly"} String '{0}' contains parts which are not specific to the Date. String '{0}' contains parts which are not specific to the TimeOnly. + {Locked="TimeOnly"} The DateTime represented by the string '{0}' is not supported in calendar '{1}'. diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs index b352a42c79cbd5..eec2b6674bff8a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -9,7 +9,7 @@ namespace System /// /// 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. /// - public readonly struct DateOnly : IComparable, IComparable, IEquatable, IFormattable + public readonly struct DateOnly : IComparable, IComparable, IEquatable, IFormattable, ISpanFormattable { private readonly int _dayNumber; @@ -32,12 +32,12 @@ private DateOnly(int dayNumber) /// /// Return the instance of the DateOnly structure representing the minimal possible date can be created. /// - public static DateOnly MinValue { get; } = new DateOnly(MinDayNumber); + public static DateOnly MinValue => new DateOnly(MinDayNumber); /// /// Return the instance of the DateOnly structure representing the maximal possible date can be created. /// - public static DateOnly MaxValue { get; } = new DateOnly(MaxDayNumber); + public static DateOnly MaxValue => new DateOnly(MaxDayNumber); /// /// Initializes a new instance of the DateOnly structure to the specified year, month, and day. @@ -173,7 +173,7 @@ public DateOnly AddDays(int value) public static bool operator <(DateOnly left, DateOnly right) => left._dayNumber < right._dayNumber; /// - /// Determines whether one specified DateOnly represents a datethat is the same as or earlier than another specified DateOnly. + /// Determines whether one specified DateOnly represents a date that is the same as or earlier than another specified DateOnly. /// /// The first object to compare. /// The second object to compare. @@ -222,12 +222,12 @@ public int CompareTo(DateOnly value) public int CompareTo(object? value) { if (value == null) return 1; - if (!(value is DateOnly)) + if (value is not DateOnly dateOnly) { throw new ArgumentException(SR.Arg_MustBeDateOnly); } - return CompareTo((DateOnly)value); + return CompareTo(dateOnly); } /// @@ -289,12 +289,12 @@ public static DateOnly Parse(ReadOnlySpan s, IFormatProvider? provider, Da if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref result)) { - throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_BadDateOnly)!, s.ToString())); + throw new FormatException(SR.Format(SR.Format_BadDateOnly, s.ToString())); } if ((result.flags & ParseFlagsDateMask) != 0) { - throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_DateOnlyContainsNoneDateParts)!, s.ToString())); + throw new FormatException(SR.Format(SR.Format_DateOnlyContainsNoneDateParts, s.ToString())); } return new DateOnly(DayNumberFromDateTime(result.parsedDate)); @@ -345,12 +345,12 @@ public static DateOnly ParseExact(ReadOnlySpan s, ReadOnlySpan forma if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref result)) { - throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_BadDateOnly)!, s.ToString())); + throw new FormatException(SR.Format(SR.Format_BadDateOnly, s.ToString())); } if ((result.flags & ParseFlagsDateMask) != 0) { - throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_DateOnlyContainsNoneDateParts)!, s.ToString())); + throw new FormatException(SR.Format(SR.Format_DateOnlyContainsNoneDateParts, s.ToString())); } return new DateOnly(DayNumberFromDateTime(result.parsedDate)); @@ -390,7 +390,7 @@ public static DateOnly ParseExact(ReadOnlySpan s, string [] formats, IForm string? format = formats[i]; if (string.IsNullOrEmpty(format)) { - throw new FormatException(SR.GetResourceString(SR.Argument_BadFormatSpecifier)); + throw new FormatException(SR.Argument_BadFormatSpecifier); } if (format.Length == 1) @@ -415,7 +415,7 @@ public static DateOnly ParseExact(ReadOnlySpan s, string [] formats, IForm } } - throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_BadDateOnly)!, s.ToString())); + throw new FormatException(SR.Format(SR.Format_BadDateOnly, s.ToString())); } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index eca0cc29358e67..7ee0bb10b8ded6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -9,7 +9,7 @@ namespace System /// /// Represents a time of day, as would be read from a clock, within the range 00:00:00 to 23:59:59.9999999 /// - public readonly struct TimeOnly : IComparable, IComparable, IEquatable, IFormattable + public readonly struct TimeOnly : IComparable, IComparable, IEquatable, IFormattable, ISpanFormattable { // represent the number of ticks map to the time of the day. 1 ticks = 100-nanosecond in time measurements. private readonly long _ticks; @@ -23,12 +23,12 @@ namespace System /// /// Represents the smallest possible value of TimeOnly. /// - public static TimeOnly MinValue { get; } = new TimeOnly((ulong) MinTimeTicks); + public static TimeOnly MinValue => new TimeOnly((ulong) MinTimeTicks); /// /// Represents the largest possible value of TimeOnly. /// - public static TimeOnly MaxValue { get; } = new TimeOnly((ulong) MaxTimeTicks); + public static TimeOnly MaxValue => new TimeOnly((ulong) MaxTimeTicks); /// /// Initializes a new instance of the timeOnly structure to the specified hour and the minute. @@ -292,12 +292,12 @@ public int CompareTo(TimeOnly value) public int CompareTo(object? value) { if (value == null) return 1; - if (!(value is TimeOnly)) + if (value is not TimeOnly timeOnly) { throw new ArgumentException(SR.Arg_MustBeTimeOnly); } - return CompareTo((TimeOnly)value); + return CompareTo(timeOnly); } /// @@ -360,12 +360,12 @@ public static TimeOnly Parse(ReadOnlySpan s, IFormatProvider? provider, Da if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref result)) { - throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_BadTimeOnly)!, s.ToString())); + throw new FormatException(SR.Format(SR.Format_BadTimeOnly, s.ToString())); } if ((result.flags & ParseFlagsTimeMask) != 0) { - throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_TimeOnlyContainsNoneTimeParts)!, s.ToString())); + throw new FormatException(SR.Format(SR.Format_TimeOnlyContainsNoneTimeParts, s.ToString())); } return new TimeOnly(result.parsedDate.TimeOfDay.Ticks); @@ -416,12 +416,12 @@ public static TimeOnly ParseExact(ReadOnlySpan s, ReadOnlySpan forma if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref result)) { - throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_BadTimeOnly)!, s.ToString())); + throw new FormatException(SR.Format(SR.Format_BadTimeOnly, s.ToString())); } if ((result.flags & ParseFlagsTimeMask) != 0) { - throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_TimeOnlyContainsNoneTimeParts)!, s.ToString())); + throw new FormatException(SR.Format(SR.Format_TimeOnlyContainsNoneTimeParts, s.ToString())); } return new TimeOnly(result.parsedDate.TimeOfDay.Ticks); @@ -461,7 +461,7 @@ public static TimeOnly ParseExact(ReadOnlySpan s, string [] formats, IForm string? format = formats[i]; if (string.IsNullOrEmpty(format)) { - throw new FormatException(SR.GetResourceString(SR.Argument_BadFormatSpecifier)); + throw new FormatException(SR.Argument_BadFormatSpecifier); } if (format.Length == 1) @@ -486,7 +486,7 @@ public static TimeOnly ParseExact(ReadOnlySpan s, string [] formats, IForm } } - throw new FormatException(SR.Format(SR.GetResourceString(SR.Format_BadTimeOnly)!, s.ToString())); + throw new FormatException(SR.Format(SR.Format_BadTimeOnly, s.ToString())); } /// From 40592c6dc5546e92f887e1d78de26aa055808de1 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 9 Apr 2021 20:31:15 -0700 Subject: [PATCH 03/12] Address Feedback 2 Added a temorary logging info to the exception for CI failure diagnostics --- .../src/Resources/Strings.resx | 5 +- .../src/System/DateOnly.cs | 66 +++++++++---------- .../src/System/DateTime.cs | 13 ++++ .../System/Globalization/DateTimeFormat.cs | 18 ++--- .../src/System/TimeOnly.cs | 12 ++-- .../tests/System/TimeOnlyTests.cs | 13 +++- 6 files changed, 73 insertions(+), 54 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 5fbc493fd21001..24e588863fb486 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -1133,6 +1133,7 @@ The only allowed values for the styles are AllowWhiteSpaces, AllowTrailingWhite, AllowLeadingWhite, and AllowInnerWhite. + {Locked="AllowWhiteSpaces, AllowTrailingWhite, AllowLeadingWhite, and AllowInnerWhite"} The DigitSubstitution property must be of a valid member of the DigitShapes enumeration. Valid entries include Context, NativeNational or None. @@ -1663,7 +1664,7 @@ Ticks must be between DateTime.MinValue.Ticks and DateTime.MaxValue.Ticks. - Ticks must be between 0 and 863999999999. + Ticks must be between 0 and 863_999_999_999. Years value must be between +/-10000. @@ -1762,7 +1763,7 @@ Month must be between one and twelve. - Day bumber must be between 0 and 3652058. + Day number must be between 0 and 36_520_58. The Month parameter must be in the range 1 through 12. diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs index eec2b6674bff8a..59e4257ea372c9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -7,7 +7,7 @@ namespace System { /// - /// 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. + /// 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. /// public readonly struct DateOnly : IComparable, IComparable, IEquatable, IFormattable, ISpanFormattable { @@ -17,7 +17,7 @@ namespace System private const int MinDayNumber = 0; // Maps to December 31 year 9999. The value calculated from "new DateTime(9999, 12, 31).Ticks / TimeSpan.TicksPerDay" - private const int MaxDayNumber = 3652058; + private const int MaxDayNumber = 3_652_058; private static int DayNumberFromDateTime(DateTime dt) => (int)(dt.Ticks / TimeSpan.TicksPerDay); @@ -25,41 +25,41 @@ namespace System private DateOnly(int dayNumber) { - Debug.Assert((uint)dayNumber <= 3652058); + Debug.Assert((uint)dayNumber <= MaxDayNumber); _dayNumber = dayNumber; } /// - /// Return the instance of the DateOnly structure representing the minimal possible date can be created. + /// Gets the earliest possible date that can be created. /// public static DateOnly MinValue => new DateOnly(MinDayNumber); /// - /// Return the instance of the DateOnly structure representing the maximal possible date can be created. + /// Gets the latest possible date that can be created. /// public static DateOnly MaxValue => new DateOnly(MaxDayNumber); /// - /// Initializes a new instance of the DateOnly structure to the specified year, month, and day. + /// Creates a new instance of the DateOnly structure to the specified year, month, and day. /// /// The year (1 through 9999). /// The month (1 through 12). - /// The day (1 through the number of days in month). + /// The day (1 through the number of days in ). public DateOnly(int year, int month, int day) => _dayNumber = DayNumberFromDateTime(new DateTime(year, month, day)); /// - /// Initializes a new instance of the DateOnly structure to the specified year, month, and day for the specified calendar. + /// Creates a new instance of the DateOnly structure to the specified year, month, and day for the specified calendar. /// /// The year (1 through the number of years in calendar). /// The month (1 through the number of months in calendar). - /// The day (1 through the number of days in month).. + /// The day (1 through the number of days in ). /// The calendar that is used to interpret year, month, and day.. public DateOnly(int year, int month, int day, Calendar calendar) => _dayNumber = DayNumberFromDateTime(new DateTime(year, month, day, calendar)); /// - /// Initializes a new instance of the DateOnly structure to the specified number of days. + /// Creates a new instance of the DateOnly structure to the specified number of days. /// - /// The number of days since January 1, 0001 in the Gregorian calendar. + /// The number of days since January 1, 0001 in the Proleptic Gregorian calendar. public static DateOnly FromDayNumber(int dayNumber) { if ((uint)dayNumber > MaxDayNumber) @@ -96,15 +96,15 @@ public static DateOnly FromDayNumber(int dayNumber) public int DayOfYear => GetEquivalentDateTime().DayOfYear; /// - /// Gets the number if days since January 1, 0001 in the Gregorian calendar represented by this instance. + /// Gets the number of days since January 1, 0001 in the Proleptic Gregorian calendar represented by this instance. /// public int DayNumber => _dayNumber; /// - /// Returns a new DateOnly that adds the specified number of days to the value of this instance. + /// Adds the specified number of days to the value of this instance. /// - /// A number of days. The value parameter can be negative or positive. - /// An object whose value is the sum of the date represented by this instance and the number of days represented by value. + /// The number of days to add. To subtract days, specify a negative number. + /// An instance whose value is the sum of the date represented by this instance and the number of days represented by value. public DateOnly AddDays(int value) { int newDayNumber = _dayNumber + value; @@ -119,14 +119,14 @@ public DateOnly AddDays(int value) } /// - /// Returns a new DateOnly that adds the specified number of months to the value of this instance. + /// Adds the specified number of months to the value of this instance. /// /// A number of months. The months parameter can be negative or positive. /// An object whose value is the sum of the date represented by this instance and months. public DateOnly AddMonths(int value) => new DateOnly(DayNumberFromDateTime(GetEquivalentDateTime().AddMonths(value))); /// - /// Returns a new DateOnly that adds the specified number of years to the value of this instance. + /// Adds the specified number of years to the value of this instance. /// /// A number of years. The value parameter can be negative or positive. /// An object whose value is the sum of the date represented by this instance and the number of years represented by value. @@ -184,7 +184,7 @@ public DateOnly AddDays(int value) /// Returns a DateTime that is set to the date of this DateOnly instance and the time of specified input time. /// /// The time of the day. - /// The DateTime instance composed of the date of the current DateOnly instance and teh time specified by the input time. + /// The DateTime instance composed of the date of the current DateOnly instance and the time specified by the input time. public DateTime ToDateTime(TimeOnly time) => new DateTime(_dayNumber * TimeSpan.TicksPerDay + time.Ticks); /// @@ -192,7 +192,7 @@ public DateOnly AddDays(int value) /// /// The time of the day. /// One of the enumeration values that indicates whether ticks specifies a local time, Coordinated Universal Time (UTC), or neither. - /// The DateTime instance composed of the date of the current DateOnly instance and teh time specified by the input time. + /// The DateTime instance composed of the date of the current DateOnly instance and the time specified by the input time. public DateTime ToDateTime(TimeOnly time, DateTimeKind kind) => new DateTime(_dayNumber * TimeSpan.TicksPerDay + time.Ticks, kind); /// @@ -242,14 +242,7 @@ public int CompareTo(object? value) /// /// The object to compare to this instance. /// true if value is an instance of DateOnly and equals the value of this instance; otherwise, false. - public override bool Equals(object? value) - { - if (value is DateOnly) - { - return _dayNumber == ((DateOnly)value)._dayNumber; - } - return false; - } + public override bool Equals(object? value) => value is DateOnly dateOnly && _dayNumber == dateOnly._dayNumber; /// /// Returns the hash code for this instance. @@ -257,8 +250,6 @@ public override bool Equals(object? value) /// A 32-bit signed integer hash code. public override int GetHashCode() => _dayNumber; - // Only Allowed DateTimeStyles: AllowWhiteSpaces, AllowTrailingWhite, AllowLeadingWhite, and AllowInnerWhite - /// /// Converts a memory span that contains string representation of a date to its DateOnly equivalent by using the conventions of the current culture. /// @@ -284,7 +275,7 @@ public static DateOnly Parse(ReadOnlySpan s, IFormatProvider? provider, Da throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); } - DateTimeResult result = default; // The buffer to store the parsing result. + DateTimeResult result = default; result.Init(s); if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref result)) @@ -294,7 +285,8 @@ public static DateOnly Parse(ReadOnlySpan s, IFormatProvider? provider, Da if ((result.flags & ParseFlagsDateMask) != 0) { - throw new FormatException(SR.Format(SR.Format_DateOnlyContainsNoneDateParts, s.ToString())); + // Adding temporary logging the flags to the exception to get more info from the failure case in CI. This code should be removed before merging the change: + throw new FormatException(SR.Format(SR.Format_DateOnlyContainsNoneDateParts, s.ToString()) + " - " + result.flags); } return new DateOnly(DayNumberFromDateTime(result.parsedDate)); @@ -350,7 +342,8 @@ public static DateOnly ParseExact(ReadOnlySpan s, ReadOnlySpan forma if ((result.flags & ParseFlagsDateMask) != 0) { - throw new FormatException(SR.Format(SR.Format_DateOnlyContainsNoneDateParts, s.ToString())); + // Adding temporary logging the flags to the exception to get more info from the failure case in CI. This code should be removed before merging the change: + throw new FormatException(SR.Format(SR.Format_DateOnlyContainsNoneDateParts, s.ToString()) + " - " + result.flags); } return new DateOnly(DayNumberFromDateTime(result.parsedDate)); @@ -407,7 +400,7 @@ public static DateOnly ParseExact(ReadOnlySpan s, string [] formats, IForm // Create a new result each time to ensure the runs are independent. Carry through // flags from the caller and return the result. - DateTimeResult result = default; // The buffer to store the parsing result. + DateTimeResult result = default; result.Init(s); if (DateTimeParse.TryParseExact(s, format, dtfi, style, ref result) && ((result.flags & ParseFlagsDateMask) == 0)) { @@ -415,6 +408,7 @@ public static DateOnly ParseExact(ReadOnlySpan s, string [] formats, IForm } } + // Adding temporary logging the flags to the exception to get more info from the failure case in CI. This code should be removed before merging the change: throw new FormatException(SR.Format(SR.Format_BadDateOnly, s.ToString())); } @@ -832,7 +826,7 @@ public string ToString(string? format, IFormatProvider? provider) } } - DateTimeFormat.IsValidCustomDateFormat(format.AsSpan(), allowThrow: true); + DateTimeFormat.IsValidCustomDateFormat(format.AsSpan(), throwOnError: true); return DateTimeFormat.Format(GetEquivalentDateTime(), format, provider); } @@ -890,7 +884,7 @@ public string ToString(string? format, IFormatProvider? provider) } } - if (!DateTimeFormat.IsValidCustomDateFormat(format, allowThrow: false)) + if (!DateTimeFormat.IsValidCustomDateFormat(format, throwOnError: false)) { charsWritten = 0; return false; @@ -899,4 +893,4 @@ public string ToString(string? format, IFormatProvider? provider) return DateTimeFormat.TryFormat(GetEquivalentDateTime(), destination, out charsWritten, format, provider); } } -} \ No newline at end of file +} diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs index 3afc0e8d7b23ca..a2b15123297059 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs @@ -611,6 +611,19 @@ private static ulong TimeToTicks(int hour, int minute, int second) return (uint)totalSeconds * (ulong)TicksPerSecond; } + internal static ulong TimeToTicks(int hour, int minute, int second, int millisecond) + { + ulong ticks = TimeToTicks(hour, minute, second); + + if ((uint)millisecond >= MillisPerSecond) ThrowMillisecondOutOfRange(); + + ticks += (uint)millisecond * (uint)TicksPerMillisecond; + + Debug.Assert(ticks <= MaxTicks, "Input parameters validated already"); + + return ticks; + } + // Returns the number of days in the month given by the year and // month arguments. // diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs index 1806d466688b97..5dd09cb0b50fc9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs @@ -1112,7 +1112,7 @@ private static StringBuilder FormatStringBuilder(DateTime dateTime, ReadOnlySpan return FormatCustomized(dateTime, format, dtfi, offset, result: null); } - internal static bool IsValidCustomDateFormat(ReadOnlySpan format, bool allowThrow) + internal static bool IsValidCustomDateFormat(ReadOnlySpan format, bool throwOnError) { int length = format.Length; int i = 0; @@ -1124,13 +1124,14 @@ internal static bool IsValidCustomDateFormat(ReadOnlySpan format, bool all case '\\': if (i == length - 1) { - if (allowThrow) + if (throwOnError) { throw new FormatException(SR.Format_InvalidString); } return false; } + i += 2; break; @@ -1144,7 +1145,7 @@ internal static bool IsValidCustomDateFormat(ReadOnlySpan format, bool all if (i >= length) { - if (allowThrow) + if (throwOnError) { throw new FormatException(SR.Format(SR.Format_BadQuote, quoteChar)); } @@ -1166,7 +1167,7 @@ internal static bool IsValidCustomDateFormat(ReadOnlySpan format, bool all case 'z': case 'K': // reject non-date formats - if (allowThrow) + if (throwOnError) { throw new FormatException(SR.Format_InvalidString); } @@ -1183,7 +1184,7 @@ internal static bool IsValidCustomDateFormat(ReadOnlySpan format, bool all } - internal static bool IsValidCustomTimeFormat(ReadOnlySpan format, bool allowThrow) + internal static bool IsValidCustomTimeFormat(ReadOnlySpan format, bool throwOnError) { int length = format.Length; int i = 0; @@ -1195,13 +1196,14 @@ internal static bool IsValidCustomTimeFormat(ReadOnlySpan format, bool all case '\\': if (i == length - 1) { - if (allowThrow) + if (throwOnError) { throw new FormatException(SR.Format_InvalidString); } return false; } + i += 2; break; @@ -1215,7 +1217,7 @@ internal static bool IsValidCustomTimeFormat(ReadOnlySpan format, bool all if (i >= length) { - if (allowThrow) + if (throwOnError) { throw new FormatException(SR.Format(SR.Format_BadQuote, quoteChar)); } @@ -1232,7 +1234,7 @@ internal static bool IsValidCustomTimeFormat(ReadOnlySpan format, bool all case '/': case 'z': case 'k': - if (allowThrow) + if (throwOnError) { throw new FormatException(SR.Format_InvalidString); } diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index 7ee0bb10b8ded6..4a3a6f628e13eb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -43,7 +43,7 @@ public TimeOnly(int hour, int minute) : this((ulong)new DateTime(1, 1, 1, hour, /// The hours (0 through 23). /// The minutes (0 through 59). /// The seconds (0 through 59). - public TimeOnly(int hour, int minute, int second) : this((ulong)new DateTime(1, 1, 1, hour, minute, second).TimeOfDay.Ticks) {} + public TimeOnly(int hour, int minute, int second) : this(DateTime.TimeToTicks(hour, minute, second, 0)) {} /// /// Initializes a new instance of the timeOnly structure to the specified hour, minute, second, and millisecond. @@ -52,12 +52,12 @@ public TimeOnly(int hour, int minute, int second) : this((ulong)new DateTime(1, /// The minutes (0 through 59). /// The seconds (0 through 59). /// The millisecond (0 through 999). - public TimeOnly(int hour, int minute, int second, int millisecond) : this((ulong)new DateTime(1, 1, 1, hour, minute, second, millisecond).TimeOfDay.Ticks) {} + public TimeOnly(int hour, int minute, int second, int millisecond) : this(DateTime.TimeToTicks(hour, minute, second, millisecond)) {} /// - /// Initializes a new instance of the timeOnly structure to a specified number of ticks. + /// Initializes a new instance of the TimeOnly structure using a specified number of ticks. /// - /// A time expressed in the number of 100-nanosecond intervals that have elapsed since midnight 00:00:00.000 AM. + /// A time of day expressed in the number of 100-nanosecond units since 00:00:00.0000000. public TimeOnly(long ticks) { if ((ulong)ticks > MaxTimeTicks) @@ -896,7 +896,7 @@ public string ToString(string? format, IFormatProvider? provider) } } - DateTimeFormat.IsValidCustomTimeFormat(format.AsSpan(), allowThrow: true); + DateTimeFormat.IsValidCustomTimeFormat(format.AsSpan(), throwOnError: true); return DateTimeFormat.Format(ToDateTime(), format, provider); } @@ -949,7 +949,7 @@ public string ToString(string? format, IFormatProvider? provider) } } - if (!DateTimeFormat.IsValidCustomTimeFormat(format, allowThrow: false)) + if (!DateTimeFormat.IsValidCustomTimeFormat(format, throwOnError: false)) { charsWritten = 0; return false; diff --git a/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs index b067ca9b2803b5..38bbd04ed62f47 100644 --- a/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs @@ -418,7 +418,7 @@ public static void InvalidFormatsTest() public static void CustomFormattingTest() { TimeOnly timeOnly = TimeOnly.FromDateTime(DateTime.Now); - string format = "hh 'dash' mm \"dash\" ss'....'fffffff tt"; + string format = "HH 'dash' mm \"dash\" ss'....'fffffff"; string formatted = timeOnly.ToString(format); TimeOnly parsedTimeOnly = TimeOnly.ParseExact(formatted, format); Assert.Equal(timeOnly, parsedTimeOnly); @@ -436,13 +436,22 @@ public static void AllCulturesTest() TimeOnly timeOnly = new TimeOnly((DateTime.Now.TimeOfDay.Ticks / TimeSpan.TicksPerMinute) * TimeSpan.TicksPerMinute); foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.SpecificCultures)) { + if (string.IsNullOrEmpty(ci.DateTimeFormat.TimeSeparator)) + { + // cannot parse concatenated time part numbers. + continue; + } + string formatted = timeOnly.ToString("t", ci); TimeOnly parsedTimeOnly = TimeOnly.ParseExact(formatted, "t", ci); Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly.Hour % 12, parsedTimeOnly.Hour % 12); + Assert.Equal(timeOnly.Minute, parsedTimeOnly.Minute); formatted = timeOnly.ToString("T", ci); parsedTimeOnly = TimeOnly.ParseExact(formatted, "T", ci); - Assert.Equal(timeOnly, parsedTimeOnly); + Assert.Equal(timeOnly.Hour % 12, parsedTimeOnly.Hour % 12); + Assert.Equal(timeOnly.Minute, parsedTimeOnly.Minute); } } From 8066b6d13557e51341fd37fcfcdb3c577fad3c1c Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sat, 10 Apr 2021 10:57:11 -0700 Subject: [PATCH 04/12] Addressing Feedback 3 --- .../src/Resources/Strings.resx | 3 ++- .../src/System/DateOnly.cs | 17 +++++++++++------ .../src/System/DateTime.cs | 2 ++ .../src/System/TimeOnly.cs | 14 +++++++++++--- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 24e588863fb486..2f7e1eaaf6e8b9 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -2200,7 +2200,8 @@ {Locked="TimeOnly"} - String '{0}' contains parts which are not specific to the Date. + String '{0}' contains parts which are not specific to the DateOnly. + {Locked="DateOnly"} String '{0}' contains parts which are not specific to the TimeOnly. diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs index 59e4257ea372c9..e7ced0d6cc357a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -21,7 +21,7 @@ namespace System private static int DayNumberFromDateTime(DateTime dt) => (int)(dt.Ticks / TimeSpan.TicksPerDay); - private DateTime GetEquivalentDateTime() => new DateTime(_dayNumber * TimeSpan.TicksPerDay); + private DateTime GetEquivalentDateTime() => DateTime.UnsafeCreate(_dayNumber * TimeSpan.TicksPerDay); private DateOnly(int dayNumber) { @@ -285,8 +285,7 @@ public static DateOnly Parse(ReadOnlySpan s, IFormatProvider? provider, Da if ((result.flags & ParseFlagsDateMask) != 0) { - // Adding temporary logging the flags to the exception to get more info from the failure case in CI. This code should be removed before merging the change: - throw new FormatException(SR.Format(SR.Format_DateOnlyContainsNoneDateParts, s.ToString()) + " - " + result.flags); + throw new FormatException(SR.Format(SR.Format_DateOnlyContainsNoneDateParts, s.ToString())); } return new DateOnly(DayNumberFromDateTime(result.parsedDate)); @@ -325,10 +324,12 @@ public static DateOnly ParseExact(ReadOnlySpan s, ReadOnlySpan forma if (format[0] == 'o' || format[0] == 'O') { format = OFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } else if (format[0] == 'r' || format[0] == 'R') { format = RFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } } @@ -342,8 +343,7 @@ public static DateOnly ParseExact(ReadOnlySpan s, ReadOnlySpan forma if ((result.flags & ParseFlagsDateMask) != 0) { - // Adding temporary logging the flags to the exception to get more info from the failure case in CI. This code should be removed before merging the change: - throw new FormatException(SR.Format(SR.Format_DateOnlyContainsNoneDateParts, s.ToString()) + " - " + result.flags); + throw new FormatException(SR.Format(SR.Format_DateOnlyContainsNoneDateParts, s.ToString())); } return new DateOnly(DayNumberFromDateTime(result.parsedDate)); @@ -391,10 +391,12 @@ public static DateOnly ParseExact(ReadOnlySpan s, string [] formats, IForm if (format[0] == 'o' || format[0] == 'O') { format = OFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } else if (format[0] == 'r' || format[0] == 'R') { format = RFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } } @@ -408,7 +410,6 @@ public static DateOnly ParseExact(ReadOnlySpan s, string [] formats, IForm } } - // Adding temporary logging the flags to the exception to get more info from the failure case in CI. This code should be removed before merging the change: throw new FormatException(SR.Format(SR.Format_BadDateOnly, s.ToString())); } @@ -556,10 +557,12 @@ public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format if (format[0] == 'o' || format[0] == 'O') { format = OFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } else if (format[0] == 'r' || format[0] == 'R') { format = RFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } } @@ -621,10 +624,12 @@ public static bool TryParseExact(ReadOnlySpan s, string [] formats, IForma if (format[0] == 'o' || format[0] == 'O') { format = OFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } else if (format[0] == 'r' || format[0] == 'R') { format = RFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs index a2b15123297059..aee82eb98c608f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs @@ -145,6 +145,8 @@ private DateTime(ulong dateData) this._dateData = dateData; } + internal static DateTime UnsafeCreate(long ticks) => new DateTime((ulong) ticks); + public DateTime(long ticks, DateTimeKind kind) { if ((ulong)ticks > MaxTicks) ThrowTicksOutOfRange(); diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index 4a3a6f628e13eb..f66f75b71454d1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -35,7 +35,7 @@ namespace System /// /// The hours (0 through 23). /// The minutes (0 through 59). - public TimeOnly(int hour, int minute) : this((ulong)new DateTime(1, 1, 1, hour, minute, 0).TimeOfDay.Ticks) {} + public TimeOnly(int hour, int minute) : this(DateTime.TimeToTicks(hour, minute, 0, 0)) {} /// /// Initializes a new instance of the timeOnly structure to the specified hour, minute, and second. @@ -404,10 +404,12 @@ public static TimeOnly ParseExact(ReadOnlySpan s, ReadOnlySpan forma if (format[0] == 'o' || format[0] == 'O') { format = OFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } else if (format[0] == 'r' || format[0] == 'R') { format = RFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } } @@ -468,11 +470,13 @@ public static TimeOnly ParseExact(ReadOnlySpan s, string [] formats, IForm { if (format[0] == 'o' || format[0] == 'O') { - format = "HH':'mm':'ss'.'fffffff"; + format = OFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } else if (format[0] == 'r' || format[0] == 'R') { - format = "HH':'mm':'ss"; + format = RFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } } @@ -635,10 +639,12 @@ public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format if (format[0] == 'o' || format[0] == 'O') { format = OFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } else if (format[0] == 'r' || format[0] == 'R') { format = RFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } } @@ -701,10 +707,12 @@ public static bool TryParseExact(ReadOnlySpan s, string [] formats, IForma if (format[0] == 'o' || format[0] == 'O') { format = OFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } else if (format[0] == 'r' || format[0] == 'R') { format = RFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; } } From f4204ca5315c84305bb59bd3d3c1ff33d9e7fa5c Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sat, 10 Apr 2021 12:07:25 -0700 Subject: [PATCH 05/12] Fix the failed test --- src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs index 38bbd04ed62f47..60327884eb832c 100644 --- a/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs @@ -444,7 +444,6 @@ public static void AllCulturesTest() string formatted = timeOnly.ToString("t", ci); TimeOnly parsedTimeOnly = TimeOnly.ParseExact(formatted, "t", ci); - Assert.Equal(timeOnly, parsedTimeOnly); Assert.Equal(timeOnly.Hour % 12, parsedTimeOnly.Hour % 12); Assert.Equal(timeOnly.Minute, parsedTimeOnly.Minute); From b440eb30ac6a07a618f461e30a0640f6d1bca38e Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sat, 10 Apr 2021 15:28:14 -0700 Subject: [PATCH 06/12] More Test fixes --- .../src/System/DateOnly.cs | 23 +++++++++------- .../src/System/TimeOnly.cs | 26 +++++++++++-------- .../tests/System/TimeOnlyTests.cs | 12 ++++++--- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs index e7ced0d6cc357a..e77572c2db4f7b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -380,6 +380,8 @@ public static DateOnly ParseExact(ReadOnlySpan s, string [] formats, IForm for (int i = 0; i < formats.Length; i++) { + DateTimeFormatInfo dtfiToUse = dtfi; + string? format = formats[i]; if (string.IsNullOrEmpty(format)) { @@ -391,12 +393,12 @@ public static DateOnly ParseExact(ReadOnlySpan s, string [] formats, IForm if (format[0] == 'o' || format[0] == 'O') { format = OFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; + dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; } else if (format[0] == 'r' || format[0] == 'R') { format = RFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; + dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; } } @@ -404,7 +406,7 @@ public static DateOnly ParseExact(ReadOnlySpan s, string [] formats, IForm // flags from the caller and return the result. DateTimeResult result = default; result.Init(s); - if (DateTimeParse.TryParseExact(s, format, dtfi, style, ref result) && ((result.flags & ParseFlagsDateMask) == 0)) + if (DateTimeParse.TryParseExact(s, format, dtfiToUse, style, ref result) && ((result.flags & ParseFlagsDateMask) == 0)) { return new DateOnly(DayNumberFromDateTime(result.parsedDate)); } @@ -507,7 +509,7 @@ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, Dat return false; } - DateTimeResult dtResult = default; // The buffer to store the parsing result. + DateTimeResult dtResult = default; dtResult.Init(s); if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) @@ -566,7 +568,7 @@ public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format } } - DateTimeResult dtResult = default; // The buffer to store the parsing result. + DateTimeResult dtResult = default; dtResult.Init(s); if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) @@ -613,6 +615,7 @@ public static bool TryParseExact(ReadOnlySpan s, string [] formats, IForma for (int i = 0; i < formats.Length; i++) { + DateTimeFormatInfo dtfiToUse = dtfi; string? format = formats[i]; if (string.IsNullOrEmpty(format)) { @@ -624,20 +627,20 @@ public static bool TryParseExact(ReadOnlySpan s, string [] formats, IForma if (format[0] == 'o' || format[0] == 'O') { format = OFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; + dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; } else if (format[0] == 'r' || format[0] == 'R') { format = RFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; + dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; } } // 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; // The buffer to store the parsing result. + DateTimeResult dtResult = default; dtResult.Init(s); - if (DateTimeParse.TryParseExact(s, format, dtfi, style, ref dtResult) && ((dtResult.flags & ParseFlagsDateMask) == 0)) + if (DateTimeParse.TryParseExact(s, format, dtfiToUse, style, ref dtResult) && ((dtResult.flags & ParseFlagsDateMask) == 0)) { result = new DateOnly(DayNumberFromDateTime(dtResult.parsedDate)); return true; @@ -671,7 +674,7 @@ public static bool TryParse(string s, IFormatProvider? provider, DateTimeStyles return false; } - DateTimeResult dtResult = default; // The buffer to store the parsing result. + DateTimeResult dtResult = default; dtResult.Init(s); if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index f66f75b71454d1..6fa36ec3dbc88f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -355,7 +355,7 @@ public static TimeOnly Parse(ReadOnlySpan s, IFormatProvider? provider, Da throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); } - DateTimeResult result = default; // The buffer to store the parsing result. + DateTimeResult result = default; result.Init(s); if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref result)) @@ -413,7 +413,8 @@ public static TimeOnly ParseExact(ReadOnlySpan s, ReadOnlySpan forma } } - DateTimeResult result = default; // The buffer to store the parsing result. + DateTimeResult result = default; + result.Init(s); if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref result)) @@ -460,6 +461,7 @@ public static TimeOnly ParseExact(ReadOnlySpan s, string [] formats, IForm for (int i = 0; i < formats.Length; i++) { + DateTimeFormatInfo dtfiToUse = dtfi; string? format = formats[i]; if (string.IsNullOrEmpty(format)) { @@ -471,20 +473,20 @@ public static TimeOnly ParseExact(ReadOnlySpan s, string [] formats, IForm if (format[0] == 'o' || format[0] == 'O') { format = OFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; + dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; } else if (format[0] == 'r' || format[0] == 'R') { format = RFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; + dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; } } // Create a new result each time to ensure the runs are independent. Carry through // flags from the caller and return the result. - DateTimeResult result = default; // The buffer to store the parsing result. + DateTimeResult result = default; result.Init(s); - if (DateTimeParse.TryParseExact(s, format, dtfi, style, ref result) && ((result.flags & ParseFlagsTimeMask) == 0)) + if (DateTimeParse.TryParseExact(s, format, dtfiToUse, style, ref result) && ((result.flags & ParseFlagsTimeMask) == 0)) { return new TimeOnly(result.parsedDate.TimeOfDay.Ticks); } @@ -587,7 +589,8 @@ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, Dat return false; } - DateTimeResult dtResult = default; // The buffer to store the parsing result. + DateTimeResult dtResult = default; + dtResult.Init(s); if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) @@ -648,7 +651,7 @@ public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format } } - DateTimeResult dtResult = default; // The buffer to store the parsing result. + DateTimeResult dtResult = default; dtResult.Init(s); if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) @@ -696,6 +699,7 @@ public static bool TryParseExact(ReadOnlySpan s, string [] formats, IForma for (int i = 0; i < formats.Length; i++) { + DateTimeFormatInfo dtfiToUse = dtfi; string? format = formats[i]; if (string.IsNullOrEmpty(format)) { @@ -707,12 +711,12 @@ public static bool TryParseExact(ReadOnlySpan s, string [] formats, IForma if (format[0] == 'o' || format[0] == 'O') { format = OFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; + dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; } else if (format[0] == 'r' || format[0] == 'R') { format = RFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; + dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; } } @@ -720,7 +724,7 @@ public static bool TryParseExact(ReadOnlySpan s, string [] formats, IForma // flags from the caller and return the result. DateTimeResult dtResult = default; dtResult.Init(s); - if (DateTimeParse.TryParseExact(s, format, dtfi, style, ref dtResult) && ((dtResult.flags & ParseFlagsTimeMask) == 0)) + if (DateTimeParse.TryParseExact(s, format, dtfiToUse, style, ref dtResult) && ((dtResult.flags & ParseFlagsTimeMask) == 0)) { result = new TimeOnly(dtResult.parsedDate.TimeOfDay.Ticks); return true; diff --git a/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs index 60327884eb832c..c48a301e917bae 100644 --- a/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs @@ -268,12 +268,16 @@ public static void BasicFormatParseTest() string s = timeOnly.ToString(pattern); TimeOnly parsedTimeOnly = TimeOnly.Parse(s); Assert.True(TimeOnly.TryParse(s, out TimeOnly parsedTimeOnly1)); - Assert.Equal(timeOnly, parsedTimeOnly); - Assert.Equal(timeOnly, parsedTimeOnly1); + Assert.Equal(timeOnly.Hour % 12, parsedTimeOnly.Hour % 12); + Assert.Equal(timeOnly.Minute, parsedTimeOnly.Minute); + Assert.Equal(timeOnly.Hour % 12, parsedTimeOnly1.Hour % 12); + Assert.Equal(timeOnly.Minute, parsedTimeOnly1.Minute); parsedTimeOnly = TimeOnly.Parse(s.AsSpan()); Assert.True(TimeOnly.TryParse(s.AsSpan(), out parsedTimeOnly1)); - Assert.Equal(timeOnly, parsedTimeOnly); - Assert.Equal(timeOnly, parsedTimeOnly1); + Assert.Equal(timeOnly.Hour % 12, parsedTimeOnly.Hour % 12); + Assert.Equal(timeOnly.Minute, parsedTimeOnly.Minute); + Assert.Equal(timeOnly.Hour % 12, parsedTimeOnly1.Hour % 12); + Assert.Equal(timeOnly.Minute, parsedTimeOnly1.Minute); s = timeOnly.ToString(pattern, CultureInfo.InvariantCulture); parsedTimeOnly = TimeOnly.Parse(s, CultureInfo.InvariantCulture); From 81b433a0fb9b055fb135cb45e53f80f76af29316 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sun, 11 Apr 2021 11:00:58 -0700 Subject: [PATCH 07/12] Fedback 3 --- .../src/System/DateOnly.cs | 16 +++---- .../src/System/TimeOnly.cs | 44 +++++++------------ .../tests/System/DateOnlyTests.cs | 2 +- .../tests/System/TimeOnlyTests.cs | 2 +- 4 files changed, 26 insertions(+), 38 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs index e77572c2db4f7b..fa2a8ed46c1026 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -356,7 +356,7 @@ public static DateOnly ParseExact(ReadOnlySpan s, ReadOnlySpan forma /// A span containing the characters that represent a date to convert. /// An array of allowable formats of s. /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. - public static DateOnly ParseExact(ReadOnlySpan s, string [] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + public static DateOnly ParseExact(ReadOnlySpan s, string[] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); /// /// Converts the specified span representation of a date to its DateOnly equivalent using the specified array of formats, culture-specific format information, and style. @@ -367,7 +367,7 @@ public static DateOnly ParseExact(ReadOnlySpan s, ReadOnlySpan forma /// An object that supplies culture-specific formatting information about s. /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. - public static DateOnly ParseExact(ReadOnlySpan s, string [] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) + public static DateOnly ParseExact(ReadOnlySpan s, string[] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) { if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) { @@ -467,7 +467,7 @@ public static DateOnly ParseExact(string s, string format, IFormatProvider? prov /// A span containing the characters that represent a date to convert. /// An array of allowable formats of s. /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. - public static DateOnly ParseExact(string s, string [] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + public static DateOnly ParseExact(string s, string[] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); /// /// Converts the specified string representation of a date to its DateOnly equivalent using the specified array of formats, culture-specific format information, and style. @@ -478,7 +478,7 @@ public static DateOnly ParseExact(string s, string format, IFormatProvider? prov /// An object that supplies culture-specific formatting information about s. /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. - public static DateOnly ParseExact(string s, string [] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) + public static DateOnly ParseExact(string s, string[] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); return ParseExact(s.AsSpan(), formats, provider, style); @@ -592,7 +592,7 @@ public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format /// An array of allowable formats 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 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, string [] formats, out DateOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParseExact(ReadOnlySpan s, string[] formats, out DateOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); /// /// Converts the specified char span of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded. @@ -603,7 +603,7 @@ public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format /// 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, string [] formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) + public static bool TryParseExact(ReadOnlySpan s, string[] formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) { result = default; if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0 || formats == null) @@ -729,7 +729,7 @@ public static bool TryParseExact(string s, string format, IFormatProvider? provi /// An array of allowable formats 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 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(string s, string [] formats, out DateOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParseExact(string s, string[] formats, out DateOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); /// /// Converts the specified string of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded. @@ -740,7 +740,7 @@ public static bool TryParseExact(string s, string format, IFormatProvider? provi /// 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(string s, string [] formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) + public static bool TryParseExact(string s, string[] formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) { if (s == null) { diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index 6fa36ec3dbc88f..43d199fd96d719 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -7,7 +7,7 @@ namespace System { /// - /// Represents a time of day, as would be read from a clock, within the range 00:00:00 to 23:59:59.9999999 + /// Represents a time of day, as would be read from a clock, within the range 00:00:00 to 23:59:59.9999999. /// public readonly struct TimeOnly : IComparable, IComparable, IEquatable, IFormattable, ISpanFormattable { @@ -18,7 +18,7 @@ namespace System private const long MinTimeTicks = 0; // MaxTimeTicks is the max tick value for the time in the day. It is calculated using DateTime.Today.AddTicks(-1).TimeOfDay.Ticks. - private const long MaxTimeTicks = 863999999999; + private const long MaxTimeTicks = 863_999_999_999; /// /// Represents the smallest possible value of TimeOnly. @@ -149,7 +149,7 @@ private TimeOnly AddTicks(long ticks, out int wrappedDays) /// A number of whole and fractional hours. The value parameter can be negative or positive. /// When this method returns, contains the number of circulated days resulted from this addition operation. /// An object whose value is the sum of the time represented by this instance and the number of hours represented by value. - public TimeOnly AddHours(double value, out int wrappedDays)=> AddTicks((long)(value * TimeSpan.TicksPerHour), out wrappedDays); + public TimeOnly AddHours(double value, out int wrappedDays) => AddTicks((long)(value * TimeSpan.TicksPerHour), out wrappedDays); /// /// Returns a new TimeOnly that adds the specified number of minutes to the value of this instance. @@ -312,14 +312,7 @@ public int CompareTo(object? value) /// /// The object to compare to this instance. /// true if value is an instance of TimeOnly and equals the value of this instance; otherwise, false. - public override bool Equals(object? value) - { - if (value is TimeOnly) - { - return _ticks == ((TimeOnly)value)._ticks; - } - return false; - } + public override bool Equals(object? value) => value is TimeOnly timeOnly && _ticks == timeOnly._ticks; /// /// Returns the hash code for this instance. @@ -437,7 +430,7 @@ public static TimeOnly ParseExact(ReadOnlySpan s, ReadOnlySpan forma /// A span containing the characters that represent a time to convert. /// An array of allowable formats of s. /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. - public static TimeOnly ParseExact(ReadOnlySpan s, string [] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + public static TimeOnly ParseExact(ReadOnlySpan s, string[] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); /// /// Converts the specified span representation of a time to its TimeOnly equivalent using the specified array of formats, culture-specific format information, and style. @@ -448,7 +441,7 @@ public static TimeOnly ParseExact(ReadOnlySpan s, ReadOnlySpan forma /// An object that supplies culture-specific formatting information about s. /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. - public static TimeOnly ParseExact(ReadOnlySpan s, string [] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) + public static TimeOnly ParseExact(ReadOnlySpan s, string[] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) { if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) { @@ -547,7 +540,7 @@ public static TimeOnly ParseExact(string s, string format, IFormatProvider? prov /// A span containing the characters that represent a time to convert. /// An array of allowable formats of s. /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. - public static TimeOnly ParseExact(string s, string [] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + public static TimeOnly ParseExact(string s, string[] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); /// /// Converts the specified string representation of a time to its TimeOnly equivalent using the specified array of formats, culture-specific format information, and style. @@ -558,7 +551,7 @@ public static TimeOnly ParseExact(string s, string format, IFormatProvider? prov /// An object that supplies culture-specific formatting information about s. /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. - public static TimeOnly ParseExact(string s, string [] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) + public static TimeOnly ParseExact(string s, string[] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) { if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); return ParseExact(s.AsSpan(), formats, provider, style); @@ -676,7 +669,7 @@ public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format /// An array of allowable formats 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 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, string [] formats, out TimeOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParseExact(ReadOnlySpan s, string[] formats, out TimeOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); /// /// Converts the specified char span of a time to its TimeOnly equivalent and returns a value that indicates whether the conversion succeeded. @@ -687,7 +680,7 @@ public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format /// 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, string [] formats, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) + public static bool TryParseExact(ReadOnlySpan s, string[] formats, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { result = default; if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0 || formats == null) @@ -799,7 +792,7 @@ public static bool TryParseExact(string s, string format, IFormatProvider? provi /// An array of allowable formats 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 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(string s, string [] formats, out TimeOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParseExact(string s, string[] formats, out TimeOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); /// /// Converts the specified string of a time to its TimeOnly equivalent and returns a value that indicates whether the conversion succeeded. @@ -810,7 +803,7 @@ public static bool TryParseExact(string s, string format, IFormatProvider? provi /// 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 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(string s, string [] formats, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) + public static bool TryParseExact(string s, string[] formats, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { if (s == null) { @@ -821,14 +814,6 @@ public static bool TryParseExact(string s, string [] formats, IFormatProvider? p return TryParseExact(s.AsSpan(), formats, provider, style, out result); } - - // Acceptable formats: - // Sortable date/time pattern s Time part only - // RFC1123 pattern r/R Time part only like o - // round-trip date/time pattern o/O Time part only - // Short time pattern t - // Long time pattern T - /// /// Converts the value of the current TimeOnly object to its equivalent long date string representation. /// @@ -853,6 +838,7 @@ public static bool TryParseExact(string s, string [] formats, IFormatProvider? p /// /// A standard or custom time format string. /// A string representation of value of the current TimeOnly object as specified by format. + /// The accepted standard formats are 'r', 'R', 'o', 'O', 't' and 'T'. public string ToString(string? format) => ToString(format, DateTimeFormatInfo.CurrentInfo); /// @@ -868,6 +854,7 @@ public static bool TryParseExact(string s, string [] formats, IFormatProvider? p /// A standard or custom time format string. /// An object that supplies culture-specific formatting information. /// A string representation of value of the current TimeOnly object as specified by format and provider. + /// The accepted standard formats are 'r', 'R', 'o', 'O', 't' and 'T'. public string ToString(string? format, IFormatProvider? provider) { if (format == null || format.Length == 0) @@ -920,6 +907,7 @@ public string ToString(string? format, IFormatProvider? provider) /// A span containing the characters that represent a standard or custom format string that defines the acceptable format for destination. /// An optional object that supplies culture-specific formatting information for destination. /// true if the formatting was successful; otherwise, false. + /// The accepted standard formats are 'r', 'R', 'o', 'O', 't' and 'T'. public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default(ReadOnlySpan), IFormatProvider? provider = null) { if (format.Length == 0) @@ -970,4 +958,4 @@ public string ToString(string? format, IFormatProvider? provider) return DateTimeFormat.TryFormat(ToDateTime(), destination, out charsWritten, format, provider); } } -} \ No newline at end of file +} diff --git a/src/libraries/System.Runtime/tests/System/DateOnlyTests.cs b/src/libraries/System.Runtime/tests/System/DateOnlyTests.cs index 56078042056a2b..935c75e01bb7c3 100644 --- a/src/libraries/System.Runtime/tests/System/DateOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System/DateOnlyTests.cs @@ -354,7 +354,7 @@ public static void BasicFormatParseTest() [ConditionalFact(nameof(IsNotArabicCulture))] public static void FormatParseTest() { - string [] patterns = new string[] { CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern, CultureInfo.CurrentCulture.DateTimeFormat.LongDatePattern, "d", "D", "o", "r" }; + string[] patterns = new string[] { CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern, CultureInfo.CurrentCulture.DateTimeFormat.LongDatePattern, "d", "D", "o", "r" }; DateOnly dateOnly = DateOnly.FromDateTime(DateTime.Today); diff --git a/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs index c48a301e917bae..e8e3f27af9b54e 100644 --- a/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs @@ -307,7 +307,7 @@ public static void BasicFormatParseTest() [ConditionalFact(nameof(IsNotArabicCulture))] public static void FormatParseTest() { - string [] patterns = new string[] { CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern, CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern, "t", "T", "o", "r" }; + string[] patterns = new string[] { CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern, CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern, "t", "T", "o", "r" }; TimeOnly timeOnly = TimeOnly.FromDateTime(DateTime.Now); From 3a5af136fe4e6b2d177cca83fe3edab45271ce92 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sun, 11 Apr 2021 11:14:23 -0700 Subject: [PATCH 08/12] Feedback 4 --- .../System.Runtime/ref/System.Runtime.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 8c577fbf442f23..86de5c6a41a4cf 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -1328,26 +1328,26 @@ public static partial class Convert public static System.DateOnly Parse(System.ReadOnlySpan s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } public static System.DateOnly ParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format) { throw null; } public static System.DateOnly ParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } - public static System.DateOnly ParseExact(System.ReadOnlySpan s, string [] formats) { throw null; } - public static System.DateOnly ParseExact(System.ReadOnlySpan s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.DateOnly ParseExact(System.ReadOnlySpan s, string[] formats) { throw null; } + public static System.DateOnly ParseExact(System.ReadOnlySpan s, string[] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } public static System.DateOnly Parse(string s) { throw null; } public static System.DateOnly Parse(string s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } public static System.DateOnly ParseExact(string s, string format) { throw null; } public static System.DateOnly ParseExact(string s, string format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } - public static System.DateOnly ParseExact(string s, string [] formats) { throw null; } - public static System.DateOnly ParseExact(string s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.DateOnly ParseExact(string s, string[] formats) { throw null; } + public static System.DateOnly ParseExact(string s, string[] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out System.DateOnly result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.DateOnly result) { throw null; } public static bool TryParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, out System.DateOnly result) { throw null; } public static bool TryParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.DateOnly result) { throw null; } - public static bool TryParseExact(System.ReadOnlySpan s, string [] formats, out System.DateOnly result) { throw null; } - public static bool TryParseExact(System.ReadOnlySpan s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.DateOnly result) { throw null; } + public static bool TryParseExact(System.ReadOnlySpan s, string[] formats, out System.DateOnly result) { throw null; } + public static bool TryParseExact(System.ReadOnlySpan s, string[] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.DateOnly result) { throw null; } public static bool TryParse(string s, out System.DateOnly result) { throw null; } public static bool TryParse(string s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.DateOnly result) { throw null; } public static bool TryParseExact(string s, string format, out System.DateOnly result) { throw null; } public static bool TryParseExact(string s, string format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.DateOnly result) { throw null; } - public static bool TryParseExact(string s, string [] formats, out System.DateOnly result) { throw null; } - public static bool TryParseExact(string s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.DateOnly result) { throw null; } + public static bool TryParseExact(string s, string[] formats, out System.DateOnly result) { throw null; } + public static bool TryParseExact(string s, string[] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.DateOnly result) { throw null; } public string ToLongDateString() { throw null; } public string ToShortDateString() { throw null; } public override string ToString() { throw null; } @@ -3839,26 +3839,26 @@ public ThreadStaticAttribute() { } public static System.TimeOnly Parse(System.ReadOnlySpan s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } public static System.TimeOnly ParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format) { throw null; } public static System.TimeOnly ParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } - public static System.TimeOnly ParseExact(System.ReadOnlySpan s, string [] formats) { throw null; } - public static System.TimeOnly ParseExact(System.ReadOnlySpan s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.TimeOnly ParseExact(System.ReadOnlySpan s, string[] formats) { throw null; } + public static System.TimeOnly ParseExact(System.ReadOnlySpan s, string[] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } public static System.TimeOnly Parse(string s) { throw null; } public static System.TimeOnly Parse(string s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } public static System.TimeOnly ParseExact(string s, string format) { throw null; } public static System.TimeOnly ParseExact(string s, string format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } - public static System.TimeOnly ParseExact(string s, string [] formats) { throw null; } - public static System.TimeOnly ParseExact(string s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.TimeOnly ParseExact(string s, string[] formats) { throw null; } + public static System.TimeOnly ParseExact(string s, string[] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } public static bool TryParse(System.ReadOnlySpan s, out System.TimeOnly result) { throw null; } public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.TimeOnly result) { throw null; } public static bool TryParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, out System.TimeOnly result) { throw null; } public static bool TryParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.TimeOnly result) { throw null; } - public static bool TryParseExact(System.ReadOnlySpan s, string [] formats, out System.TimeOnly result) { throw null; } - public static bool TryParseExact(System.ReadOnlySpan s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.TimeOnly result) { throw null; } + public static bool TryParseExact(System.ReadOnlySpan s, string[] formats, out System.TimeOnly result) { throw null; } + public static bool TryParseExact(System.ReadOnlySpan s, string[] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.TimeOnly result) { throw null; } public static bool TryParse(string s, out System.TimeOnly result) { throw null; } public static bool TryParse(string s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.TimeOnly result) { throw null; } public static bool TryParseExact(string s, string format, out System.TimeOnly result) { throw null; } public static bool TryParseExact(string s, string format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.TimeOnly result) { throw null; } - public static bool TryParseExact(string s, string [] formats, out System.TimeOnly result) { throw null; } - public static bool TryParseExact(string s, string [] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.TimeOnly result) { throw null; } + public static bool TryParseExact(string s, string[] formats, out System.TimeOnly result) { throw null; } + public static bool TryParseExact(string s, string[] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style, out System.TimeOnly result) { throw null; } public string ToLongTimeString() { throw null; } public string ToShortTimeString() { throw null; } public override string ToString() { throw null; } From 2fb06434235d4191be30d26733412f95d8168e80 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Mon, 12 Apr 2021 16:37:46 -0700 Subject: [PATCH 09/12] Feedback 5 --- .../src/Resources/Strings.resx | 14 +- .../src/System/DateOnly.cs | 318 +++++++----------- .../System/Globalization/DateTimeFormat.cs | 11 +- .../src/System/TimeOnly.cs | 296 +++++++--------- .../System.Runtime/ref/System.Runtime.cs | 12 +- .../tests/System/TimeOnlyTests.cs | 22 +- 6 files changed, 275 insertions(+), 398 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 2f7e1eaaf6e8b9..5706b4543dcb65 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -1664,7 +1664,7 @@ Ticks must be between DateTime.MinValue.Ticks and DateTime.MaxValue.Ticks. - Ticks must be between 0 and 863_999_999_999. + Ticks must be between 0 and and TimeOnly.MaxValue.Ticks. Years value must be between +/-10000. @@ -1763,7 +1763,8 @@ Month must be between one and twelve. - Day number must be between 0 and 36_520_58. + Day number must be between 0 and DateOnly.MaxValue.DayNumber. + {Locked="DateOnly.MaxValue.DayNumber"} The Month parameter must be in the range 1 through 12. @@ -2199,13 +2200,8 @@ String '{0}' was not recognized as a valid TimeOnly. {Locked="TimeOnly"} - - String '{0}' contains parts which are not specific to the DateOnly. - {Locked="DateOnly"} - - - String '{0}' contains parts which are not specific to the TimeOnly. - {Locked="TimeOnly"} + + String '{0}' contains parts which are not specific to the {1}. The DateTime represented by the string '{0}' is not supported in calendar '{1}'. diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs index fa2a8ed46c1026..6cee176a31d103 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -6,6 +6,15 @@ namespace System { + internal enum ParseOperationResult + { + Success, + WrongStyles, + ParseFailure, + WrongParts, + BadFormatSpecifier + } + /// /// 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. /// @@ -140,6 +149,14 @@ public DateOnly AddDays(int value) /// true if left and right represent the same date; otherwise, false. public static bool operator ==(DateOnly left, DateOnly right) => left._dayNumber == right._dayNumber; + /// + /// Determines whether two specified instances of DateOnly are not equal. + /// + /// The first object to compare. + /// The second object to compare. + /// true if left and right do not represent the same date; otherwise, false. + public static bool operator !=(DateOnly left, DateOnly right) => left._dayNumber != right._dayNumber; + /// /// Determines whether one specified DateOnly is later than another specified DateTime. /// @@ -156,14 +173,6 @@ public DateOnly AddDays(int value) /// true if left is the same as or later than right; otherwise, false. public static bool operator >=(DateOnly left, DateOnly right) => left._dayNumber >= right._dayNumber; - /// - /// Determines whether two specified instances of DateOnly are not equal. - /// - /// The first object to compare. - /// The second object to compare. - /// true if left and right do not represent the same date; otherwise, false. - public static bool operator !=(DateOnly left, DateOnly right) => left._dayNumber != right._dayNumber; - /// /// Determines whether one specified DateOnly is earlier than another specified DateOnly. /// @@ -207,12 +216,7 @@ public DateOnly AddDays(int value) /// /// The object to compare to the current instance. /// 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. - public int CompareTo(DateOnly value) - { - if (_dayNumber < value._dayNumber) return -1; - if (_dayNumber > value._dayNumber) return 1; - return 0; - } + public int CompareTo(DateOnly value) => _dayNumber.CompareTo(value._dayNumber); /// /// 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. @@ -250,14 +254,6 @@ public int CompareTo(object? value) /// A 32-bit signed integer hash code. public override int GetHashCode() => _dayNumber; - /// - /// Converts a memory span that contains string representation of a date to its DateOnly equivalent by using the conventions of the current culture. - /// - /// The memory span that contains the string to parse. - /// An object that is equivalent to the date contained in s. - public static DateOnly Parse(ReadOnlySpan s) => Parse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); - - private const ParseFlags ParseFlagsDateMask = ParseFlags.HaveHour | ParseFlags.HaveMinute | ParseFlags.HaveSecond | ParseFlags.HaveTime | ParseFlags.TimeZoneUsed | ParseFlags.TimeZoneUtc | ParseFlags.CaptureOffset | ParseFlags.UtcSortPattern; @@ -268,38 +264,24 @@ public int CompareTo(object? value) /// An object that supplies culture-specific format information about s. /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. /// 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, DateTimeStyles style = DateTimeStyles.None) + 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)); - } - - DateTimeResult result = default; - result.Init(s); - - if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref result)) + ParseOperationResult result = TryParseInternal(s, provider, style, out DateOnly dateOnly); + if (result != ParseOperationResult.Success) { - throw new FormatException(SR.Format(SR.Format_BadDateOnly, s.ToString())); - } - - if ((result.flags & ParseFlagsDateMask) != 0) - { - throw new FormatException(SR.Format(SR.Format_DateOnlyContainsNoneDateParts, s.ToString())); + switch (result) + { + case ParseOperationResult.WrongStyles: throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + case ParseOperationResult.ParseFailure: throw new FormatException(SR.Format(SR.Format_BadDateOnly, s.ToString())); + default: + Debug.Assert(result == ParseOperationResult.WrongParts); + throw new FormatException(SR.Format(SR.Format_DateTimeOnlyContainsNoneDateParts, s.ToString(), nameof(DateOnly))); + } } - return new DateOnly(DayNumberFromDateTime(result.parsedDate)); + return dateOnly; } - /// - /// Converts the specified span 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. - /// - /// A span containing the characters that represent a date to convert. - /// A span containing the characters that represent a format specifier that defines the required format of s. - /// An object that is equivalent to the date contained in s, as specified by format. - public static DateOnly ParseExact(ReadOnlySpan s, ReadOnlySpan format) => ParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); - private const string OFormat = "yyyy'-'MM'-'dd"; private const string RFormat = "ddd, dd MMM yyyy"; @@ -312,41 +294,24 @@ public static DateOnly Parse(ReadOnlySpan s, IFormatProvider? provider, Da /// An object that supplies culture-specific formatting information about s. /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. - public static DateOnly ParseExact(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) + public static DateOnly ParseExact(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider = default, DateTimeStyles style = DateTimeStyles.None) { - if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) - { - throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); - } + ParseOperationResult result = TryParseExactInternal(s, format, provider, style, out DateOnly dateOnly); - if (format.Length == 1) + if (result != ParseOperationResult.Success) { - if (format[0] == 'o' || format[0] == 'O') + switch (result) { - format = OFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; - } - else if (format[0] == 'r' || format[0] == 'R') - { - format = RFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; - } - } - - DateTimeResult result = default; - result.Init(s); - - if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref result)) - { - throw new FormatException(SR.Format(SR.Format_BadDateOnly, s.ToString())); - } + case ParseOperationResult.WrongStyles: throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + case ParseOperationResult.ParseFailure: throw new FormatException(SR.Format(SR.Format_BadDateOnly, s.ToString())); + default: + Debug.Assert(result == ParseOperationResult.WrongParts); + throw new FormatException(SR.Format(SR.Format_DateTimeOnlyContainsNoneDateParts, s.ToString(), nameof(DateOnly))); - if ((result.flags & ParseFlagsDateMask) != 0) - { - throw new FormatException(SR.Format(SR.Format_DateOnlyContainsNoneDateParts, s.ToString())); + } } - return new DateOnly(DayNumberFromDateTime(result.parsedDate)); + return dateOnly; } /// @@ -356,7 +321,7 @@ public static DateOnly ParseExact(ReadOnlySpan s, ReadOnlySpan forma /// A span containing the characters that represent a date to convert. /// An array of allowable formats of s. /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. - public static DateOnly ParseExact(ReadOnlySpan s, string[] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + public static DateOnly ParseExact(ReadOnlySpan s, string[] formats) => ParseExact(s, formats, null, DateTimeStyles.None); /// /// Converts the specified span representation of a date to its DateOnly equivalent using the specified array of formats, culture-specific format information, and style. @@ -369,50 +334,20 @@ public static DateOnly ParseExact(ReadOnlySpan s, ReadOnlySpan forma /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. public static DateOnly ParseExact(ReadOnlySpan s, string[] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) { - if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) + ParseOperationResult result = TryParseExactInternal(s, formats, provider, style, out DateOnly dateOnly); + if (result != ParseOperationResult.Success) { - throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); - } - - if (formats == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.formats); - - DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider); - - for (int i = 0; i < formats.Length; i++) - { - DateTimeFormatInfo dtfiToUse = dtfi; - - string? format = formats[i]; - if (string.IsNullOrEmpty(format)) - { - throw new FormatException(SR.Argument_BadFormatSpecifier); - } - - if (format.Length == 1) - { - if (format[0] == 'o' || format[0] == 'O') - { - format = OFormat; - dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; - } - else if (format[0] == 'r' || format[0] == 'R') - { - format = RFormat; - dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; - } - } - - // Create a new result each time to ensure the runs are independent. Carry through - // flags from the caller and return the result. - DateTimeResult result = default; - result.Init(s); - if (DateTimeParse.TryParseExact(s, format, dtfiToUse, style, ref result) && ((result.flags & ParseFlagsDateMask) == 0)) + switch (result) { - return new DateOnly(DayNumberFromDateTime(result.parsedDate)); + case ParseOperationResult.WrongStyles: throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + case ParseOperationResult.ParseFailure: throw new FormatException(SR.Format(SR.Format_BadDateOnly, s.ToString())); + default: + Debug.Assert(result == ParseOperationResult.BadFormatSpecifier); + throw new FormatException(SR.Argument_BadFormatSpecifier); } } - throw new FormatException(SR.Format(SR.Format_BadDateOnly, s.ToString())); + return dateOnly; } /// @@ -420,7 +355,7 @@ public static DateOnly ParseExact(ReadOnlySpan s, string[] formats, IForma /// /// The string that contains the string to parse. /// An object that is equivalent to the date contained in s. - public static DateOnly Parse(string s) => Parse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + public static DateOnly Parse(string s) => Parse(s, null, DateTimeStyles.None); /// /// Converts a string that contains string representation of a date to its DateOnly equivalent by using culture-specific format information and a formatting style. @@ -442,7 +377,7 @@ public static DateOnly Parse(string s, IFormatProvider? provider, DateTimeStyles /// A string containing the characters that represent a date to convert. /// A string that represent a format specifier that defines the required format of s. /// An object that is equivalent to the date contained in s, as specified by format. - public static DateOnly ParseExact(string s, string format) => ParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + public static DateOnly ParseExact(string s, string format) => ParseExact(s, format, null, DateTimeStyles.None); /// /// Converts the specified string representation of a date to its DateOnly equivalent using the specified format, culture-specific format information, and style. @@ -467,7 +402,7 @@ public static DateOnly ParseExact(string s, string format, IFormatProvider? prov /// A span containing the characters that represent a date to convert. /// An array of allowable formats of s. /// An object that is equivalent to the date contained in s, as specified by format, provider, and style. - public static DateOnly ParseExact(string s, string[] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + public static DateOnly ParseExact(string s, string[] formats) => ParseExact(s, formats, null, DateTimeStyles.None); /// /// Converts the specified string representation of a date to its DateOnly equivalent using the specified array of formats, culture-specific format information, and style. @@ -490,7 +425,7 @@ public static DateOnly ParseExact(string s, string[] formats, IFormatProvider? p /// A span containing the characters representing the date to convert. /// 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, out DateOnly result) => TryParse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParse(ReadOnlySpan s, out DateOnly result) => TryParse(s, null, DateTimeStyles.None, out result); /// /// 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. @@ -500,13 +435,14 @@ public static DateOnly ParseExact(string s, string[] formats, IFormatProvider? p /// 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) - { - result = default; + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) => TryParseInternal(s, provider, style, out result) == ParseOperationResult.Success; + private static ParseOperationResult TryParseInternal(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) + { if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) { - return false; + result = default; + return ParseOperationResult.WrongStyles; } DateTimeResult dtResult = default; @@ -514,16 +450,18 @@ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, Dat if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) { - return false; + result = default; + return ParseOperationResult.ParseFailure; } if ((dtResult.flags & ParseFlagsDateMask) != 0) { - return false; + result = default; + return ParseOperationResult.WrongParts; } result = new DateOnly(DayNumberFromDateTime(dtResult.parsedDate)); - return true; + return ParseOperationResult.Success; } /// @@ -534,7 +472,7 @@ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, Dat /// The required 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, ReadOnlySpan format, out DateOnly result) => TryParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format, out DateOnly result) => TryParseExact(s, format, null, DateTimeStyles.None, out result); /// /// Converts the specified span representation of a date to its DateOnly equivalent using the specified format, culture-specific format information, and style. @@ -546,25 +484,31 @@ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, Dat /// 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, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) + public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) => + TryParseExactInternal(s, format, provider, style, out result) == ParseOperationResult.Success; + private static ParseOperationResult TryParseExactInternal(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) { - result = default; if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) { - return false; + result = default; + return ParseOperationResult.WrongStyles; } if (format.Length == 1) { - if (format[0] == 'o' || format[0] == 'O') - { - format = OFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; - } - else if (format[0] == 'r' || format[0] == 'R') + switch (format[0]) { - format = RFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; + case 'o': + case 'O': + format = OFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; + break; + + case 'r': + case 'R': + format = RFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; + break; } } @@ -573,16 +517,19 @@ public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) { - return false; + result = default; + return ParseOperationResult.ParseFailure; } if ((dtResult.flags & ParseFlagsDateMask) != 0) { - return false; + result = default; + return ParseOperationResult.WrongParts; } result = new DateOnly(DayNumberFromDateTime(dtResult.parsedDate)); - return true; + + return ParseOperationResult.Success; } /// @@ -592,7 +539,7 @@ public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format /// An array of allowable formats 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 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, string[] formats, out DateOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParseExact(ReadOnlySpan s, string[] formats, out DateOnly result) => TryParseExact(s, formats, null, DateTimeStyles.None, out result); /// /// Converts the specified char span of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded. @@ -603,12 +550,15 @@ public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format /// 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, string[] formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) + public static bool TryParseExact(ReadOnlySpan s, string[] formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) => + TryParseExactInternal(s, formats, provider, style, out result) == ParseOperationResult.Success; + + private static ParseOperationResult TryParseExactInternal(ReadOnlySpan s, string[] formats, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) { - result = default; if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0 || formats == null) { - return false; + result = default; + return ParseOperationResult.WrongStyles; } DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider); @@ -619,20 +569,25 @@ public static bool TryParseExact(ReadOnlySpan s, string[] formats, IFormat string? format = formats[i]; if (string.IsNullOrEmpty(format)) { - return false; + result = default; + return ParseOperationResult.BadFormatSpecifier; } if (format.Length == 1) { - if (format[0] == 'o' || format[0] == 'O') + switch (format[0]) { - format = OFormat; - dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; - } - else if (format[0] == 'r' || format[0] == 'R') - { - format = RFormat; - dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; + case 'o': + case 'O': + format = OFormat; + dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; + break; + + case 'r': + case 'R': + format = RFormat; + dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; + break; } } @@ -643,11 +598,12 @@ public static bool TryParseExact(ReadOnlySpan s, string[] formats, IFormat if (DateTimeParse.TryParseExact(s, format, dtfiToUse, style, ref dtResult) && ((dtResult.flags & ParseFlagsDateMask) == 0)) { result = new DateOnly(DayNumberFromDateTime(dtResult.parsedDate)); - return true; + return ParseOperationResult.Success; } } - return false; + result = default; + return ParseOperationResult.ParseFailure; } /// @@ -656,7 +612,7 @@ public static bool TryParseExact(ReadOnlySpan s, string[] formats, IFormat /// A string containing the characters representing the date to convert. /// 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(string s, out DateOnly result) => TryParse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParse(string s, out DateOnly result) => TryParse(s, null, DateTimeStyles.None, out result); /// /// 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. @@ -669,26 +625,12 @@ public static bool TryParseExact(ReadOnlySpan s, string[] formats, IFormat public static bool TryParse(string s, IFormatProvider? provider, DateTimeStyles style, out DateOnly result) { result = default; - if (s == null || (style & ~DateTimeStyles.AllowWhiteSpaces) != 0) - { - return false; - } - - DateTimeResult dtResult = default; - dtResult.Init(s); - - if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) - { - return false; - } - - if ((dtResult.flags & ParseFlagsDateMask) != 0) + if (s == null) { return false; } - result = new DateOnly(DayNumberFromDateTime(dtResult.parsedDate)); - return true; + return TryParse(s.AsSpan(), provider, style, out result); } /// @@ -699,7 +641,7 @@ public static bool TryParse(string s, IFormatProvider? provider, DateTimeStyles /// The required 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(string s, string format, out DateOnly result) => TryParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParseExact(string s, string format, out DateOnly result) => TryParseExact(s, format, null, DateTimeStyles.None, out result); /// /// Converts the specified span representation of a date to its DateOnly equivalent using the specified format, culture-specific format information, and style. @@ -729,7 +671,7 @@ public static bool TryParseExact(string s, string format, IFormatProvider? provi /// An array of allowable formats 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 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(string s, string[] formats, out DateOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParseExact(string s, string[] formats, out DateOnly result) => TryParseExact(s, formats, null, DateTimeStyles.None, out result); /// /// Converts the specified string of a date to its DateOnly equivalent and returns a value that indicates whether the conversion succeeded. @@ -775,7 +717,7 @@ public static bool TryParseExact(string s, string[] formats, IFormatProvider? pr /// /// A standard or custom date format string. /// A string representation of value of the current DateOnly object as specified by format. - public string ToString(string? format) => ToString(format, DateTimeFormatInfo.CurrentInfo); + public string ToString(string? format) => ToString(format, null); /// /// Converts the value of the current DateOnly object to its equivalent string representation using the specified culture-specific format information. @@ -804,21 +746,21 @@ public string ToString(string? format, IFormatProvider? provider) case 'o': case 'O': { - Span destination = stackalloc char[10]; - bool b = DateTimeFormat.TryFormatDateOnlyO(Year, Month, Day, destination); - Debug.Assert(b); - - return destination.ToString(); + return string.Create(10, this, (destination, value) => + { + bool b = DateTimeFormat.TryFormatDateOnlyO(value.Year, value.Month, value.Day, destination); + Debug.Assert(b); + }); } case 'r': case 'R': { - Span destination = stackalloc char[16]; - bool b = DateTimeFormat.TryFormatDateOnlyR(DayOfWeek, Year, Month, Day, destination); - Debug.Assert(b); - - return destination.ToString(); + return string.Create(16, this, (destination, value) => + { + bool b = DateTimeFormat.TryFormatDateOnlyR(value.DayOfWeek, value.Year, value.Month, value.Day, destination); + Debug.Assert(b); + }); } case 'm': @@ -850,7 +792,7 @@ public string ToString(string? format, IFormatProvider? provider) { if (format.Length == 0) { - format = "d".AsSpan(); + format = "d"; } if (format.Length == 1) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs index 5dd09cb0b50fc9..35e7f18a593ceb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs @@ -1114,15 +1114,14 @@ private static StringBuilder FormatStringBuilder(DateTime dateTime, ReadOnlySpan internal static bool IsValidCustomDateFormat(ReadOnlySpan format, bool throwOnError) { - int length = format.Length; int i = 0; - while (i < length) + while (i < format.Length) { switch (format[i]) { case '\\': - if (i == length - 1) + if (i == format.Length - 1) { if (throwOnError) { @@ -1138,12 +1137,12 @@ internal static bool IsValidCustomDateFormat(ReadOnlySpan format, bool thr case '\'': case '"': char quoteChar = format[i++]; - while (i < length && format[i] != quoteChar) + while (i < format.Length && format[i] != quoteChar) { i++; } - if (i >= length) + if (i >= format.Length) { if (throwOnError) { @@ -1266,7 +1265,7 @@ internal static bool TryFormatTimeOnlyO(int hour, int minute, int second, long f destination[5] = ':'; WriteTwoDecimalDigits((uint)second, destination, 6); destination[8] = '.'; - WriteDigits((uint)fraction, destination.Slice(9, 7)); + WriteDigits((uint)fraction, destination.Slice(9)); return true; } diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index 43d199fd96d719..3b87322f4cb902 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -23,12 +23,12 @@ namespace System /// /// Represents the smallest possible value of TimeOnly. /// - public static TimeOnly MinValue => new TimeOnly((ulong) MinTimeTicks); + public static TimeOnly MinValue => new TimeOnly((ulong)MinTimeTicks); /// /// Represents the largest possible value of TimeOnly. /// - public static TimeOnly MaxValue => new TimeOnly((ulong) MaxTimeTicks); + public static TimeOnly MaxValue => new TimeOnly((ulong)MaxTimeTicks); /// /// Initializes a new instance of the timeOnly structure to the specified hour and the minute. @@ -192,6 +192,14 @@ public bool IsBetween(TimeOnly start, TimeOnly end) /// true if left and right represent the same time; otherwise, false. public static bool operator ==(TimeOnly left, TimeOnly right) => left._ticks == right._ticks; + /// + /// Determines whether two specified instances of TimeOnly are not equal. + /// + /// The first object to compare. + /// The second object to compare. + /// true if left and right do not represent the same time; otherwise, false. + public static bool operator !=(TimeOnly left, TimeOnly right) => left._ticks != right._ticks; + /// /// Determines whether one specified TimeOnly is later than another specified TimeOnly. /// @@ -208,14 +216,6 @@ public bool IsBetween(TimeOnly start, TimeOnly end) /// true if left is the same as or later than right; otherwise, false. public static bool operator >=(TimeOnly left, TimeOnly right) => left._ticks >= right._ticks; - /// - /// Determines whether two specified instances of TimeOnly are not equal. - /// - /// The first object to compare. - /// The second object to compare. - /// true if left and right do not represent the same time; otherwise, false. - public static bool operator !=(TimeOnly left, TimeOnly right) => left._ticks != right._ticks; - /// /// Determines whether one specified TimeOnly is earlier than another specified TimeOnly. /// @@ -272,12 +272,7 @@ public bool IsBetween(TimeOnly start, TimeOnly end) /// Zero if this instance is the same as value. /// Greater than zero if this instance is later than value. /// - public int CompareTo(TimeOnly value) - { - if (_ticks < value._ticks) return -1; - if (_ticks > value._ticks) return 1; - return 0; - } + public int CompareTo(TimeOnly value) => _ticks.CompareTo(value._ticks); /// /// Compares the value of this instance to a specified object that contains a specified TimeOnly value, and returns an integer that indicates whether this instance is earlier than, the same as, or later than the specified TimeOnly value. @@ -324,13 +319,6 @@ public override int GetHashCode() return unchecked((int)ticks) ^ (int)(ticks >> 32); } - /// - /// Converts a memory span that contains string representation of a time to its TimeOnly equivalent by using the conventions of the current culture. - /// - /// The memory span that contains the string to parse. - /// An object that is equivalent to the time contained in s. - public static TimeOnly Parse(ReadOnlySpan s) => Parse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); - private const ParseFlags ParseFlagsTimeMask = ParseFlags.HaveYear | ParseFlags.HaveMonth | ParseFlags.HaveDay | ParseFlags.HaveDate | ParseFlags.TimeZoneUsed | ParseFlags.TimeZoneUtc | ParseFlags.ParsedMonthName | ParseFlags.CaptureOffset | ParseFlags.UtcSortPattern; @@ -341,38 +329,24 @@ public override int GetHashCode() /// An object that supplies culture-specific format information about s. /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. /// An object that is equivalent to the time contained in s, as specified by provider and styles. - public static TimeOnly Parse(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) + 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)); - } - - DateTimeResult result = default; - result.Init(s); - - if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref result)) - { - throw new FormatException(SR.Format(SR.Format_BadTimeOnly, s.ToString())); - } - - if ((result.flags & ParseFlagsTimeMask) != 0) + ParseOperationResult result = TryParseInternal(s, provider, style, out TimeOnly timeOnly); + if (result != ParseOperationResult.Success) { - throw new FormatException(SR.Format(SR.Format_TimeOnlyContainsNoneTimeParts, s.ToString())); + switch (result) + { + case ParseOperationResult.WrongStyles: throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + case ParseOperationResult.ParseFailure: throw new FormatException(SR.Format(SR.Format_BadTimeOnly, s.ToString())); + default: + Debug.Assert(result == ParseOperationResult.WrongParts); + throw new FormatException(SR.Format(SR.Format_DateTimeOnlyContainsNoneDateParts, s.ToString(), nameof(TimeOnly))); + } } - return new TimeOnly(result.parsedDate.TimeOfDay.Ticks); + return timeOnly; } - /// - /// Converts the specified span representation of a time to its TimeOnly equivalent using the specified format. - /// The format of the string representation must match the specified format exactly or an exception is thrown. - /// - /// A span containing the characters that represent a time to convert. - /// A span containing the characters that represent a format specifier that defines the required format of s. - /// An object that is equivalent to the time contained in s, as specified by format. - public static TimeOnly ParseExact(ReadOnlySpan s, ReadOnlySpan format) => ParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); - private const string OFormat = "HH':'mm':'ss'.'fffffff"; private const string RFormat = "HH':'mm':'ss"; @@ -385,42 +359,22 @@ public static TimeOnly Parse(ReadOnlySpan s, IFormatProvider? provider, Da /// An object that supplies culture-specific formatting information about s. /// A bitwise combination of enumeration values that indicates the permitted format of s. A typical value to specify is None. /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. - public static TimeOnly ParseExact(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) + public static TimeOnly ParseExact(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider = default, DateTimeStyles style = DateTimeStyles.None) { - if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) - { - throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); - } - - if (format.Length == 1) + ParseOperationResult result = TryParseExactInternal(s, format, provider, style, out TimeOnly timeOnly); + if (result != ParseOperationResult.Success) { - if (format[0] == 'o' || format[0] == 'O') + switch (result) { - format = OFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; - } - else if (format[0] == 'r' || format[0] == 'R') - { - format = RFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; + case ParseOperationResult.WrongStyles: throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + case ParseOperationResult.ParseFailure: throw new FormatException(SR.Format(SR.Format_BadTimeOnly, s.ToString())); + default: + Debug.Assert(result == ParseOperationResult.WrongParts); + throw new FormatException(SR.Format(SR.Format_DateTimeOnlyContainsNoneDateParts, s.ToString(), nameof(TimeOnly))); } } - DateTimeResult result = default; - - result.Init(s); - - if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref result)) - { - throw new FormatException(SR.Format(SR.Format_BadTimeOnly, s.ToString())); - } - - if ((result.flags & ParseFlagsTimeMask) != 0) - { - throw new FormatException(SR.Format(SR.Format_TimeOnlyContainsNoneTimeParts, s.ToString())); - } - - return new TimeOnly(result.parsedDate.TimeOfDay.Ticks); + return timeOnly; } /// @@ -430,7 +384,7 @@ public static TimeOnly ParseExact(ReadOnlySpan s, ReadOnlySpan forma /// A span containing the characters that represent a time to convert. /// An array of allowable formats of s. /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. - public static TimeOnly ParseExact(ReadOnlySpan s, string[] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + public static TimeOnly ParseExact(ReadOnlySpan s, string[] formats) => ParseExact(s, formats, null, DateTimeStyles.None); /// /// Converts the specified span representation of a time to its TimeOnly equivalent using the specified array of formats, culture-specific format information, and style. @@ -443,49 +397,20 @@ public static TimeOnly ParseExact(ReadOnlySpan s, ReadOnlySpan forma /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. public static TimeOnly ParseExact(ReadOnlySpan s, string[] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) { - if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) - { - throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); - } - - if (formats == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.formats); - - DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider); - - for (int i = 0; i < formats.Length; i++) + ParseOperationResult result = TryParseExactInternal(s, formats, provider, style, out TimeOnly timeOnly); + if (result != ParseOperationResult.Success) { - DateTimeFormatInfo dtfiToUse = dtfi; - string? format = formats[i]; - if (string.IsNullOrEmpty(format)) - { - throw new FormatException(SR.Argument_BadFormatSpecifier); - } - - if (format.Length == 1) + switch (result) { - if (format[0] == 'o' || format[0] == 'O') - { - format = OFormat; - dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; - } - else if (format[0] == 'r' || format[0] == 'R') - { - format = RFormat; - dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; - } - } - - // Create a new result each time to ensure the runs are independent. Carry through - // flags from the caller and return the result. - DateTimeResult result = default; - result.Init(s); - if (DateTimeParse.TryParseExact(s, format, dtfiToUse, style, ref result) && ((result.flags & ParseFlagsTimeMask) == 0)) - { - return new TimeOnly(result.parsedDate.TimeOfDay.Ticks); + case ParseOperationResult.WrongStyles: throw new ArgumentException(SR.Argument_InvalidDateStyles, nameof(style)); + case ParseOperationResult.ParseFailure: throw new FormatException(SR.Format(SR.Format_BadTimeOnly, s.ToString())); + default: + Debug.Assert(result == ParseOperationResult.BadFormatSpecifier); + throw new FormatException(SR.Argument_BadFormatSpecifier); } } - throw new FormatException(SR.Format(SR.Format_BadTimeOnly, s.ToString())); + return timeOnly; } /// @@ -493,7 +418,7 @@ public static TimeOnly ParseExact(ReadOnlySpan s, string[] formats, IForma /// /// The string that contains the string to parse. /// An object that is equivalent to the time contained in s. - public static TimeOnly Parse(string s) => Parse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + public static TimeOnly Parse(string s) => Parse(s, null, DateTimeStyles.None); /// /// Converts a string that contains string representation of a time to its TimeOnly equivalent by using culture-specific format information and a formatting style. @@ -515,7 +440,7 @@ public static TimeOnly Parse(string s, IFormatProvider? provider, DateTimeStyles /// A string containing the characters that represent a time to convert. /// A string that represent a format specifier that defines the required format of s. /// An object that is equivalent to the time contained in s, as specified by format. - public static TimeOnly ParseExact(string s, string format) => ParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + public static TimeOnly ParseExact(string s, string format) => ParseExact(s, format, null, DateTimeStyles.None); /// /// Converts the specified string representation of a time to its TimeOnly equivalent using the specified format, culture-specific format information, and style. @@ -540,7 +465,7 @@ public static TimeOnly ParseExact(string s, string format, IFormatProvider? prov /// A span containing the characters that represent a time to convert. /// An array of allowable formats of s. /// An object that is equivalent to the time contained in s, as specified by format, provider, and style. - public static TimeOnly ParseExact(string s, string[] formats) => ParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None); + public static TimeOnly ParseExact(string s, string[] formats) => ParseExact(s, formats, null, DateTimeStyles.None); /// /// Converts the specified string representation of a time to its TimeOnly equivalent using the specified array of formats, culture-specific format information, and style. @@ -563,7 +488,7 @@ public static TimeOnly ParseExact(string s, string[] formats, IFormatProvider? p /// A span containing the characters representing the time to convert. /// 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 time. This parameter is passed uninitialized. /// true if the s parameter was converted successfully; otherwise, false. - public static bool TryParse(ReadOnlySpan s, out TimeOnly result) => TryParse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParse(ReadOnlySpan s, out TimeOnly result) => TryParse(s, null, DateTimeStyles.None, out result); /// /// Converts the specified span representation of a time to its TimeOnly equivalent using the specified array of formats, culture-specific format information, and style. And returns a value that indicates whether the conversion succeeded. @@ -573,13 +498,14 @@ public static TimeOnly ParseExact(string s, string[] formats, IFormatProvider? p /// 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 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) + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) => + TryParseInternal(s, provider, style, out result) == ParseOperationResult.Success; + private static ParseOperationResult TryParseInternal(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { - result = default; - if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) { - return false; + result = default; + return ParseOperationResult.WrongStyles; } DateTimeResult dtResult = default; @@ -588,17 +514,19 @@ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, Dat if (!DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) { - return false; + result = default; + return ParseOperationResult.ParseFailure; } if ((dtResult.flags & ParseFlagsTimeMask) != 0) { - return false; + result = default; + return ParseOperationResult.WrongParts; } result = new TimeOnly(dtResult.parsedDate.TimeOfDay.Ticks); - return true; + return ParseOperationResult.Success; } /// @@ -609,7 +537,7 @@ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, Dat /// The required 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, ReadOnlySpan format, out TimeOnly result) => TryParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format, out TimeOnly result) => TryParseExact(s, format, null, DateTimeStyles.None, out result); /// /// Converts the specified span representation of a time to its TimeOnly equivalent using the specified format, culture-specific format information, and style. @@ -621,26 +549,31 @@ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, Dat /// 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, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) + public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) => + TryParseExactInternal(s, format, provider, style, out result) == ParseOperationResult.Success; + private static ParseOperationResult TryParseExactInternal(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { - result = default; - if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) { - return false; + result = default; + return ParseOperationResult.WrongStyles; } if (format.Length == 1) { - if (format[0] == 'o' || format[0] == 'O') - { - format = OFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; - } - else if (format[0] == 'r' || format[0] == 'R') + switch (format[0]) { - format = RFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; + case 'o': + case 'O': + format = OFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; + break; + + case 'r': + case 'R': + format = RFormat; + provider = CultureInfo.InvariantCulture.DateTimeFormat; + break; } } @@ -649,17 +582,19 @@ public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format if (!DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, ref dtResult)) { - return false; + result = default; + return ParseOperationResult.ParseFailure; } if ((dtResult.flags & ParseFlagsTimeMask) != 0) { - return false; + result = default; + return ParseOperationResult.WrongParts; } result = new TimeOnly(dtResult.parsedDate.TimeOfDay.Ticks); - return true; + return ParseOperationResult.Success; } /// @@ -669,7 +604,7 @@ public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format /// An array of allowable formats 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 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, string[] formats, out TimeOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParseExact(ReadOnlySpan s, string[] formats, out TimeOnly result) => TryParseExact(s, formats, null, DateTimeStyles.None, out result); /// /// Converts the specified char span of a time to its TimeOnly equivalent and returns a value that indicates whether the conversion succeeded. @@ -680,12 +615,15 @@ public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format /// 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, string[] formats, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) + public static bool TryParseExact(ReadOnlySpan s, string[] formats, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) => + TryParseExactInternal(s, formats, provider, style, out result) == ParseOperationResult.Success; + + private static ParseOperationResult TryParseExactInternal(ReadOnlySpan s, string[] formats, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { - result = default; if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0 || formats == null) { - return false; + result = default; + return ParseOperationResult.WrongStyles; } DateTimeFormatInfo dtfi = DateTimeFormatInfo.GetInstance(provider); @@ -696,20 +634,25 @@ public static bool TryParseExact(ReadOnlySpan s, string[] formats, IFormat string? format = formats[i]; if (string.IsNullOrEmpty(format)) { - return false; + result = default; + return ParseOperationResult.BadFormatSpecifier; } if (format.Length == 1) { - if (format[0] == 'o' || format[0] == 'O') + switch (format[0]) { - format = OFormat; - dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; - } - else if (format[0] == 'r' || format[0] == 'R') - { - format = RFormat; - dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; + case 'o': + case 'O': + format = OFormat; + dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; + break; + + case 'r': + case 'R': + format = RFormat; + dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; + break; } } @@ -720,11 +663,12 @@ public static bool TryParseExact(ReadOnlySpan s, string[] formats, IFormat if (DateTimeParse.TryParseExact(s, format, dtfiToUse, style, ref dtResult) && ((dtResult.flags & ParseFlagsTimeMask) == 0)) { result = new TimeOnly(dtResult.parsedDate.TimeOfDay.Ticks); - return true; + return ParseOperationResult.Success; } } - return false; + result = default; + return ParseOperationResult.ParseFailure; } /// @@ -733,7 +677,7 @@ public static bool TryParseExact(ReadOnlySpan s, string[] formats, IFormat /// A string containing the characters representing the time to convert. /// 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 time. This parameter is passed uninitialized. /// true if the s parameter was converted successfully; otherwise, false. - public static bool TryParse(string s, out TimeOnly result) => TryParse(s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParse(string s, out TimeOnly result) => TryParse(s, null, DateTimeStyles.None, out result); /// /// Converts the specified string representation of a time to its TimeOnly equivalent using the specified array of formats, culture-specific format information, and style. And returns a value that indicates whether the conversion succeeded. @@ -745,9 +689,9 @@ public static bool TryParseExact(ReadOnlySpan s, string[] formats, IFormat /// true if the s parameter was converted successfully; otherwise, false. public static bool TryParse(string s, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { - result = default; if (s == null) { + result = default; return false; } @@ -762,7 +706,7 @@ public static bool TryParse(string s, IFormatProvider? provider, DateTimeStyles /// The required 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(string s, string format, out TimeOnly result) => TryParseExact(s, format, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParseExact(string s, string format, out TimeOnly result) => TryParseExact(s, format, null, DateTimeStyles.None, out result); /// /// Converts the specified span representation of a time to its TimeOnly equivalent using the specified format, culture-specific format information, and style. @@ -792,7 +736,7 @@ public static bool TryParseExact(string s, string format, IFormatProvider? provi /// An array of allowable formats 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 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(string s, string[] formats, out TimeOnly result) => TryParseExact(s, formats, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out result); + public static bool TryParseExact(string s, string[] formats, out TimeOnly result) => TryParseExact(s, formats, null, DateTimeStyles.None, out result); /// /// Converts the specified string of a time to its TimeOnly equivalent and returns a value that indicates whether the conversion succeeded. @@ -839,7 +783,7 @@ public static bool TryParseExact(string s, string[] formats, IFormatProvider? pr /// A standard or custom time format string. /// A string representation of value of the current TimeOnly object as specified by format. /// The accepted standard formats are 'r', 'R', 'o', 'O', 't' and 'T'. - public string ToString(string? format) => ToString(format, DateTimeFormatInfo.CurrentInfo); + public string ToString(string? format) => ToString(format, null); /// /// Converts the value of the current TimeOnly object to its equivalent string representation using the specified culture-specific format information. @@ -869,21 +813,21 @@ public string ToString(string? format, IFormatProvider? provider) case 'o': case 'O': { - Span destination = stackalloc char[16]; - bool b = DateTimeFormat.TryFormatTimeOnlyO(Hour, Minute, Second, _ticks % TimeSpan.TicksPerSecond, destination); - Debug.Assert(b); - - return destination.ToString(); + return string.Create(16, this, (destination, value) => + { + bool b = DateTimeFormat.TryFormatTimeOnlyO(value.Hour, value.Minute, value.Second, value._ticks % TimeSpan.TicksPerSecond, destination); + Debug.Assert(b); + }); } case 'r': case 'R': { - Span destination = stackalloc char[8]; - bool b = DateTimeFormat.TryFormatTimeOnlyR(Hour, Minute, Second, destination); - Debug.Assert(b); - - return destination.ToString(); + return string.Create(8, this, (destination, value) => + { + bool b = DateTimeFormat.TryFormatTimeOnlyR(value.Hour, value.Minute, value.Second, destination); + Debug.Assert(b); + }); } case 't': @@ -912,7 +856,7 @@ public string ToString(string? format, IFormatProvider? provider) { if (format.Length == 0) { - format = "t".AsSpan(); + format = "t"; } if (format.Length == 1) diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 86de5c6a41a4cf..707e8f389e0287 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -1324,10 +1324,8 @@ public static partial class Convert public bool Equals(System.DateOnly value) { throw null; } public override bool Equals(object? value) { throw null; } public override int GetHashCode() { throw null; } - public static System.DateOnly Parse(System.ReadOnlySpan s) { throw null; } - public static System.DateOnly Parse(System.ReadOnlySpan s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } - public static System.DateOnly ParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format) { throw null; } - public static System.DateOnly ParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.DateOnly Parse(System.ReadOnlySpan s, System.IFormatProvider? provider = default, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.DateOnly ParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, System.IFormatProvider? provider = default, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } public static System.DateOnly ParseExact(System.ReadOnlySpan s, string[] formats) { throw null; } public static System.DateOnly ParseExact(System.ReadOnlySpan s, string[] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } public static System.DateOnly Parse(string s) { throw null; } @@ -3835,10 +3833,8 @@ public ThreadStaticAttribute() { } public bool Equals(System.TimeOnly value) { throw null; } public override bool Equals(object? value) { throw null; } public override int GetHashCode() { throw null; } - public static System.TimeOnly Parse(System.ReadOnlySpan s) { throw null; } - public static System.TimeOnly Parse(System.ReadOnlySpan s, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } - public static System.TimeOnly ParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format) { throw null; } - public static System.TimeOnly ParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.TimeOnly Parse(System.ReadOnlySpan s, System.IFormatProvider? provider = default, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } + public static System.TimeOnly ParseExact(System.ReadOnlySpan s, System.ReadOnlySpan format, System.IFormatProvider? provider = default, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } public static System.TimeOnly ParseExact(System.ReadOnlySpan s, string[] formats) { throw null; } public static System.TimeOnly ParseExact(System.ReadOnlySpan s, string[] formats, System.IFormatProvider? provider, System.Globalization.DateTimeStyles style = System.Globalization.DateTimeStyles.None) { throw null; } public static System.TimeOnly Parse(string s) { throw null; } diff --git a/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs index e8e3f27af9b54e..dd7b5b8c1fc9a1 100644 --- a/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs @@ -62,10 +62,10 @@ public static void ConstructorsTest() Assert.Throws(() => new TimeOnly(10, -2)); Assert.Throws(() => new TimeOnly(10, 10, 60)); Assert.Throws(() => new TimeOnly(10, 10, -3)); - Assert.Throws(() => new TimeOnly(10, 10, 10, 1000)); - Assert.Throws(() => new TimeOnly(10, 10, 10, -4)); - Assert.Throws(() => new TimeOnly(TimeOnly.MaxValue.Ticks + 1)); - Assert.Throws(() => new TimeOnly(-1)); + AssertExtensions.Throws("millisecond", () => new TimeOnly(10, 10, 10, 1000)); + AssertExtensions.Throws("millisecond", () => new TimeOnly(10, 10, 10, -4)); + AssertExtensions.Throws("ticks", () => new TimeOnly(TimeOnly.MaxValue.Ticks + 1)); + AssertExtensions.Throws("ticks", () => new TimeOnly(-1)); } [Fact] @@ -156,7 +156,7 @@ public static void IsBetweenTest() } [Fact] - public static void CompareOperatosTest() + public static void CompareOperatorsTest() { TimeOnly to1 = new TimeOnly(14, 30); TimeOnly to2 = new TimeOnly(14, 30); @@ -201,8 +201,8 @@ public static void FromToTimeSpanTest() Assert.Equal(TimeOnly.MaxValue, TimeOnly.FromTimeSpan(TimeOnly.MaxValue.ToTimeSpan())); - Assert.Throws(() => TimeOnly.FromTimeSpan(new TimeSpan(24, 0, 0))); - Assert.Throws(() => TimeOnly.FromTimeSpan(new TimeSpan(-1, 0, 0))); + AssertExtensions.Throws("ticks", () => TimeOnly.FromTimeSpan(new TimeSpan(24, 0, 0))); + AssertExtensions.Throws("ticks", () => TimeOnly.FromTimeSpan(new TimeSpan(-1, 0, 0))); } [Fact] @@ -289,13 +289,13 @@ public static void BasicFormatParseTest() Assert.Equal(parsedTimeOnly, parsedTimeOnly1); Assert.False(TimeOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out parsedTimeOnly1)); - Assert.Throws(() => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal)); + AssertExtensions.Throws("style", () => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal)); Assert.False(TimeOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out parsedTimeOnly1)); - Assert.Throws(() => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal)); + AssertExtensions.Throws("style", () => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal)); Assert.False(TimeOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out parsedTimeOnly1)); - Assert.Throws(() => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal)); + AssertExtensions.Throws("style", () => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal)); Assert.False(TimeOnly.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out parsedTimeOnly1)); - Assert.Throws(() => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault)); + AssertExtensions.Throws("style", () => TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault)); s = " " + s + " "; parsedTimeOnly = TimeOnly.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces); From 90ae81eceabc8b9ffbfc7b8450bf6888658ba8d0 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Tue, 13 Apr 2021 09:47:23 -0700 Subject: [PATCH 10/12] Update src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs Co-authored-by: Santiago Fernandez Madero --- src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index 3b87322f4cb902..84bc040d9cb185 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -551,6 +551,7 @@ private static ParseOperationResult TryParseInternal(ReadOnlySpan s, IForm /// true if s was converted successfully; otherwise, false. public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) => TryParseExactInternal(s, format, provider, style, out result) == ParseOperationResult.Success; + private static ParseOperationResult TryParseExactInternal(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out TimeOnly result) { if ((style & ~DateTimeStyles.AllowWhiteSpaces) != 0) From 381d5887752f08c9f2ba17b0471cd1df9e9c9b5f Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Tue, 13 Apr 2021 13:34:09 -0700 Subject: [PATCH 11/12] Feedback 6 --- .../System.Private.CoreLib/src/System/TimeOnly.cs | 10 +++++----- .../System.Runtime/tests/System/TimeOnlyTests.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index 84bc040d9cb185..e6867efa1360f7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -233,12 +233,12 @@ public bool IsBetween(TimeOnly start, TimeOnly end) public static bool operator <=(TimeOnly left, TimeOnly right) => left._ticks <= right._ticks; /// - /// Subtracts a specified time from another specified time and returns a time interval. + /// Gives the elapsed time between two points on a circular clock, which will always be a positive value. /// - /// The time value to subtract from (the minuend). - /// The time value to subtract (the subtrahend). - /// The absolute time interval between t1 and t2. - public static TimeSpan operator -(TimeOnly t1, TimeOnly t2) => new TimeSpan(Math.Abs(t1._ticks - t2._ticks)); + /// The first TimeOnly instance. + /// The second TimeOnly instance.. + /// The elapsed time between t1 and t2. + public static TimeSpan operator -(TimeOnly t1, TimeOnly t2) => new TimeSpan((t2._ticks - t1._ticks + TimeSpan.TicksPerDay) % TimeSpan.TicksPerDay); /// /// Constructs a TimeOnly object from a TimeSpan representing the time elapsed since midnight. diff --git a/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs index dd7b5b8c1fc9a1..54e5ed5f8cb3ad 100644 --- a/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs @@ -186,7 +186,7 @@ public static void SubtractOperatorTest() TimeOnly to2 = new TimeOnly(14, 0); Assert.Equal(new TimeSpan(3, 29, 20), to1 - to2); - Assert.Equal(new TimeSpan(3, 29, 20), to2 - to1); + Assert.Equal(new TimeSpan(20,30, 40), to2 - to1); Assert.Equal(TimeSpan.Zero, to1 - to1); } From 5ba16e8e30e76acb9888d554773c5ec519fc1dde Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Tue, 13 Apr 2021 15:24:44 -0700 Subject: [PATCH 12/12] Feedback 7 --- src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs | 2 +- src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index e6867efa1360f7..291bc47f73d56d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -238,7 +238,7 @@ public bool IsBetween(TimeOnly start, TimeOnly end) /// The first TimeOnly instance. /// The second TimeOnly instance.. /// The elapsed time between t1 and t2. - public static TimeSpan operator -(TimeOnly t1, TimeOnly t2) => new TimeSpan((t2._ticks - t1._ticks + TimeSpan.TicksPerDay) % TimeSpan.TicksPerDay); + public static TimeSpan operator -(TimeOnly t1, TimeOnly t2) => new TimeSpan((t1._ticks - t2._ticks + TimeSpan.TicksPerDay) % TimeSpan.TicksPerDay); /// /// Constructs a TimeOnly object from a TimeSpan representing the time elapsed since midnight. diff --git a/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs index 54e5ed5f8cb3ad..a2d6cecac91da7 100644 --- a/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs +++ b/src/libraries/System.Runtime/tests/System/TimeOnlyTests.cs @@ -185,9 +185,10 @@ public static void SubtractOperatorTest() TimeOnly to1 = new TimeOnly(10, 30, 40); TimeOnly to2 = new TimeOnly(14, 0); - Assert.Equal(new TimeSpan(3, 29, 20), to1 - to2); - Assert.Equal(new TimeSpan(20,30, 40), to2 - to1); + Assert.Equal(new TimeSpan(3, 29, 20), to2 - to1); + Assert.Equal(new TimeSpan(20,30, 40), to1 - to2); Assert.Equal(TimeSpan.Zero, to1 - to1); + Assert.Equal(new TimeSpan(2,0, 0), new TimeOnly(1, 0) - new TimeOnly(23, 0)); } [Fact]