0

I've got a list of ISO 3166 two-letter country codes acquired from an external source. For each, I create

     new System.Globalization.RegionInfo(countryCode) 

and occasionally one is invalid resulting in an ArgumentException "Culture name 'xx' is not supported."

I want a function to determine if the country code is valid before I pass it into the constructor. This is my attempt:

    private bool IsCultureValid(string cultureName)
    {
        return CultureInfo.GetCultures(CultureTypes.AllCultures)
            .Any(c => c.Name.Equals(cultureName, StringComparison.InvariantCultureIgnoreCase));
    }

The function returns a false negative for many inputs (function returns false, but I can create a RegionInfo object with that input if I try). Some inputs:

  • zw (Zimbabwe)
  • au (Australia)
  • mx (Mexico)
  • ve (Bolivarian Republic of Venezuela)
  • hn (Honduras)
  • kw (Kuwait)

What am I missing? Is there a better approach here? Thanks in advance!

Kenn
  • 2,379
  • 2
  • 29
  • 38

4 Answers4

5

I realize this is a dated question. However, I recently ran across a similar situation where I needed to validate incoming ISO currency codes. All of the examples I could find here and elsewhere relied on catching the exception that was thrown when attempting to create a region or culture with an invalid code/id. Which is just not a good practice.

My own research into the problem led me to realize that for the most part, the problem was the invariant culture and neutral cultures. Once they are removed from the CultureInfo array it is possible to generate a list of only valid RegionInfo objects.

This is an extrapolation from my own issue to provide the requested answer. Though obviously a variation of this could be applied anywhere you need just the valid RegionInfo objects.

private bool IsValidRegion(string isoCountryCode)
    {
        return CultureInfo.GetCultures(CultureTypes.AllCultures)
            .Where(x => !x.Equals(CultureInfo.InvariantCulture)) //Remove the invariant culture as a region cannot be created from it.
            .Where(x => !x.IsNeutralCulture) //Remove nuetral cultures as a region cannot be created from them.
            .Select(x => new RegionInfo(x.LCID))
            .Any(x => x.Name.Equals(isoCountryCode, StringComparison.InvariantCulture));
    }

Edit: Unless using custom cultures this can actually be done even more directly. Simply use the "CultureTypes.SpecificCultures" enum value.

Shawn Wheeler
  • 201
  • 2
  • 6
  • Watch out, this won't get you a list of ALL regions. I'm still trying to find how one might do that, but as of right now, you can't get a valid region for WF from this method. (try this PowerShell script: [System.Globalization.RegionInfo]::new("WF")) – Dave Markle Mar 06 '19 at 18:45
1

You could write a function that creates the specific culture inside a try/catch block and return the CultureInfo object instead of bool.

By the way, there is no such culture as ve, it's es-VE and so on for Mexico, Honduras.. Culture info for "derived" cultures must have parent culture code before. en-AU, en-US, and so on

http://www.localeplanet.com/dotnet/es-VE/index.html

To get a list of all correct values of installed cultures, use:

CultureInfo.GetCultures();

https://msdn.microsoft.com/en-us/library/system.globalization.cultureinfo.getcultures(v=vs.110).aspx

Oscar
  • 13,594
  • 8
  • 47
  • 75
  • This would work. But it uses exceptions for the expected flow of logic which is a terrible practice and terrible for performance of a real-time system (required here) – Kenn Jun 12 '15 at 17:57
  • @Kenn I agree, but there's no other way when you must relay in an external list of data to create specific cultures with wrong codes. You can't simply assume they're right. Are you thinking in a better solution? – Oscar Jun 12 '15 at 18:00
  • Just seems the gap here is that I'm using CultureInfo to get a list of RegionInfo inputs. Can't help but think that I'm missing an obvious solution in another part of the CLR? Worst case I can do the try/catch and cache the result, I suppose. – Kenn Jun 12 '15 at 18:09
  • @Kenn Your problem is that input list is wrong. You should use es-VE instead of only ve for Venezuela, as explained before. – Oscar Jun 12 '15 at 18:11
  • https://msdn.microsoft.com/en-us/library/atwc2921(v=vs.110).aspx Input is "A string that contains a two-letter code defined in ISO 3166 for country/region." The input is coming from a third party in this scenario, I can't control it. The two letter format should be fine. – Kenn Jun 16 '15 at 07:45
  • Haven't found anything better than this. Bummer. Used a try{}catch with a cache. – Kenn Jun 17 '15 at 01:30
