Home > database >  How to force .Net ToString to use given Globalization / CultureInfo
How to force .Net ToString to use given Globalization / CultureInfo

Time:12-23

I'm working with a piece of .Net Core 6 library (dll), in which, amongst other functionality, numbers and date/time information is outputted as strings and this code is required to be globalized ie. to be culture-aware. The library is for a document producing system, so in the same application session, users would produce documents for de-CH, de-FR, en-US etc. So basically, We would like to have the library culture-aware, but the culture outputted should have it's "standard" formats, not the ones customised in Operating system level or user level.

For example for decimal values, the library code is using these methods to output stuff:

public string ToString();
public string ToString(string? format);
public string ToString(IFormatProvider? provider)
public string ToString(string? format, IFormatProvider? provider)

So calling these overloads by different users in different computers having different culture-settings, will format the output differently: decimal separator can be comma or point and the thousands grouping delimiter might be non-braking-space or ’ etc.

However, when provider argument is passed explicitly by using an instance of a specific culture for example "fr-CH" (Swiss-French) and with CultureInfo.UseUserOverride == false, I was expecting always the same output string, independent of user and/or computer. So, I was thinking, that .Net would have "built-in" values for all the properties of CultureInfo-instances for all culture-types and with argument useUserOverride: false I could force the formatting to use these "Built-in" values and hence would output always the same string.

But this seems not be the case!

The code:

CultureInfo cultInfo = new CultureInfo(name: "fr-CH", useUserOverride: false)       //* fr-CH" is Swiss-French
Decimal myDec = -123456789123456.987654321M; 
String output = myDec.ToString(format: "N4", provider: cultInfo);       // I was expecting always same output independent, which user is calling it in which computer, but this is not the case!

This example code was compiled with .Net Core 6 as Console application and I run it on a) a Windows 10 Pro PC and b) on a Windows Server 2012R2 and they had having different outputs.

  • Fr-CH/Windows Pro 10 PC: -123 456 789 123 456,9877
  • Fr-CH/Windows Server 2012R2: -123'456'789'123'456.9877

The same by setting CultureInfo cultInfo = CultureInfo.InvariantCulture: Then the result look always the same, which is expected.

I also tried setting Thread.CurrentThread.CurrentCulture and Thread.CurrentThread.CurrentUICulture using the cultInfo-variable the above and then calling ToString(format?, provider?), but there was no effect: the results were exactly like without setting the CurrentThread. PS. I actually learned from here, here and here that setting Thread.CurrentThread.CurrentUICulture has nothing to do with formatting (my case), but with translation (resx stuff).

I'm aware of this vaste piece of Microsoft documentation about CultureInfo-class, but I cannot see the tree from the forest there.

My questions are as follows:

  • Has any version of .Net Core (or any version .Net Framework) a "built-in values" for all CultureInfo-properties at all?
  • How could I force my code to use always exactly the culture given culture independent of the environment?

This would be very important in order to be able to unit-test the library output in a reliable way.

Likely important this would be, when reading back (utilizing System.Convert(value, provider?) the values outputted by any user in any computer (just knowing the culture they were outputted).

CodePudding user response:

You can set up a custom culture and configure that one according to your needs.

Start from an existing neutral one, like the InvariantCulture.
Make a clone in order to keep that InvariantCulture as-is.

var clone = CultureInfo.InvariantCulture.Clone() as CultureInfo;

Then make the required changes upon that clone - e.g.:

clone.NumberFormat.NumberDecimalSeparator = ",";
clone.NumberFormat.NumberGroupSeparator = ".";

Optionally but advisable, you might want to make a readonly version of it.

var customCulture = CultureInfo.ReadOnly(clone);

Use that culture wherever you need a fixed controlled output - e.g.:

var number = -123456789123456.987654321M;
var formattedNumber = number.ToString("N4", customCulture)); // -123.456.789.123.456,9877

Apply that same culture when parsing the formatted string version back to a decimal - e.g.:

var parsedNumber = decimal.Parse(formattedNumber, customCulture);

If you are interested in how a CultureInfo is set up have a look at the source code.
In short the InvariantCulture is one with fixed settings, whereas the others rely on the operating system and optional user overrides.

From the documentation:

.NET derives its cultural data from a one of a variety of sources, depending on implementation, platform, and version:

  • In .NET Framework 3.5 and earlier versions, cultural data is provided by both the Windows operating system and .NET Framework.

  • In .NET Framework 4 and later versions, cultural data is provided by the Windows operating system.

  • In all versions of .NET Core running on Windows, cultural data is provided by the Windows operating system.

Because of this, a culture available on a particular .NET implementation, platform, or version may not be available on a different .NET implementation, platform, or version.

Some CultureInfo objects differ depending on the underlying platform.

CodePudding user response:

Set it on the thead for it to work and if you don't want it to repeat in every place:

CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("fr-CH");
.....
Decimal myDec = -123456789123456.987654321M;
String output = myDec.ToString(format: "N4");
Console.WriteLine(output);

when you run the code on other computers, try to check the cultureinfo and see what it will return. I bet it is not setting it to fr-CH:

CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture;
        Console.WriteLine(currentCulture.Name);
  • Related