Skip to content

Commit

Permalink
ConvertIcuTimeFormatString: convert narrow no-break spaces to spaces …
Browse files Browse the repository at this point in the history
…too. (#83589)
  • Loading branch information
tmds authored Apr 4, 2023
1 parent e935780 commit b54d6ef
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,14 @@ private static string ConvertIcuTimeFormatString(ReadOnlySpan<char> icuFormatStr

for (int i = 0; i < icuFormatString.Length; i++)
{
switch (icuFormatString[i])
char current = icuFormatString[i];
switch (current)
{
case '\'':
result[resultPos++] = icuFormatString[i++];
while (i < icuFormatString.Length)
{
char current = icuFormatString[i];
current = icuFormatString[i];
result[resultPos++] = current;
if (current == '\'')
{
Expand All @@ -254,13 +255,10 @@ private static string ConvertIcuTimeFormatString(ReadOnlySpan<char> icuFormatStr
case 'h':
case 'm':
case 's':
result[resultPos++] = icuFormatString[i];
break;

case ' ':
case '\u00A0':
// Convert nonbreaking spaces into regular spaces
result[resultPos++] = ' ';
case '\u00A0': // no-break space
case '\u202F': // narrow no-break space
result[resultPos++] = current;
break;

case 'a': // AM/PM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5571,7 +5571,7 @@ internal bool MatchSpecifiedWords(string target, bool checkWordBoundary, scoped
// Check word by word
int targetPosition = 0; // Where we are in the target string
int thisPosition = Index; // Where we are in this string
int wsIndex = target.AsSpan(targetPosition).IndexOfAny(' ', '\u00A0');
int wsIndex = target.AsSpan(targetPosition).IndexOfAny("\u0020\u00A0\u202F");
if (wsIndex < 0)
{
return false;
Expand Down Expand Up @@ -5615,7 +5615,7 @@ internal bool MatchSpecifiedWords(string target, bool checkWordBoundary, scoped
matchLength++;
}

wsIndex = target.AsSpan(targetPosition).IndexOfAny(' ', '\u00A0');
wsIndex = target.AsSpan(targetPosition).IndexOfAny("\u0020\u00A0\u202F");
if (wsIndex < 0)
{
break;
Expand Down Expand Up @@ -5678,7 +5678,8 @@ internal bool Match(char ch)
{
return false;
}
if (Value[Index] == ch)
if ((Value[Index] == ch) ||
(ch == ' ' && IsSpaceReplacingChar(Value[Index])))
{
m_current = ch;
return true;
Expand All @@ -5687,6 +5688,8 @@ internal bool Match(char ch)
return false;
}

private static bool IsSpaceReplacingChar(char c) => c == '\u00a0' || c == '\u202f';

//
// Actions: From the current position, try matching the longest word in the specified string array.
// E.g. words[] = {"AB", "ABC", "ABCD"}, if the current position points to a substring like "ABC DEF",
Expand Down
51 changes: 51 additions & 0 deletions src/libraries/System.Runtime/tests/System/DateTimeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2008,6 +2008,57 @@ public static void Parse_ValidInput_Succeeds(string input, CultureInfo culture,
Assert.Equal(expected, DateTime.Parse(input, culture));
}

public static IEnumerable<object[]> FormatAndParse_DifferentUnicodeSpaces_Succeeds_MemberData()
{
char[] spaceTypes = new[] { ' ', // space
'\u00A0', // no-break space
'\u202F', // narrow no-break space
};
return spaceTypes.SelectMany(formatSpaceChar => spaceTypes.Select(parseSpaceChar => new object[] { formatSpaceChar, parseSpaceChar }));
}

[Theory]
[MemberData(nameof(FormatAndParse_DifferentUnicodeSpaces_Succeeds_MemberData))]
public void FormatAndParse_DifferentUnicodeSpaces_Succeeds(char formatSpaceChar, char parseSpaceChar)
{
var dateTime = new DateTime(2020, 5, 7, 9, 37, 40, DateTimeKind.Local);

DateTimeFormatInfo formatDtfi = CreateDateTimeFormatInfo(formatSpaceChar);
string formatted = dateTime.ToString(formatDtfi);
Assert.Contains(formatSpaceChar, formatted);

DateTimeFormatInfo parseDtfi = CreateDateTimeFormatInfo(parseSpaceChar);
Assert.Equal(dateTime, DateTime.Parse(formatted, parseDtfi));

static DateTimeFormatInfo CreateDateTimeFormatInfo(char spaceChar)
{
return new DateTimeFormatInfo()
{
Calendar = DateTimeFormatInfo.InvariantInfo.Calendar,
CalendarWeekRule = DateTimeFormatInfo.InvariantInfo.CalendarWeekRule,
FirstDayOfWeek = DayOfWeek.Monday,
AMDesignator = "AM",
DateSeparator = "/",
FullDateTimePattern = $"dddd,{spaceChar}MMMM{spaceChar}d,{spaceChar}yyyy{spaceChar}h:mm:ss{spaceChar}tt",
LongDatePattern = $"dddd,{spaceChar}MMMM{spaceChar}d,{spaceChar}yyyy",
LongTimePattern = $"h:mm:ss{spaceChar}tt",
MonthDayPattern = "MMMM d",
PMDesignator = "PM",
ShortDatePattern = "M/d/yyyy",
ShortTimePattern = $"h:mm{spaceChar}tt",
TimeSeparator = ":",
YearMonthPattern = $"MMMM{spaceChar}yyyy",
AbbreviatedDayNames = new[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" },
ShortestDayNames = new[] { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" },
DayNames = new[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" },
AbbreviatedMonthNames = new[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" },
MonthNames = new[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" },
AbbreviatedMonthGenitiveNames = new[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" },
MonthGenitiveNames = new[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" }
};
}
}

public static IEnumerable<object[]> ParseExact_ValidInput_Succeeds_MemberData()
{
foreach (DateTimeStyles style in new[] { DateTimeStyles.None, DateTimeStyles.AllowWhiteSpaces })
Expand Down

0 comments on commit b54d6ef

Please sign in to comment.