Skip to content

Space before AM/PM in the en-US locale changed from regular (0x0020) to non-breaking (0x202f) #102220

@glorious-beard

Description

@glorious-beard

Description

In the string produced by string.Format() using either the short time pattern "t" or long time pattern "T" in en-US (CultureInfo.GetCultureInfo("en-US")), the regular space between the numeric portion of the time and AM/PM portion changed from a regular space (\u0020) to a non-breaking space (\u202f). This breaks any existing code expecting a regular space.

Reproduction Steps

xUnit test demonstrating issue:

using System.Globalization;
using Xunit.Abstractions;

namespace SpaceInTimeTests;

public class UnitTest1
{
    private readonly ITestOutputHelper _testOutputHelper;

    public UnitTest1(ITestOutputHelper testOutputHelper)
    {
        _testOutputHelper = testOutputHelper;
    }

    [Theory]
    [InlineData("2009-06-15T13:45:30", "en-US", "t", "1:45 PM")]
    [InlineData("2009-06-15T13:45:30", "hr-HR", "t", "13:45")]
    [InlineData("2009-06-15T13:45:30", "en-US", "T", "1:45:30 PM")]
    [InlineData("2009-06-15T13:45:30", "hr-HR", "T", "13:45:30")]
    public void Test1(string timestamp, string locale, string formatStyle, string expected)
    {
        if (!DateTimeOffset.TryParse(timestamp, out var dateTime))
        {
            throw new ArgumentException("Failed to parse {0}", timestamp);
        }
        var actual = string.Format(CultureInfo.GetCultureInfo(locale), $"{{0:{formatStyle}}}", dateTime);
        
        _testOutputHelper.WriteLine("EXPECTED '{0}' => {1}", expected, string.Join('|', expected.ToCharArray().Select(x => $"0x{(int)x:x4}")));
        _testOutputHelper.WriteLine("ACTUAL   '{0}' => {1}", actual, string.Join('|', actual.ToCharArray().Select(x => $"0x{(int)x:x4}")));
        Assert.Equal(expected, actual);
    }
}

Cases using the "en-US" locale fail with the following error:

Assert.Equal() Failure: Strings differ
                  ↓ (pos 7)
Expected: "1:45:30 PM"
Actual:   "1:45:30 PM"
                  ↑ (pos 7)
   at SpaceInTimeTests.UnitTest1.Test1(String timestamp, String locale, String formatStyle, String expected) in /Users/chetan/projects/SpaceInTimeTests/UnitTest1.cs:line 30
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)



EXPECTED '1:45:30 PM' => 0x0031|0x003a|0x0034|0x0035|0x003a|0x0033|0x0030|0x0020|0x0050|0x004d
ACTUAL   '1:45:30 PM' => 0x0031|0x003a|0x0034|0x0035|0x003a|0x0033|0x0030|0x202f|0x0050|0x004d

Expected behavior

Regular spaces (\u0020) between numeric and AM/PM portions of the timestamp. (see "expected" from the output in "Reproduction Steps").

Actual behavior

See "Actual" output in "Reproduction Steps"

Regression?

Yes, it worked prior to 8.0.204. I'm seeing this happening in 8.0.204 and 8.0.300.

Known Workarounds

Disabling unit tests

Configuration

  • .NET version: 8.0.300 (and 8.0.204)
  • OS: MacOS 14.4 (Sonoma) (ARM64), Linux Ubuntu 20.04 (x64), whatever OS linux .NET ASP.Net containers use (x64)

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions