根据用户的区域设置很难格式化 DotNet 程序中的数字

根据用户的区域设置很难格式化 DotNet 程序中的数字

我已获得一台服务器来托管我的 DotNet 7 应用程序,但在显示根据用户区域设置格式化的数字时遇到问题。

操作系统是:

$ cat os-release
NAME="Red Hat Enterprise Linux"
VERSION="8.7 (Ootpa)"
ID="rhel"
ID_LIKE="fedora"
VERSION_ID="8.7"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Red Hat Enterprise Linux 8.7 (Ootpa)"
...

我的区域设置是:

$ locale
LANG=en_ZA.UTF-8
LC_CTYPE="en_ZA.UTF-8"
LC_NUMERIC="en_ZA.UTF-8"
LC_TIME="en_ZA.UTF-8"
LC_COLLATE="en_ZA.UTF-8"
LC_MONETARY="en_ZA.UTF-8"
LC_MESSAGES="en_ZA.UTF-8"
...

数字设置为:

$ locale -k LC_NUMERIC
decimal_point="."
thousands_sep=","
grouping=3;3
numeric-decimal-point-wc=46
numeric-thousands-sep-wc=44
numeric-codeset="UTF-8"

所以我希望像123456789.9876格式化为区域设置的数字格式的数字能够像123,456,789.99.

如果我创建最简单的程序:

static void Main(string[] args) {
    const decimal d = 123456789.9876M;
    Console.WriteLine($"{d:n}");
}

dotnet run,它打印123 456 789,988

我相信 DotNet 使用Unicode (ICU) 的内部组件,但这是任何 DotNet 应用程序运行的要求,并且似乎已安装。

$ dnf list installed \*libicu\*
libicu.x86_64    60.3-2.el8_1    @rhel-8-for-x86_64-baseos-rpms

我无法强制DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true(例如在项目文件中),因为我的应用程序需要不允许不变文化的 SQL Server。

同一应用程序在我的 WSL/Ubuntu 实例以及我尝试过的所有 Windows 实例上以预期格式打印数字。

我试过了包括特定版本的 libicu与应用程序,但它不影响格式。

为了帮助应用程序开发人员确保所有部署的一致性,.NET 5 及更高版本允许 Windows 和 Unix 上的应用程序携带和使用自己的 ICU 副本。

<PackageReference Include="Microsoft.ICU.ICU4C.Runtime" Version="68.2.0.9" />
<RuntimeHostConfigurationOption Include="System.Globalization.AppLocalIcu" Value="68.2.0.9" />

服务器的所有者具有安全意识,用户拥有必要的最低权限。

有谁知道如何让我的应用程序使用配置的区域设置?


从简单的测试程序输出更多信息,似乎区域设置是正确的,但使用的 LC_NUMERIC/LC_MONETARY 设置与上面显示的不同。

The current culture is en-ZA
The decimal separator is ","
The group separator is " "
The decimal digits is "3"
Number formatted: 123 456 789,988
The currency symbol is "R"
The currency decimal separator is ","
The currency group separator is " "
The currency decimal digits is "2"
Currency formatted: R123 456 789,99

作为适合我的情况的解决方法,我能够以编程方式强制执行特定于文化的设置。

public static void SetCustomZAThreadCulture(bool linuxOnly = true) {
    if (!linuxOnly || CrossPlatform.IsLinux()) {
        CultureInfo customCultureInfo = (CultureInfo)Thread.CurrentThread.CurrentCulture.Clone();
        customCultureInfo.NumberFormat = new NumberFormatInfo {
            NumberDecimalSeparator = ".",
            NumberGroupSeparator = ",",
            NumberDecimalDigits = 2,
            CurrencySymbol = "R",
            CurrencyDecimalSeparator = ".",
            CurrencyGroupSeparator = ",",
            CurrencyDecimalDigits = 2,
        };
        Thread.CurrentThread.CurrentCulture = customCultureInfo;
        CultureInfo.DefaultThreadCurrentCulture = customCultureInfo;
    }
}

相关内容