Skip to content

Commit b54d6ef

Browse files
authored
ConvertIcuTimeFormatString: convert narrow no-break spaces to spaces too. (#83589)
1 parent e935780 commit b54d6ef

File tree

3 files changed

+63
-11
lines changed

3 files changed

+63
-11
lines changed

src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Icu.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,14 @@ private static string ConvertIcuTimeFormatString(ReadOnlySpan<char> icuFormatStr
232232

233233
for (int i = 0; i < icuFormatString.Length; i++)
234234
{
235-
switch (icuFormatString[i])
235+
char current = icuFormatString[i];
236+
switch (current)
236237
{
237238
case '\'':
238239
result[resultPos++] = icuFormatString[i++];
239240
while (i < icuFormatString.Length)
240241
{
241-
char current = icuFormatString[i];
242+
current = icuFormatString[i];
242243
result[resultPos++] = current;
243244
if (current == '\'')
244245
{
@@ -254,13 +255,10 @@ private static string ConvertIcuTimeFormatString(ReadOnlySpan<char> icuFormatStr
254255
case 'h':
255256
case 'm':
256257
case 's':
257-
result[resultPos++] = icuFormatString[i];
258-
break;
259-
260258
case ' ':
261-
case '\u00A0':
262-
// Convert nonbreaking spaces into regular spaces
263-
result[resultPos++] = ' ';
259+
case '\u00A0': // no-break space
260+
case '\u202F': // narrow no-break space
261+
result[resultPos++] = current;
264262
break;
265263

266264
case 'a': // AM/PM

src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeParse.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5571,7 +5571,7 @@ internal bool MatchSpecifiedWords(string target, bool checkWordBoundary, scoped
55715571
// Check word by word
55725572
int targetPosition = 0; // Where we are in the target string
55735573
int thisPosition = Index; // Where we are in this string
5574-
int wsIndex = target.AsSpan(targetPosition).IndexOfAny(' ', '\u00A0');
5574+
int wsIndex = target.AsSpan(targetPosition).IndexOfAny("\u0020\u00A0\u202F");
55755575
if (wsIndex < 0)
55765576
{
55775577
return false;
@@ -5615,7 +5615,7 @@ internal bool MatchSpecifiedWords(string target, bool checkWordBoundary, scoped
56155615
matchLength++;
56165616
}
56175617

5618-
wsIndex = target.AsSpan(targetPosition).IndexOfAny(' ', '\u00A0');
5618+
wsIndex = target.AsSpan(targetPosition).IndexOfAny("\u0020\u00A0\u202F");
56195619
if (wsIndex < 0)
56205620
{
56215621
break;
@@ -5678,7 +5678,8 @@ internal bool Match(char ch)
56785678
{
56795679
return false;
56805680
}
5681-
if (Value[Index] == ch)
5681+
if ((Value[Index] == ch) ||
5682+
(ch == ' ' && IsSpaceReplacingChar(Value[Index])))
56825683
{
56835684
m_current = ch;
56845685
return true;
@@ -5687,6 +5688,8 @@ internal bool Match(char ch)
56875688
return false;
56885689
}
56895690

5691+
private static bool IsSpaceReplacingChar(char c) => c == '\u00a0' || c == '\u202f';
5692+
56905693
//
56915694
// Actions: From the current position, try matching the longest word in the specified string array.
56925695
// E.g. words[] = {"AB", "ABC", "ABCD"}, if the current position points to a substring like "ABC DEF",

src/libraries/System.Runtime/tests/System/DateTimeTests.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2008,6 +2008,57 @@ public static void Parse_ValidInput_Succeeds(string input, CultureInfo culture,
20082008
Assert.Equal(expected, DateTime.Parse(input, culture));
20092009
}
20102010

2011+
public static IEnumerable<object[]> FormatAndParse_DifferentUnicodeSpaces_Succeeds_MemberData()
2012+
{
2013+
char[] spaceTypes = new[] { ' ', // space
2014+
'\u00A0', // no-break space
2015+
'\u202F', // narrow no-break space
2016+
};
2017+
return spaceTypes.SelectMany(formatSpaceChar => spaceTypes.Select(parseSpaceChar => new object[] { formatSpaceChar, parseSpaceChar }));
2018+
}
2019+
2020+
[Theory]
2021+
[MemberData(nameof(FormatAndParse_DifferentUnicodeSpaces_Succeeds_MemberData))]
2022+
public void FormatAndParse_DifferentUnicodeSpaces_Succeeds(char formatSpaceChar, char parseSpaceChar)
2023+
{
2024+
var dateTime = new DateTime(2020, 5, 7, 9, 37, 40, DateTimeKind.Local);
2025+
2026+
DateTimeFormatInfo formatDtfi = CreateDateTimeFormatInfo(formatSpaceChar);
2027+
string formatted = dateTime.ToString(formatDtfi);
2028+
Assert.Contains(formatSpaceChar, formatted);
2029+
2030+
DateTimeFormatInfo parseDtfi = CreateDateTimeFormatInfo(parseSpaceChar);
2031+
Assert.Equal(dateTime, DateTime.Parse(formatted, parseDtfi));
2032+
2033+
static DateTimeFormatInfo CreateDateTimeFormatInfo(char spaceChar)
2034+
{
2035+
return new DateTimeFormatInfo()
2036+
{
2037+
Calendar = DateTimeFormatInfo.InvariantInfo.Calendar,
2038+
CalendarWeekRule = DateTimeFormatInfo.InvariantInfo.CalendarWeekRule,
2039+
FirstDayOfWeek = DayOfWeek.Monday,
2040+
AMDesignator = "AM",
2041+
DateSeparator = "/",
2042+
FullDateTimePattern = $"dddd,{spaceChar}MMMM{spaceChar}d,{spaceChar}yyyy{spaceChar}h:mm:ss{spaceChar}tt",
2043+
LongDatePattern = $"dddd,{spaceChar}MMMM{spaceChar}d,{spaceChar}yyyy",
2044+
LongTimePattern = $"h:mm:ss{spaceChar}tt",
2045+
MonthDayPattern = "MMMM d",
2046+
PMDesignator = "PM",
2047+
ShortDatePattern = "M/d/yyyy",
2048+
ShortTimePattern = $"h:mm{spaceChar}tt",
2049+
TimeSeparator = ":",
2050+
YearMonthPattern = $"MMMM{spaceChar}yyyy",
2051+
AbbreviatedDayNames = new[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" },
2052+
ShortestDayNames = new[] { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" },
2053+
DayNames = new[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" },
2054+
AbbreviatedMonthNames = new[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" },
2055+
MonthNames = new[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" },
2056+
AbbreviatedMonthGenitiveNames = new[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" },
2057+
MonthGenitiveNames = new[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" }
2058+
};
2059+
}
2060+
}
2061+
20112062
public static IEnumerable<object[]> ParseExact_ValidInput_Succeeds_MemberData()
20122063
{
20132064
foreach (DateTimeStyles style in new[] { DateTimeStyles.None, DateTimeStyles.AllowWhiteSpaces })

0 commit comments

Comments
 (0)