TOC
Working with Culture & Regions:

The CultureInfo class

In the last couple of articles, we have talked about how useful the CultureInfo class is when you need full control of how numbers and dates are displayed in your application. We have also talked about how you can verify and modify which culture your application should use as a fallback. With all that in place, it's time to dig deeper into the actual CultureInfo class, to see how we can take full advantage of it.

A quick reminder before we get started: The CultureInfo class is part of the System.Globalization namespace, so be sure to import that whenever you try the examples:

using System.Globalization;

Neutral and specific cultures

In the previous examples in this chapter, we have only used specific cultures, which is a culture that specifies both a language and a country/region. An example of this is the en-US culture, which clearly states that the desired language should be English and the region is the US. An alternative to that is the en-GB culture, which is the same language (English) but with Great Britain as the region instead of the US.

There will be times when these differences are important to you, in which case you should use these region-specific versions of the CultureInfo class. On the other hand, there will also be situations where English is just a language and you don't want to tie this language to a specific country or region. For this, the .NET framework defines so-called neutral cultures, which only specifies a language. In fact, both en-US and en-GB inherit from such a neutral culture (which makes perfect sense, since they share the same language!) and you can access it from the Parent property. Let me illustrate with an example:

CultureInfo enGb = new CultureInfo("en-GB");
CultureInfo enUs = new CultureInfo("en-US");
Console.WriteLine(enGb.DisplayName);
Console.WriteLine(enUs.DisplayName);
Console.WriteLine(enGb.Parent.DisplayName);
Console.WriteLine(enUs.Parent.DisplayName);

Not a terribly useful example, but it should give you a better idea of the internal structure of the CultureInfo class. The output should look like this:

English (United Kingdom)
English (United States)
English
English

Getting the right CultureInfo

From our previous examples, you have seen that we can fetch the desired CultureInfo class by passing the language-country/region identifier to the constructor of the class. But since you might be looking for a neutral culture, as described above, you could also just pass in a language identifier:

CultureInfo en = new CultureInfo("en");

The .NET framework will then return an English, region-neutral CultureInfo instance for you. For a full list of possible language and/or language-country/region identifiers, I suggest you check out the MSDN documentation.

Another way of identifying a specific culture is with the so-called LCID (LoCale ID). You will find it as a property on existing CultureInfo instances, but if you know the ID, you can also use it to instantiate a CultureInfo object. For instance, the LCID for en-US is 1033:

CultureInfo enUs = new CultureInfo(1033);

However, in most situations, it's much easier to use the language-country/region specifier, as previously demonstrated.

Getting a list of available Cultures

We can now get a specific culture and use it for various purposes, but perhaps you need a list of the available cultures, e.g. to allow the user to select a language and/or country/region. Fortunately, the .NET framework makes that easy for us as well - here's an example:

CultureInfo[] specificCultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
foreach (CultureInfo ci in specificCultures)
    Console.WriteLine(ci.DisplayName);
Console.WriteLine("Total: " + specificCultures.Length);

As you can tell from the first line of code, I use the GetCultures static method on the CultureInfo class to get a list of cultures. It requires the CultureTypes parameter, which specifies which kind of cultures you're looking for. In this case, I have asked for the specific cultures, which, as we talked about previously, are the cultures which are tied to both a language and a country/region. That's quite a long list, by the way - on this computer, I get a total of 563 available cultures!

But perhaps you're more interested in the neutral cultures? That would, for instance, make perfect sense if you were building a list of available languages, while not caring which country or region they were related to. Doing it is as simple as changing the CultureTypes parameter:

CultureInfo[] neutralCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
foreach (CultureInfo ci in neutralCultures)
    Console.WriteLine(ci.DisplayName);
Console.WriteLine("Total: " + neutralCultures.Length);

By doing so, you will also see that there not quite as many neutral cultures as there are specific cultures - on my computer/.NET framework version, the result is a total of 280 neutral cultures.

Important properties & methods of CultureInfo

Once you have an instance of the CultureInfo class, you immediately get access to a very wide range of usable properties and methods. These members can help you accomplish many useful things in regards to culture - let's have a look at some of them!

DateTimeFormat

With the DateTimeFormat property, you get access to information about how date and time should be formatted, as well as a lot of useful information about the calendar for the given culture. A nice example of this is the FirstDayOfWeek and CalendarWeekRule properties - they can tell you on which day a week starts (usually Sunday or Monday) and how the first calendar week of the year is decided (e.g. just the first day or the first full week):

CultureInfo enUs = new CultureInfo("en-US");
Console.WriteLine("First day of the: " + enUs.DateTimeFormat.FirstDayOfWeek.ToString());
Console.WriteLine("First calendar week starts with: " + enUs.DateTimeFormat.CalendarWeekRule.ToString());

