diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs index b4796fd92e63d9..a7ebe4c496f641 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs @@ -341,10 +341,10 @@ internal enum DS new DS[] { DS.BEGIN, DS.ERROR, DS.TX_N, DS.N, DS.D_Nd, DS.T_Nt, DS.ERROR, DS.D_M, DS.D_M, DS.D_S, DS.T_S, DS.BEGIN, DS.D_Y, DS.D_Y, DS.ERROR, DS.BEGIN, DS.BEGIN, DS.ERROR }, // DS.N // DS.N -new DS[] { DS.ERROR, DS.DX_NN, DS.ERROR, DS.NN, DS.D_NNd, DS.ERROR, DS.DX_NM, DS.D_NM, DS.D_MNd, DS.D_NDS, DS.ERROR, DS.N, DS.D_YN, DS.D_YNd, DS.DX_YN, DS.N, DS.N, DS.ERROR }, +new DS[] { DS.ERROR, DS.DX_NN, DS.TX_NN, DS.NN, DS.D_NNd, DS.ERROR, DS.DX_NM, DS.D_NM, DS.D_MNd, DS.D_NDS, DS.ERROR, DS.N, DS.D_YN, DS.D_YNd, DS.DX_YN, DS.N, DS.N, DS.ERROR }, // DS.NN // DS.NN -new DS[] { DS.DX_NN, DS.DX_NNN, DS.TX_N, DS.DX_NNN, DS.ERROR, DS.T_Nt, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.ERROR, DS.T_S, DS.NN, DS.DX_NNY, DS.ERROR, DS.DX_NNY, DS.NN, DS.NN, DS.ERROR }, +new DS[] { DS.DX_NN, DS.DX_NNN, DS.TX_NNN, DS.DX_NNN, DS.ERROR, DS.T_Nt, DS.DX_MNN, DS.DX_MNN, DS.ERROR, DS.ERROR, DS.T_S, DS.NN, DS.DX_NNY, DS.ERROR, DS.DX_NNY, DS.NN, DS.NN, DS.ERROR }, // DS.D_Nd // DS.D_Nd new DS[] { DS.ERROR, DS.DX_NN, DS.ERROR, DS.D_NN, DS.D_NNd, DS.ERROR, DS.DX_NM, DS.D_MN, DS.D_MNd, DS.ERROR, DS.ERROR, DS.D_Nd, DS.D_YN, DS.D_YNd, DS.DX_YN, DS.ERROR, DS.D_Nd, DS.ERROR }, @@ -3192,8 +3192,8 @@ private static bool ParseFractionExact(ref __DTString str, int maxDigitLen, ref /*=================================ParseSign================================== **Action: Parse a positive or a negative sign. - **Returns: true if postive sign. flase if negative sign. - **Arguments: str: a __DTString. The parsing will start from the + **Returns: true if positive sign. false if negative sign. + **Arguments: str: a __DTString. The parsing will start from the ** next character after str.Index. **Exceptions: FormatException if end of string is encountered or a sign ** symbol is not found. @@ -5244,6 +5244,8 @@ private static void Trace(string s) // internal ref struct __DTString { + internal const char RightToLeftMark = '\u200F'; + // // Value property: stores the real string to be parsed. // @@ -5418,7 +5420,7 @@ internal TokenType GetSeparatorToken(DateTimeFormatInfo dtfi, out int indexBefor indexBeforeSeparator = Index; charBeforeSeparator = m_current; TokenType tokenType; - if (!SkipWhiteSpaceCurrent()) + if (!SkipWhiteSpaceAndRtlMarkCurrent()) { // Reach the end of the string. return TokenType.SEP_End; @@ -5672,18 +5674,20 @@ internal void SkipWhiteSpaces() } // - // Skip white spaces from the current position + // Skip white spaces and right-to-left Mark from the current position // + // U+200F is the Unicode right-to-left mark. In some Bidi cultures, this mark gets inserted inside the formatted date or time to have the output displayed in the correct layout. + // This mark does not affect the date or the time component values. Ignoring this mark during parsing wouldn't affect the result but will avoid having the parsing fail. // Return false if end of string is encountered. // - internal bool SkipWhiteSpaceCurrent() + internal bool SkipWhiteSpaceAndRtlMarkCurrent() { if (Index >= Length) { return false; } - if (!char.IsWhiteSpace(m_current)) + if (!char.IsWhiteSpace(m_current) && m_current != RightToLeftMark) { return true; } @@ -5691,7 +5695,7 @@ internal bool SkipWhiteSpaceCurrent() while (++Index < Length) { m_current = Value[Index]; - if (!char.IsWhiteSpace(m_current)) + if (!char.IsWhiteSpace(m_current) && m_current != RightToLeftMark) { return true; } diff --git a/src/libraries/System.Runtime/tests/System/DateTimeTests.cs b/src/libraries/System.Runtime/tests/System/DateTimeTests.cs index 1a9a487ecd4849..0cbe26a005ede3 100644 --- a/src/libraries/System.Runtime/tests/System/DateTimeTests.cs +++ b/src/libraries/System.Runtime/tests/System/DateTimeTests.cs @@ -1024,6 +1024,43 @@ public static void Parse_Japanese() Assert.Equal(expected, DateTime.Parse(expectedString, cultureInfo)); } + private static bool IsNotOSXOrBrowser => !PlatformDetection.IsOSXLike && !PlatformDetection.IsBrowser; + + [ConditionalTheory(nameof(IsNotOSXOrBrowser))] + [InlineData("ar")] + [InlineData("ar-EG")] + [InlineData("ar-IQ")] + [InlineData("ar-SA")] + [InlineData("ar-YE")] + public static void DateTimeParsingWithBiDiCultureTest(string cultureName) + { + DateTime dt = new DateTime(2021, 11, 30, 14, 30, 40); + CultureInfo ci = CultureInfo.GetCultureInfo(cultureName); + string formatted = dt.ToString("d", ci); + Assert.Equal(dt.Date, DateTime.Parse(formatted, ci)); + formatted = dt.ToString("g", ci); + DateTime parsed = DateTime.Parse(formatted, ci); + Assert.Equal(dt.Date, parsed.Date); + Assert.Equal(dt.Hour, parsed.Hour); + Assert.Equal(dt.Minute, parsed.Minute); + } + + [Fact] + public static void DateTimeParsingWithSpaceTimeSeparators() + { + DateTime dt = new DateTime(2021, 11, 30, 14, 30, 40); + CultureInfo ci = CultureInfo.GetCultureInfo("en-US"); + // It is possible we find some cultures use such formats. dz-BT is example of that + string formatted = dt.ToString("yyyy/MM/dd hh mm tt", ci); + DateTime parsed = DateTime.Parse(formatted, ci); + Assert.Equal(dt.Hour, parsed.Hour); + Assert.Equal(dt.Minute, parsed.Minute); + + formatted = dt.ToString("yyyy/MM/dd hh mm ss tt", ci); + parsed = DateTime.Parse(formatted, ci); + Assert.Equal(dt, parsed); + } + [Fact] public static void Parse_InvalidArguments_Throws() {