diff options
Diffstat (limited to 'src/mscorlib/corefx/System/Globalization/CultureInfo.cs')
-rw-r--r-- | src/mscorlib/corefx/System/Globalization/CultureInfo.cs | 528 |
1 files changed, 471 insertions, 57 deletions
diff --git a/src/mscorlib/corefx/System/Globalization/CultureInfo.cs b/src/mscorlib/corefx/System/Globalization/CultureInfo.cs index f2b3742ab4..da084d17f9 100644 --- a/src/mscorlib/corefx/System/Globalization/CultureInfo.cs +++ b/src/mscorlib/corefx/System/Globalization/CultureInfo.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -26,15 +26,10 @@ // //////////////////////////////////////////////////////////////////////////// -using System; -using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.Contracts; -using System.Runtime; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Serialization; -using System.Security; using System.Threading; namespace System.Globalization @@ -42,9 +37,12 @@ namespace System.Globalization #if INSIDE_CLR using StringCultureInfoDictionary = Dictionary<string, CultureInfo>; + using StringLcidDictionary = Dictionary<int, CultureInfo>; + using Lock = Object; #else using StringCultureInfoDictionary = LowLevelDictionary<string, CultureInfo>; + using StringLcidDictionary = LowLevelDictionary<int, CultureInfo>; #endif [Serializable] @@ -79,6 +77,9 @@ namespace System.Globalization [NonSerialized] internal bool m_isInherited; + [NonSerialized] + private CultureInfo m_consoleFallbackCulture; + // Names are confusing. Here are 3 names we have: // // new CultureInfo() m_name m_nonSortName m_sortName @@ -106,7 +107,6 @@ namespace System.Globalization [NonSerialized] private string m_sortName; - //--------------------------------------------------------------------// // // Static data members @@ -139,11 +139,21 @@ namespace System.Globalization private static readonly Lock m_lock = new Lock(); private static volatile StringCultureInfoDictionary s_NameCachedCultures; + private static volatile StringLcidDictionary s_LcidCachedCultures; //The parent culture. [NonSerialized] private CultureInfo m_parent; + // LOCALE constants of interest to us internally and privately for LCID functions + // (ie: avoid using these and use names if possible) + internal const int LOCALE_NEUTRAL = 0x0000; + private const int LOCALE_USER_DEFAULT = 0x0400; + private const int LOCALE_SYSTEM_DEFAULT = 0x0800; + internal const int LOCALE_CUSTOM_UNSPECIFIED = 0x1000; + internal const int LOCALE_CUSTOM_DEFAULT = 0x0c00; + internal const int LOCALE_INVARIANT = 0x007F; + static AsyncLocal<CultureInfo> s_asyncLocalCurrentCulture; static AsyncLocal<CultureInfo> s_asyncLocalCurrentUICulture; @@ -189,17 +199,55 @@ namespace System.Globalization } - internal CultureInfo(String name, bool useUserOverride) + public CultureInfo(String name, bool useUserOverride) { if (name == null) { - throw new ArgumentNullException("name", + throw new ArgumentNullException(nameof(name), SR.ArgumentNull_String); } InitializeFromName(name, useUserOverride); } + public CultureInfo(int culture) : this(culture, true) + { + } + + public CultureInfo(int culture, bool useUserOverride) + { + // We don't check for other invalid LCIDS here... + if (culture < 0) + { + throw new ArgumentOutOfRangeException(nameof(culture), SR.ArgumentOutOfRange_NeedPosNum); + } + Contract.EndContractBlock(); + + InitializeFromCultureId(culture, useUserOverride); + } + + private void InitializeFromCultureId(int culture, bool useUserOverride) + { + switch (culture) + { + case LOCALE_CUSTOM_DEFAULT: + case LOCALE_SYSTEM_DEFAULT: + case LOCALE_NEUTRAL: + case LOCALE_USER_DEFAULT: + case LOCALE_CUSTOM_UNSPECIFIED: + // Can't support unknown custom cultures and we do not support neutral or + // non-custom user locales. + throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported); + + default: + // Now see if this LCID is supported in the system default CultureData table. + m_cultureData = CultureData.GetCultureData(culture, useUserOverride); + break; + } + m_isInherited = (this.GetType() != typeof(System.Globalization.CultureInfo)); + m_name = m_cultureData.CultureName; + } + private void InitializeFromName(string name, bool useUserOverride) { // Get our data providing record @@ -207,13 +255,38 @@ namespace System.Globalization if (this.m_cultureData == null) { - throw new CultureNotFoundException("name", name, SR.Argument_CultureNotSupported); + throw new CultureNotFoundException(nameof(name), name, SR.Argument_CultureNotSupported); } this.m_name = this.m_cultureData.CultureName; this.m_isInherited = (this.GetType() != typeof(System.Globalization.CultureInfo)); } + // Constructor called by SQL Server's special munged culture - creates a culture with + // a TextInfo and CompareInfo that come from a supplied alternate source. This object + // is ALWAYS read-only. + // Note that we really cannot use an LCID version of this override as the cached + // name we create for it has to include both names, and the logic for this is in + // the GetCultureInfo override *only*. + internal CultureInfo(String cultureName, String textAndCompareCultureName) + { + if (cultureName == null) + { + throw new ArgumentNullException(nameof(cultureName),SR.ArgumentNull_String); + } + Contract.EndContractBlock(); + + m_cultureData = CultureData.GetCultureData(cultureName, false); + if (m_cultureData == null) + throw new CultureNotFoundException(nameof(cultureName), cultureName, SR.Argument_CultureNotSupported); + + m_name = m_cultureData.CultureName; + + CultureInfo altCulture = GetCultureInfo(textAndCompareCultureName); + compareInfo = altCulture.CompareInfo; + textInfo = altCulture.TextInfo; + } + // We do this to try to return the system UI language and the default user languages // This method will fallback if this fails (like Invariant) // @@ -239,6 +312,66 @@ namespace System.Globalization return ci; } + // + // Return a specific culture. A tad irrelevent now since we always return valid data + // for neutral locales. + // + // Note that there's interesting behavior that tries to find a smaller name, ala RFC4647, + // if we can't find a bigger name. That doesn't help with things like "zh" though, so + // the approach is of questionable value + // + public static CultureInfo CreateSpecificCulture(String name) + { + Contract.Ensures(Contract.Result<CultureInfo>() != null); + + CultureInfo culture; + + try + { + culture = new CultureInfo(name); + } + catch (ArgumentException) + { + // When CultureInfo throws this exception, it may be because someone passed the form + // like "az-az" because it came out of an http accept lang. We should try a little + // parsing to perhaps fall back to "az" here and use *it* to create the neutral. + + int idx; + + culture = null; + for (idx = 0; idx < name.Length; idx++) + { + if ('-' == name[idx]) + { + try + { + culture = new CultureInfo(name.Substring(0, idx)); + break; + } + catch (ArgumentException) + { + // throw the original exception so the name in the string will be right + throw; + } + } + } + + if (culture == null) + { + // nothing to save here; throw the original exception + throw; + } + } + + // In the most common case, they've given us a specific culture, so we'll just return that. + if (!(culture.IsNeutralCulture)) + { + return culture; + } + + return (new CultureInfo(culture.m_cultureData.SSPECIFICCULTURE)); + } + // // // // Return a specific culture. A tad irrelevent now since we always return valid data // // for neutral locales. @@ -296,7 +429,7 @@ namespace System.Globalization [OnDeserialized] private void OnDeserialized(StreamingContext ctx) { - Contract.Assert(m_name != null, "[CultureInfo.OnDeserialized] m_name != null"); + Debug.Assert(m_name != null, "[CultureInfo.OnDeserialized] m_name != null"); InitializeFromName(m_name, m_useUserOverride); } @@ -348,7 +481,7 @@ namespace System.Globalization Init(); } - Contract.Assert(s_userDefaultCulture != null); + Debug.Assert(s_userDefaultCulture != null); return s_userDefaultCulture; } @@ -356,7 +489,7 @@ namespace System.Globalization { if (value == null) { - throw new ArgumentNullException("value"); + throw new ArgumentNullException(nameof(value)); } if (s_asyncLocalCurrentCulture == null) @@ -396,7 +529,7 @@ namespace System.Globalization Init(); } - Contract.Assert(s_userDefaultCulture != null); + Debug.Assert(s_userDefaultCulture != null); return s_userDefaultCulture; } @@ -404,7 +537,7 @@ namespace System.Globalization { if (value == null) { - throw new ArgumentNullException("value"); + throw new ArgumentNullException(nameof(value)); } CultureInfo.VerifyCultureName(value, true); @@ -419,6 +552,20 @@ namespace System.Globalization } } + public static CultureInfo InstalledUICulture + { + get + { + Contract.Ensures(Contract.Result<CultureInfo>() != null); + if (s_userDefaultCulture == null) + { + Init(); + } + Debug.Assert(s_userDefaultCulture != null, "[CultureInfo.InstalledUICulture] s_userDefaultCulture != null"); + return s_userDefaultCulture; + } + } + public static CultureInfo DefaultThreadCurrentCulture { get { return s_DefaultThreadCurrentCulture; } @@ -513,6 +660,34 @@ namespace System.Globalization } } + public virtual int LCID + { + get + { + return (this.m_cultureData.ILANGUAGE); + } + } + + public virtual int KeyboardLayoutId + { + get + { + return m_cultureData.IINPUTLANGUAGEHANDLE; + } + } + + public static CultureInfo[] GetCultures(CultureTypes types) + { + Contract.Ensures(Contract.Result<CultureInfo[]>() != null); + // internally we treat UserCustomCultures as Supplementals but v2 + // treats as Supplementals and Replacements + if((types & CultureTypes.UserCustomCulture) == CultureTypes.UserCustomCulture) + { + types |= CultureTypes.ReplacementCultures; + } + return (CultureData.GetCultures(types)); + } + //////////////////////////////////////////////////////////////////////// // // Name @@ -552,6 +727,24 @@ namespace System.Globalization } } + public string IetfLanguageTag + { + get + { + Contract.Ensures(Contract.Result<string>() != null); + + // special case the compatibility cultures + switch (this.Name) + { + case "zh-CHT": + return "zh-Hant"; + case "zh-CHS": + return "zh-Hans"; + default: + return this.Name; + } + } + } //////////////////////////////////////////////////////////////////////// // @@ -567,7 +760,7 @@ namespace System.Globalization get { Contract.Ensures(Contract.Result<String>() != null); - Contract.Assert(m_name != null, "[CultureInfo.DisplayName] Always expect m_name to be set"); + Debug.Assert(m_name != null, "[CultureInfo.DisplayName] Always expect m_name to be set"); return m_cultureData.SLOCALIZEDDISPLAYNAME; } @@ -619,6 +812,32 @@ namespace System.Globalization } } + // ie: eng + public virtual String ThreeLetterISOLanguageName + { + get + { + Contract.Ensures(Contract.Result<String>() != null); + return m_cultureData.SISO639LANGNAME2; + } + } + + //////////////////////////////////////////////////////////////////////// + // + // ThreeLetterWindowsLanguageName + // + // Returns the 3 letter windows language name for the current instance. eg: "ENU" + // The ISO names are much preferred + // + //////////////////////////////////////////////////////////////////////// + public virtual String ThreeLetterWindowsLanguageName + { + get + { + Contract.Ensures(Contract.Result<String>() != null); + return m_cultureData.SABBREVLANGNAME; + } + } //////////////////////////////////////////////////////////////////////// // @@ -767,6 +986,31 @@ namespace System.Globalization } } + public CultureTypes CultureTypes + { + get + { + CultureTypes types = 0; + + if (m_cultureData.IsNeutralCulture) + types |= CultureTypes.NeutralCultures; + else + types |= CultureTypes.SpecificCultures; + + types |= m_cultureData.IsWin32Installed ? CultureTypes.InstalledWin32Cultures : 0; + +// Disable warning 618: System.Globalization.CultureTypes.FrameworkCultures' is obsolete +#pragma warning disable 618 + types |= m_cultureData.IsFramework ? CultureTypes.FrameworkCultures : 0; + +#pragma warning restore 618 + types |= m_cultureData.IsSupplementalCustomCulture ? CultureTypes.UserCustomCulture : 0; + types |= m_cultureData.IsReplacementCulture ? CultureTypes.ReplacementCultures | CultureTypes.UserCustomCulture : 0; + + return types; + } + } + public virtual NumberFormatInfo NumberFormat { get @@ -775,7 +1019,7 @@ namespace System.Globalization { NumberFormatInfo temp = new NumberFormatInfo(this.m_cultureData); temp.isReadOnly = m_isReadOnly; - numInfo = temp; + Interlocked.CompareExchange(ref numInfo, temp, null); } return (numInfo); } @@ -783,7 +1027,7 @@ namespace System.Globalization { if (value == null) { - throw new ArgumentNullException("value", SR.ArgumentNull_Obj); + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Obj); } VerifyWritable(); numInfo = value; @@ -807,8 +1051,7 @@ namespace System.Globalization // Change the calendar of DTFI to the specified calendar of this CultureInfo. DateTimeFormatInfo temp = new DateTimeFormatInfo(this.m_cultureData, this.Calendar); temp._isReadOnly = m_isReadOnly; - System.Threading.Interlocked.MemoryBarrier(); - dateTimeInfo = temp; + Interlocked.CompareExchange(ref dateTimeInfo, temp, null); } return (dateTimeInfo); } @@ -817,13 +1060,28 @@ namespace System.Globalization { if (value == null) { - throw new ArgumentNullException("value", SR.ArgumentNull_Obj); + throw new ArgumentNullException(nameof(value), SR.ArgumentNull_Obj); } VerifyWritable(); dateTimeInfo = value; } } + public void ClearCachedData() + { + s_userDefaultCulture = null; + + RegionInfo.s_currentRegionInfo = null; + #pragma warning disable 0618 // disable the obsolete warning + TimeZone.ResetTimeZone(); + #pragma warning restore 0618 + TimeZoneInfo.ClearCachedData(); + s_LcidCachedCultures = null; + s_NameCachedCultures = null; + + CultureData.ClearCachedData(); + } + /*=================================GetCalendarInstance========================== **Action: Map a Win32 CALID to an instance of supported calendar. **Returns: An instance of calendar. @@ -845,7 +1103,7 @@ namespace System.Globalization //calendars unless they're required. internal static Calendar GetCalendarInstanceRare(CalendarId calType) { - Contract.Assert(calType != CalendarId.GREGORIAN, "calType!=CalendarId.GREGORIAN"); + Debug.Assert(calType != CalendarId.GREGORIAN, "calType!=CalendarId.GREGORIAN"); switch (calType) { @@ -889,7 +1147,7 @@ namespace System.Globalization { if (calendar == null) { - Contract.Assert(this.m_cultureData.CalendarIds.Length > 0, "this.m_cultureData.CalendarIds.Length > 0"); + Debug.Assert(this.m_cultureData.CalendarIds.Length > 0, "this.m_cultureData.CalendarIds.Length > 0"); // Get the default calendar for this culture. Note that the value can be // from registry if this is a user default culture. Calendar newObj = this.m_cultureData.DefaultCalendar; @@ -929,13 +1187,26 @@ namespace System.Globalization } } - - private bool UseUserOverride + public bool UseUserOverride { get { - return (this.m_cultureData.UseUserOverride); + return m_cultureData.UseUserOverride; + } + } + + public CultureInfo GetConsoleFallbackUICulture() + { + Contract.Ensures(Contract.Result<CultureInfo>() != null); + + CultureInfo temp = m_consoleFallbackCulture; + if (temp == null) + { + temp = CreateSpecificCulture(m_cultureData.SCONSOLEFALLBACKNAME); + temp.m_isReadOnly = true; + m_consoleFallbackCulture = temp; } + return (temp); } public virtual Object Clone() @@ -979,7 +1250,7 @@ namespace System.Globalization { if (ci == null) { - throw new ArgumentNullException("ci"); + throw new ArgumentNullException(nameof(ci)); } Contract.Ensures(Contract.Result<CultureInfo>() != null); Contract.EndContractBlock(); @@ -1054,26 +1325,25 @@ namespace System.Globalization get { return Name == CultureInfo.InvariantCulture.Name; } } - // Helper function both both overloads of GetCachedReadOnlyCulture. - internal static CultureInfo GetCultureInfoHelper(string name) + // Helper function both both overloads of GetCachedReadOnlyCulture. If lcid is 0, we use the name. + // If lcid is -1, use the altName and create one of those special SQL cultures. + internal static CultureInfo GetCultureInfoHelper(int lcid, string name, string altName) { - // There is a race condition in this code with the side effect that the second thread's value - // clobbers the first in the dictionary. This is an acceptable race since the CultureInfo objects - // are content equal (but not reference equal). Since we make no guarantees there, this race is - // acceptable. - // retval is our return value. CultureInfo retval; - if (name == null) - { - return null; - } - // Temporary hashtable for the names. StringCultureInfoDictionary tempNameHT = s_NameCachedCultures; - name = CultureData.AnsiToLower(name); + if (name != null) + { + name = CultureData.AnsiToLower(name); + } + + if (altName != null) + { + altName = CultureData.AnsiToLower(altName); + } // We expect the same result for both hashtables, but will test individually for added safety. if (tempNameHT == null) @@ -1082,20 +1352,66 @@ namespace System.Globalization } else { - bool ret; - lock (m_lock) + // If we are called by name, check if the object exists in the hashtable. If so, return it. + if (lcid == -1 || lcid == 0) { - ret = tempNameHT.TryGetValue(name, out retval); + bool ret; + lock (m_lock) + { + ret = tempNameHT.TryGetValue(lcid == 0 ? name : name + '\xfffd' + altName, out retval); + } + + if (ret && retval != null) + { + return retval; + } } + } - if (ret && retval != null) + // Next, the Lcid table. + StringLcidDictionary tempLcidHT = s_LcidCachedCultures; + + if (tempLcidHT == null) + { + // Case insensitive is not an issue here, save the constructor call. + tempLcidHT = new StringLcidDictionary(); + } + else + { + // If we were called by Lcid, check if the object exists in the table. If so, return it. + if (lcid > 0) { - return retval; + bool ret; + lock (m_lock) + { + ret = tempLcidHT.TryGetValue(lcid, out retval); + } + if (ret && retval != null) + { + return retval; + } } } + + // We now have two temporary hashtables and the desired object was not found. + // We'll construct it. We catch any exceptions from the constructor call and return null. try { - retval = new CultureInfo(name, false); + switch (lcid) + { + case -1: + // call the private constructor + retval = new CultureInfo(name, altName); + break; + + case 0: + retval = new CultureInfo(name, false); + break; + + default: + retval = new CultureInfo(lcid, false); + break; + } } catch (ArgumentException) { @@ -1105,14 +1421,42 @@ namespace System.Globalization // Set it to read-only retval.m_isReadOnly = true; - // Remember our name (as constructed). Do NOT use alternate sort name versions because - // we have internal state representing the sort. (So someone would get the wrong cached version) - string newName = CultureData.AnsiToLower(retval.m_name); + if (lcid == -1) + { + lock (m_lock) + { + // This new culture will be added only to the name hash table. + tempNameHT[name + '\xfffd' + altName] = retval; + } + // when lcid == -1 then TextInfo object is already get created and we need to set it as read only. + retval.TextInfo.SetReadOnlyState(true); + } + else if (lcid == 0) + { + // Remember our name (as constructed). Do NOT use alternate sort name versions because + // we have internal state representing the sort. (So someone would get the wrong cached version) + string newName = CultureData.AnsiToLower(retval.m_name); + + // We add this new culture info object to both tables. + lock (m_lock) + { + tempNameHT[newName] = retval; + } + } + else + { + lock (m_lock) + { + tempLcidHT[lcid] = retval; + } + } - // We add this new culture info object to both tables. - lock (m_lock) + // Copy the two hashtables to the corresponding member variables. This will potentially overwrite + // new tables simultaneously created by a new thread, but maximizes thread safety. + if (-1 != lcid) { - tempNameHT[newName] = retval; + // Only when we modify the lcid hash table, is there a need to overwrite. + s_LcidCachedCultures = tempLcidHT; } s_NameCachedCultures = tempNameHT; @@ -1122,23 +1466,93 @@ namespace System.Globalization } // Gets a cached copy of the specified culture from an internal hashtable (or creates it + // if not found). (LCID version)... use named version + public static CultureInfo GetCultureInfo(int culture) + { + // Must check for -1 now since the helper function uses the value to signal + // the altCulture code path for SQL Server. + // Also check for zero as this would fail trying to add as a key to the hash. + if (culture <= 0) + { + throw new ArgumentOutOfRangeException(nameof(culture), SR.ArgumentOutOfRange_NeedPosNum); + } + Contract.Ensures(Contract.Result<CultureInfo>() != null); + Contract.EndContractBlock(); + CultureInfo retval = GetCultureInfoHelper(culture, null, null); + if (null == retval) + { + throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported); + } + return retval; + } + + // Gets a cached copy of the specified culture from an internal hashtable (or creates it // if not found). (Named version) - internal static CultureInfo GetCultureInfo(string name) + public static CultureInfo GetCultureInfo(string name) { // Make sure we have a valid, non-zero length string as name if (name == null) { - throw new ArgumentNullException("name"); + throw new ArgumentNullException(nameof(name)); } - CultureInfo retval = GetCultureInfoHelper(name); + CultureInfo retval = GetCultureInfoHelper(0, name, null); if (retval == null) { throw new CultureNotFoundException( - "name", name, SR.Argument_CultureNotSupported); + nameof(name), name, SR.Argument_CultureNotSupported); + } + return retval; + } + + // Gets a cached copy of the specified culture from an internal hashtable (or creates it + // if not found). + public static CultureInfo GetCultureInfo(string name, string altName) + { + // Make sure we have a valid, non-zero length string as name + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (altName == null) + { + throw new ArgumentNullException(nameof(altName)); + } + + Contract.Ensures(Contract.Result<CultureInfo>() != null); + Contract.EndContractBlock(); + + CultureInfo retval = GetCultureInfoHelper(-1, name, altName); + if (retval == null) + { + throw new CultureNotFoundException("name or altName", + SR.Format(SR.Argument_OneOfCulturesNotSupported, name, altName)); } return retval; } + + // This function is deprecated, we don't like it + public static CultureInfo GetCultureInfoByIetfLanguageTag(string name) + { + Contract.Ensures(Contract.Result<CultureInfo>() != null); + + // Disallow old zh-CHT/zh-CHS names + if (name == "zh-CHT" || name == "zh-CHS") + { + throw new CultureNotFoundException(nameof(name), SR.Format(SR.Argument_CultureIetfNotSupported, name)); + } + + CultureInfo ci = GetCultureInfo(name); + + // Disallow alt sorts and es-es_TS + if (ci.LCID > 0xffff || ci.LCID == 0x040a) + { + throw new CultureNotFoundException(nameof(name), SR.Format(SR.Argument_CultureIetfNotSupported, name)); + } + + return ci; + } } } |