Try changing the CultureInfo instance to your own culture or another culture you know, to see how these properties vary!

Another cool thing is that you can get information about the month and day names for the specific culture using properties like MonthNames and methods like GetMonthName(). Here's a quick example:

CultureInfo enUs = new CultureInfo("en-US");

foreach (string monthName in enUs.DateTimeFormat.MonthNames)
    Console.WriteLine(monthName);
Console.WriteLine("Current month: " + enUs.DateTimeFormat.GetMonthName(DateTime.Now.Month));

And the exact same thing can be accomplished for days, using the DayNames property and the GetDayName() method:

CultureInfo enUs = new CultureInfo("en-US");

foreach (string dayName in enUs.DateTimeFormat.DayNames)
    Console.WriteLine(dayName);
Console.WriteLine("Today is: " + enUs.DateTimeFormat.GetDayName(DateTime.Now.DayOfWeek));

There are many more useful properties and methods on the DateTimeFormat property, e.g. DateSeparator, YearMonthPattern and so on. Have a look for your self - there might very well be a solution to your date/time related problem hidden in there: DateTimeFormatInfo documentation.

NumberFormat

Just like the DateTimeFormat has information about dates, you can access information on how the specific culture treats numbers from the NumberFormat property. This information is used each time you ask for a visual representation of a number, e.g. when converting it to a string and writing it to the console, but you can also access the information yourself, by using the properties and methods on the NumberFormat property - here's an example:

CultureInfo enUs = new CultureInfo("en-US");  
Console.WriteLine(enUs.DisplayName + ":");  
Console.WriteLine("NumberGroupSeparator: " + enUs.NumberFormat.NumberGroupSeparator);  
Console.WriteLine("NumberDecimalSeparator: " + enUs.NumberFormat.NumberDecimalSeparator);  

CultureInfo deDe = new CultureInfo("de-DE");  
Console.WriteLine(deDe.DisplayName + ":");  
Console.WriteLine("NumberGroupSeparator: " + deDe.NumberFormat.NumberGroupSeparator);  
Console.WriteLine("NumberDecimalSeparator: " + deDe.NumberFormat.NumberDecimalSeparator);

We use the NumberGroupSeparator and the NumberDecimalSeparator properties to get information about how a number is displayed (e.g. 1,000.00 or 1.000,00) for the English and German cultures. If you have a look, you will find matching properties for currencies (CurrencyGroupSeparator and CurrencyDecimalSeparator) as well as percentages (PercentGroupSeparator and PercentDecimalSeparator).

Speaking of currency, the NumberFormat property can also tell you which symbol a given culture uses to display a monetary amount - simply use the CurrencySymbol property:

CultureInfo enUs = new CultureInfo("en-US");
Console.WriteLine(enUs.DisplayName + " - currency symbol: " + enUs.NumberFormat.CurrencySymbol);
CultureInfo deDe = new CultureInfo("de-DE");
Console.WriteLine(deDe.DisplayName + " - currency symbol: " + deDe.NumberFormat.CurrencySymbol);
CultureInfo ruRu = new CultureInfo("ru-RU");
Console.WriteLine(ruRu.DisplayName + " - currency symbol: " + ruRu.NumberFormat.CurrencySymbol);

All these properties are nice to know about, but in most situations, you won't have to deal with them, since C# will silently use the information to format numbers, percentages and currencies for you, as long as you specify the right format string when you turn the number into a string.

Names & identifiers

Lastly, let's have a look at the properties which represents the CultureInfo instance. We have already used some of them, e.g. Name and DisplayName, but how do they actually work? First, here's a list of available properties used to identify a CultureInfo:

  • Name will identify a CultureInfo in the languagecode-country/region-code format, e.g. "en-US" for English in the US, en-GB for English in Great Britain and so on. If no country/region is specified, only the first part will be returned, e.g. "en" for English.
  • TwoLetterISOLanguageName will do pretty much the same as Name, but it will only return the language code, no matter if a country/region has been specified or not. For instance, "en" will be returned for both "en-US" and "en-GB". The letters returned are specified in the ISO 639-1 standard.
  • ThreeLetterISOLanguageName works much like TwoLetterISOLanguageName, but it returns three letters instead of two, as specified by the ISO 639-2 standard.
  • EnglishName will return the name of the language (in English). If a country/region has been specified, this will be appended to the result, within a set of parentheses.
  • NativeName will return the name of the language (in the language specified by the CultureInfo instance). If a country/region has been specified, this will be appended to the result, within a set of parentheses.

Summary

As you can gather from the length of this article, dealing with culture in general is not a simple task. Fortunately for us, the .NET framework makes it a whole lot easier with the CultureInfo class. It's used silently all across your application when formatting numbers and dates, but it's good for you to know how it works so that you can modify the behavior if needed. Hopefully this article has taught you most of what you need to know about the CultureInfo class.