0

This is a comment-like post, not necessarily a full answer.

As in other answers, region infos can be created from culture infos. As an example, on my current system, the code:

var comp = Comparer<RegionInfo>.Create((x, y) => string.Compare(x.Name, y.Name));
var count = 0;
var set = new SortedSet<RegionInfo>(comp);
foreach (var sci in CultureInfo.GetCultures(CultureTypes.SpecificCultures)) {
    set.Add(new RegionInfo(sci.LCID));
    ++count;
}

runs through 574 specific cultures but creates only 142 different regions (and a region may possess several languages, so this is not unusual).

But (on my system at least) there are regions that do not arise from a culture.

For example, on my system, if I do:

var comp = Comparer<RegionInfo>.Create((x, y) => string.Compare(x.Name, y.Name));
var set2 = new SortedSet<RegionInfo>(comp);
for (var i = 0; i < 1000; ++i) {
    try {
        set2.Add(new RegionInfo($"{i:D3}"));
    } catch (ArgumentException) {
    }
}
for (var a = 'A'; a <= 'Z'; ++a) {
    for (var b = 'A'; b <= 'Z'; ++b) {
        try {
            set2.Add(new RegionInfo($"{a}{b}"));
        } catch (ArgumentException) {
        }
    }
}

then I get 250 distinct RegionInfo. So there seem to be more than 100 RegionInfo that do not arise from a CultureInfo.

Also note that even though

new RegionInfo(CultureInfo.InvariantCulture.LCID)

throws an exception There is no region associated with the Invariant Culture (Culture ID: 0x7F), it is still possible (again, on my system right now) to do:

new RegionInfo("IV")

which creates an instance whose EnglishName is Invariant Country.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • 1
    A warning here that the LCID is LOCALE_CUSTOM_UNSPECIFIED (4096) for many African cultures, which won't generate a useful RegionInfo from the constructor. If you use the CultureInfo.Name and the other RegionInfo constructor you will get a more complete RegionInfo list. Just had a bug due to this. – MZB Aug 31 '23 at 14:39
-1

You are getting false because they don't exist. Here is the list of all the cultures obtained with the following loop:

foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.AllCultures))
{
    ci.Name
}

ar,bg,ca,zh-Hans,cs,da,de,el,en,es,fi,fr,he,hu,is,it,ja,ko,nl,no,pl,pt,rm,ro,ru,hr,sk,sq,sv,th,tr,ur,id,uk,be,sl,et,lv,lt,tg,fa,vi,hy,az,eu,hsb,mk,tn,xh,zu,af,ka,fo,hi,mt,se,ga,ms,kk,ky,sw,tk,uz,tt,bn,pa,gu,or,ta,te,kn,ml,as,mr,sa,mn,bo,cy,km,lo,gl,kok,syr,si,iu,am,tzm,ne,fy,ps,fil,dv,ha,yo,quz,nso,ba,lb,kl,ig,ii,arn,moh,br,,ug,mi,oc,co,gsw,sah,qut,rw,wo,prs,gd,ar-SA,bg-BG,ca-ES,zh-TW,cs-CZ,da-DK,de-DE,el-GR,en-US,fi-FI,fr-FR,he-IL,hu-HU,is-IS,it-IT,ja-JP,ko-KR,nl-NL,nb-NO,pl-PL,pt-BR,rm-CH,ro-RO,ru-RU,hr-HR,sk-SK,sq-AL,sv-SE,th-TH,tr-TR,ur-PK,id-ID,uk-UA,be-BY,sl-SI,et-EE,lv-LV,lt-LT,tg-Cyrl-TJ,fa-IR,vi-VN,hy-AM,az-Latn-AZ,eu-ES,hsb-DE,mk-MK,tn-ZA,xh-ZA,zu-ZA,af-ZA,ka-GE,fo-FO,hi-IN,mt-MT,se-NO,ms-MY,kk-KZ,ky-KG,sw-KE,tk-TM,uz-Latn-UZ,tt-RU,bn-IN,pa-IN,gu-IN,or-IN,ta-IN,te-IN,kn-IN,ml-IN,as-IN,mr-IN,sa-IN,mn-MN,bo-CN,cy-GB,km-KH,lo-LA,gl-ES,kok-IN,syr-SY,si-LK,iu-Cans-CA,am-ET,ne-NP,fy-NL,ps-AF,fil-PH,dv-MV,ha-Latn-NG,yo-NG,quz-BO,nso-ZA,ba-RU,lb-LU,kl-GL,ig-NG,ii-CN,arn-CL,moh-CA,br-FR,ug-CN,mi-NZ,oc-FR,co-FR,gsw-FR,sah-RU,qut-GT,rw-RW,wo-SN,prs-AF,gd-GB,ar-IQ,zh-CN,de-CH,en-GB,es-MX,fr-BE,it-CH,nl-BE,nn-NO,pt-PT,sr-Latn-CS,sv-FI,az-Cyrl-AZ,dsb-DE,se-SE,ga-IE,ms-BN,uz-Cyrl-UZ,bn-BD,mn-Mong-CN,iu-Latn-CA,tzm-Latn-DZ,quz-EC,ar-EG,zh-HK,de-AT,en-AU,es-ES,fr-CA,sr-Cyrl-CS,se-FI,quz-PE,ar-LY,zh-SG,de-LU,en-CA,es-GT,fr-CH,hr-BA,smj-NO,ar-DZ,zh-MO,de-LI,en-NZ,es-CR,fr-LU,bs-Latn-BA,smj-SE,ar-MA,en-IE,es-PA,fr-MC,sr-Latn-BA,sma-NO,ar-TN,en-ZA,es-DO,sr-Cyrl-BA,sma-SE,ar-OM,en-JM,es-VE,bs-Cyrl-BA,sms-FI,ar-YE,en-029,es-CO,sr-Latn-RS,smn-FI,ar-SY,en-BZ,es-PE,sr-Cyrl-RS,ar-JO,en-TT,es-AR,sr-Latn-ME,ar-LB,en-ZW,es-EC,sr-Cyrl-ME,ar-KW,en-PH,es-CL,ar-AE,es-UY,ar-BH,es-PY,ar-QA,en-IN,es-BO,en-MY,es-SV,en-SG,es-HN,es-NI,es-PR,es-US,bs-Cyrl,bs-Latn,sr-Cyrl,sr-Latn,smn,az-Cyrl,sms,zh,nn,bs,az-Latn,sma,uz-Cyrl,mn-Cyrl,iu-Cans,zh-Hant,nb,sr,tg-Cyrl,dsb,smj,uz-Latn,mn-Mong,iu-Latn,tzm-Latn,ha-Latn,zh-CHS,zh-CHT

So you can see it does not contain zw, but it has en-ZW similarly for au it has en-AU

Noman Khan
  • 92
  • 5
  • I think you misunderstood the question. The function returns a "false negative" not simply the "function returns false". So the problem is that there are valid inputs to RegionInfo which are missing from the list you provided. There doesn't seem to be a way to obtain a complete list of two-letter ISO 3166 for country/regions which are accepted by RegionInfo. – Kenn Jun 16 '15 at 07:48