Fix race conditions in CultureInfo.GetCultureInfoHelper (#26567)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Globalization / CultureInfo.cs
blob27888e94b5427384c4fbe6dbec6e4e06545728fd
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 ////////////////////////////////////////////////////////////////////////////
6 //
7 //
8 //
9 // Purpose: This class represents the software preferences of a particular
10 // culture or community. It includes information such as the
11 // language, writing system, and a calendar used by the culture
12 // as well as methods for common operations such as printing
13 // dates and sorting strings.
17 // !!!! NOTE WHEN CHANGING THIS CLASS !!!!
19 // If adding or removing members to this class, please update CultureInfoBaseObject
20 // in ndp/clr/src/vm/object.h. Note, the "actual" layout of the class may be
21 // different than the order in which members are declared. For instance, all
22 // reference types will come first in the class before value types (like ints, bools, etc)
23 // regardless of the order in which they are declared. The best way to see the
24 // actual order of the class is to do a !dumpobj on an instance of the managed
25 // object inside of the debugger.
27 ////////////////////////////////////////////////////////////////////////////
29 using System.Collections.Generic;
30 using System.Diagnostics;
31 using System.Threading;
32 #if ENABLE_WINRT
33 using Internal.Runtime.Augments;
34 #endif
36 namespace System.Globalization
38 /// <summary>
39 /// This class represents the software preferences of a particular culture
40 /// or community. It includes information such as the language, writing
41 /// system and a calendar used by the culture as well as methods for
42 /// common operations such as printing dates and sorting strings.
43 /// </summary>
44 /// <remarks>
45 /// !!!! NOTE WHEN CHANGING THIS CLASS !!!!
46 /// If adding or removing members to this class, please update
47 /// CultureInfoBaseObject in ndp/clr/src/vm/object.h. Note, the "actual"
48 /// layout of the class may be different than the order in which members
49 /// are declared. For instance, all reference types will come first in the
50 /// class before value types (like ints, bools, etc) regardless of the
51 /// order in which they are declared. The best way to see the actual
52 /// order of the class is to do a !dumpobj on an instance of the managed
53 /// object inside of the debugger.
54 /// </remarks>
55 public partial class CultureInfo : IFormatProvider, ICloneable
57 // We use an RFC4646 type string to construct CultureInfo.
58 // This string is stored in _name and is authoritative.
59 // We use the _cultureData to get the data for our object
61 private bool _isReadOnly;
62 private CompareInfo? _compareInfo;
63 private TextInfo? _textInfo;
64 internal NumberFormatInfo? _numInfo;
65 internal DateTimeFormatInfo? _dateTimeInfo;
66 private Calendar? _calendar;
68 // The CultureData instance that we are going to read data from.
69 // For supported culture, this will be the CultureData instance that read data from mscorlib assembly.
70 // For customized culture, this will be the CultureData instance that read data from user customized culture binary file.
72 internal CultureData _cultureData;
74 internal bool _isInherited;
76 private CultureInfo? _consoleFallbackCulture;
78 // Names are confusing. Here are 3 names we have:
80 // new CultureInfo() _name _nonSortName _sortName
81 // en-US en-US en-US en-US
82 // de-de_phoneb de-DE_phoneb de-DE de-DE_phoneb
83 // fj-fj (custom) fj-FJ fj-FJ en-US (if specified sort is en-US)
84 // en en
86 // Note that in Silverlight we ask the OS for the text and sort behavior, so the
87 // textinfo and compareinfo names are the same as the name
89 // This has a de-DE, de-DE_phoneb or fj-FJ style name
90 internal string _name;
92 // This will hold the non sorting name to be returned from CultureInfo.Name property.
93 // This has a de-DE style name even for de-DE_phoneb type cultures
94 private string? _nonSortName;
96 // This will hold the sorting name to be returned from CultureInfo.SortName property.
97 // This might be completely unrelated to the culture name if a custom culture. Ie en-US for fj-FJ.
98 // Otherwise its the sort name, ie: de-DE or de-DE_phoneb
99 private string? _sortName;
101 // Get the current user default culture. This one is almost always used, so we create it by default.
102 private static volatile CultureInfo? s_userDefaultCulture;
104 // The culture used in the user interface. This is mostly used to load correct localized resources.
105 private static volatile CultureInfo? s_userDefaultUICulture;
107 // WARNING: We allow diagnostic tools to directly inspect these three members (s_InvariantCultureInfo, s_DefaultThreadCurrentUICulture and s_DefaultThreadCurrentCulture)
108 // See https://github.com/dotnet/corert/blob/master/Documentation/design-docs/diagnostics/diagnostics-tools-contract.md for more details.
109 // Please do not change the type, the name, or the semantic usage of this member without understanding the implication for tools.
110 // Get in touch with the diagnostics team if you have questions.
112 // The Invariant culture;
113 private static readonly CultureInfo s_InvariantCultureInfo = new CultureInfo(CultureData.Invariant, isReadOnly: true);
115 // These are defaults that we use if a thread has not opted into having an explicit culture
116 private static volatile CultureInfo? s_DefaultThreadCurrentUICulture;
117 private static volatile CultureInfo? s_DefaultThreadCurrentCulture;
119 [ThreadStatic]
120 private static CultureInfo? s_currentThreadCulture;
121 [ThreadStatic]
122 private static CultureInfo? s_currentThreadUICulture;
124 private static AsyncLocal<CultureInfo>? s_asyncLocalCurrentCulture;
125 private static AsyncLocal<CultureInfo>? s_asyncLocalCurrentUICulture;
127 private static void AsyncLocalSetCurrentCulture(AsyncLocalValueChangedArgs<CultureInfo> args)
129 s_currentThreadCulture = args.CurrentValue;
132 private static void AsyncLocalSetCurrentUICulture(AsyncLocalValueChangedArgs<CultureInfo> args)
134 s_currentThreadUICulture = args.CurrentValue;
137 private static volatile Dictionary<string, CultureInfo>? s_cachedCulturesByName;
138 private static volatile Dictionary<int, CultureInfo>? s_cachedCulturesByLcid;
140 // The parent culture.
141 private CultureInfo? _parent;
143 // LOCALE constants of interest to us internally and privately for LCID functions
144 // (ie: avoid using these and use names if possible)
145 internal const int LOCALE_NEUTRAL = 0x0000;
146 private const int LOCALE_USER_DEFAULT = 0x0400;
147 private const int LOCALE_SYSTEM_DEFAULT = 0x0800;
148 internal const int LOCALE_CUSTOM_UNSPECIFIED = 0x1000;
149 internal const int LOCALE_CUSTOM_DEFAULT = 0x0c00;
150 internal const int LOCALE_INVARIANT = 0x007F;
152 private static CultureInfo InitializeUserDefaultCulture()
154 Interlocked.CompareExchange(ref s_userDefaultCulture, GetUserDefaultCulture(), null);
155 return s_userDefaultCulture!;
158 private static CultureInfo InitializeUserDefaultUICulture()
160 Interlocked.CompareExchange(ref s_userDefaultUICulture, GetUserDefaultUICulture(), null);
161 return s_userDefaultUICulture!;
164 public CultureInfo(string name) : this(name, true)
168 public CultureInfo(string name, bool useUserOverride)
170 if (name == null)
172 throw new ArgumentNullException(nameof(name));
175 // Get our data providing record
176 CultureData? cultureData = CultureData.GetCultureData(name, useUserOverride);
178 if (cultureData == null)
180 throw new CultureNotFoundException(nameof(name), name, SR.Argument_CultureNotSupported);
183 _cultureData = cultureData;
184 _name = _cultureData.CultureName;
185 _isInherited = GetType() != typeof(CultureInfo);
188 private CultureInfo(CultureData cultureData, bool isReadOnly = false)
190 Debug.Assert(cultureData != null);
191 _cultureData = cultureData;
192 _name = cultureData.CultureName;
193 _isInherited = false;
194 _isReadOnly = isReadOnly;
197 private static CultureInfo? CreateCultureInfoNoThrow(string name, bool useUserOverride)
199 Debug.Assert(name != null);
200 CultureData? cultureData = CultureData.GetCultureData(name, useUserOverride);
201 if (cultureData == null)
203 return null;
206 return new CultureInfo(cultureData);
209 public CultureInfo(int culture) : this(culture, true)
213 public CultureInfo(int culture, bool useUserOverride)
215 // We don't check for other invalid LCIDS here...
216 if (culture < 0)
218 throw new ArgumentOutOfRangeException(nameof(culture), SR.ArgumentOutOfRange_NeedPosNum);
221 switch (culture)
223 case LOCALE_CUSTOM_DEFAULT:
224 case LOCALE_SYSTEM_DEFAULT:
225 case LOCALE_NEUTRAL:
226 case LOCALE_USER_DEFAULT:
227 case LOCALE_CUSTOM_UNSPECIFIED:
228 // Can't support unknown custom cultures and we do not support neutral or
229 // non-custom user locales.
230 throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported);
231 default:
232 // Now see if this LCID is supported in the system default CultureData table.
233 _cultureData = CultureData.GetCultureData(culture, useUserOverride);
234 break;
236 _isInherited = GetType() != typeof(CultureInfo);
237 _name = _cultureData.CultureName;
240 /// <summary>
241 /// Constructor called by SQL Server's special munged culture - creates a culture with
242 /// a TextInfo and CompareInfo that come from a supplied alternate source. This object
243 /// is ALWAYS read-only.
244 /// Note that we really cannot use an LCID version of this override as the cached
245 /// name we create for it has to include both names, and the logic for this is in
246 /// the GetCultureInfo override *only*.
247 /// </summary>
248 internal CultureInfo(string cultureName, string textAndCompareCultureName)
250 if (cultureName == null)
252 throw new ArgumentNullException(nameof(cultureName), SR.ArgumentNull_String);
255 CultureData? cultureData = CultureData.GetCultureData(cultureName, false) ??
256 throw new CultureNotFoundException(nameof(cultureName), cultureName, SR.Argument_CultureNotSupported);
258 _cultureData = cultureData;
260 _name = _cultureData.CultureName;
262 CultureInfo altCulture = GetCultureInfo(textAndCompareCultureName);
263 _compareInfo = altCulture.CompareInfo;
264 _textInfo = altCulture.TextInfo;
267 /// <summary>
268 /// We do this to try to return the system UI language and the default user languages
269 /// This method will fallback if this fails (like Invariant)
270 /// </summary>
271 private static CultureInfo GetCultureByName(string name)
275 return new CultureInfo(name)
277 _isReadOnly = true
280 catch (ArgumentException)
282 return InvariantCulture;
286 /// <summary>
287 /// Return a specific culture. A tad irrelevent now since we always
288 /// return valid data for neutral locales.
290 /// Note that there's interesting behavior that tries to find a
291 /// smaller name, ala RFC4647, if we can't find a bigger name.
292 /// That doesn't help with things like "zh" though, so the approach
293 /// is of questionable value
294 /// </summary>
295 public static CultureInfo CreateSpecificCulture(string name)
297 CultureInfo? culture;
301 culture = new CultureInfo(name);
303 catch (ArgumentException)
305 // When CultureInfo throws this exception, it may be because someone passed the form
306 // like "az-az" because it came out of an http accept lang. We should try a little
307 // parsing to perhaps fall back to "az" here and use *it* to create the neutral.
308 culture = null;
309 for (int idx = 0; idx < name.Length; idx++)
311 if ('-' == name[idx])
315 culture = new CultureInfo(name.Substring(0, idx));
316 break;
318 catch (ArgumentException)
320 // throw the original exception so the name in the string will be right
321 throw;
326 if (culture == null)
328 // nothing to save here; throw the original exception
329 throw;
333 // In the most common case, they've given us a specific culture, so we'll just return that.
334 if (!(culture.IsNeutralCulture))
336 return culture;
339 return new CultureInfo(culture._cultureData.SpecificCultureName);
342 internal static bool VerifyCultureName(string cultureName, bool throwException)
344 // This function is used by ResourceManager.GetResourceFileName().
345 // ResourceManager searches for resource using CultureInfo.Name,
346 // so we should check against CultureInfo.Name.
347 for (int i = 0; i < cultureName.Length; i++)
349 char c = cultureName[i];
350 // TODO: Names can only be RFC4646 names (ie: a-zA-Z0-9) while this allows any unicode letter/digit
351 if (char.IsLetterOrDigit(c) || c == '-' || c == '_')
353 continue;
355 if (throwException)
357 throw new ArgumentException(SR.Format(SR.Argument_InvalidResourceCultureName, cultureName));
359 return false;
361 return true;
364 internal static bool VerifyCultureName(CultureInfo culture, bool throwException)
366 // If we have an instance of one of our CultureInfos, the user can't have changed the
367 // name and we know that all names are valid in files.
368 if (!culture._isInherited)
370 return true;
373 return VerifyCultureName(culture.Name, throwException);
376 /// <summary>
377 /// This instance provides methods based on the current user settings.
378 /// These settings are volatile and may change over the lifetime of the
379 /// thread.
380 /// </summary>
381 /// <remarks>
382 /// We use the following order to return CurrentCulture and CurrentUICulture
383 /// o Use WinRT to return the current user profile language
384 /// o use current thread culture if the user already set one using CurrentCulture/CurrentUICulture
385 /// o use thread culture if the user already set one using DefaultThreadCurrentCulture
386 /// or DefaultThreadCurrentUICulture
387 /// o Use NLS default user culture
388 /// o Use NLS default system culture
389 /// o Use Invariant culture
390 /// </remarks>
391 public static CultureInfo CurrentCulture
395 #if ENABLE_WINRT
396 WinRTInteropCallbacks callbacks = WinRTInterop.UnsafeCallbacks;
397 if (callbacks != null && callbacks.IsAppxModel())
399 return (CultureInfo)callbacks.GetUserDefaultCulture();
401 #endif
402 #if FEATURE_APPX
403 if (ApplicationModel.IsUap)
405 CultureInfo? culture = GetCultureInfoForUserPreferredLanguageInAppX();
406 if (culture != null)
407 return culture;
409 #endif
411 return s_currentThreadCulture ??
412 s_DefaultThreadCurrentCulture ??
413 s_userDefaultCulture ??
414 InitializeUserDefaultCulture();
418 if (value == null)
420 throw new ArgumentNullException(nameof(value));
423 #if ENABLE_WINRT
424 WinRTInteropCallbacks callbacks = WinRTInterop.UnsafeCallbacks;
425 if (callbacks != null && callbacks.IsAppxModel())
427 callbacks.SetGlobalDefaultCulture(value);
428 return;
430 #endif
431 #if FEATURE_APPX
432 if (ApplicationModel.IsUap)
434 if (SetCultureInfoForUserPreferredLanguageInAppX(value))
436 // successfully set the culture, otherwise fallback to legacy path
437 return;
440 #endif
442 if (s_asyncLocalCurrentCulture == null)
444 Interlocked.CompareExchange(ref s_asyncLocalCurrentCulture, new AsyncLocal<CultureInfo>(AsyncLocalSetCurrentCulture), null);
446 s_asyncLocalCurrentCulture!.Value = value;
450 public static CultureInfo CurrentUICulture
454 #if ENABLE_WINRT
455 WinRTInteropCallbacks callbacks = WinRTInterop.UnsafeCallbacks;
456 if (callbacks != null && callbacks.IsAppxModel())
458 return (CultureInfo)callbacks.GetUserDefaultCulture();
460 #endif
461 #if FEATURE_APPX
462 if (ApplicationModel.IsUap)
464 CultureInfo? culture = GetCultureInfoForUserPreferredLanguageInAppX();
465 if (culture != null)
466 return culture;
468 #endif
470 return s_currentThreadUICulture ??
471 s_DefaultThreadCurrentUICulture ??
472 UserDefaultUICulture;
476 if (value == null)
478 throw new ArgumentNullException(nameof(value));
481 CultureInfo.VerifyCultureName(value, true);
483 #if ENABLE_WINRT
484 WinRTInteropCallbacks callbacks = WinRTInterop.UnsafeCallbacks;
485 if (callbacks != null && callbacks.IsAppxModel())
487 callbacks.SetGlobalDefaultCulture(value);
488 return;
490 #endif
491 #if FEATURE_APPX
492 if (ApplicationModel.IsUap)
494 if (SetCultureInfoForUserPreferredLanguageInAppX(value))
496 // successfully set the culture, otherwise fallback to legacy path
497 return;
500 #endif
502 if (s_asyncLocalCurrentUICulture == null)
504 Interlocked.CompareExchange(ref s_asyncLocalCurrentUICulture, new AsyncLocal<CultureInfo>(AsyncLocalSetCurrentUICulture), null);
507 // this one will set s_currentThreadUICulture too
508 s_asyncLocalCurrentUICulture!.Value = value;
512 internal static CultureInfo UserDefaultUICulture => s_userDefaultUICulture ?? InitializeUserDefaultUICulture();
514 public static CultureInfo InstalledUICulture => s_userDefaultCulture ?? InitializeUserDefaultCulture();
516 public static CultureInfo? DefaultThreadCurrentCulture
518 get => s_DefaultThreadCurrentCulture;
519 set =>
520 // If you add pre-conditions to this method, check to see if you also need to
521 // add them to Thread.CurrentCulture.set.
522 s_DefaultThreadCurrentCulture = value;
525 public static CultureInfo? DefaultThreadCurrentUICulture
527 get => s_DefaultThreadCurrentUICulture;
530 // If they're trying to use a Culture with a name that we can't use in resource lookup,
531 // don't even let them set it on the thread.
533 // If you add more pre-conditions to this method, check to see if you also need to
534 // add them to Thread.CurrentUICulture.set.
536 if (value != null)
538 CultureInfo.VerifyCultureName(value, true);
541 s_DefaultThreadCurrentUICulture = value;
545 /// <summary>
546 /// This instance provides methods, for example for casing and sorting,
547 /// that are independent of the system and current user settings. It
548 /// should be used only by processes such as some system services that
549 /// require such invariant results (eg. file systems). In general,
550 /// the results are not linguistically correct and do not match any
551 /// culture info.
552 /// </summary>
553 public static CultureInfo InvariantCulture
557 Debug.Assert(s_InvariantCultureInfo != null);
558 return s_InvariantCultureInfo;
562 /// <summary>
563 /// Return the parent CultureInfo for the current instance.
564 /// </summary>
565 public virtual CultureInfo Parent
569 if (_parent == null)
571 CultureInfo culture;
572 string parentName = _cultureData.ParentName;
574 if (string.IsNullOrEmpty(parentName))
576 culture = InvariantCulture;
578 else
580 culture = CreateCultureInfoNoThrow(parentName, _cultureData.UseUserOverride) ??
581 // For whatever reason our IPARENT or SPARENT wasn't correct, so use invariant
582 // We can't allow ourselves to fail. In case of custom cultures the parent of the
583 // current custom culture isn't installed.
584 InvariantCulture;
587 Interlocked.CompareExchange<CultureInfo?>(ref _parent, culture, null);
589 return _parent!;
593 public virtual int LCID => _cultureData.LCID;
595 public virtual int KeyboardLayoutId => _cultureData.KeyboardLayoutId;
597 public static CultureInfo[] GetCultures(CultureTypes types)
599 // internally we treat UserCustomCultures as Supplementals but v2
600 // treats as Supplementals and Replacements
601 if ((types & CultureTypes.UserCustomCulture) == CultureTypes.UserCustomCulture)
603 types |= CultureTypes.ReplacementCultures;
605 return CultureData.GetCultures(types);
608 /// <summary>
609 /// Returns the full name of the CultureInfo. The name is in format like
610 /// "en-US" This version does NOT include sort information in the name.
611 /// </summary>
612 public virtual string Name
616 // We return non sorting name here.
617 if (_nonSortName == null)
619 _nonSortName = _cultureData.Name ?? string.Empty;
621 return _nonSortName;
625 /// <summary>
626 /// This one has the sort information (ie: de-DE_phoneb)
627 /// </summary>
628 internal string SortName
632 if (_sortName == null)
634 _sortName = _cultureData.SortName;
637 return _sortName;
641 public string IetfLanguageTag =>
642 // special case the compatibility cultures
643 Name switch
645 "zh-CHT" => "zh-Hant",
646 "zh-CHS" => "zh-Hans",
647 _ => Name,
650 /// <summary>
651 /// Returns the full name of the CultureInfo in the localized language.
652 /// For example, if the localized language of the runtime is Spanish and the CultureInfo is
653 /// US English, "Ingles (Estados Unidos)" will be returned.
654 /// </summary>
655 public virtual string DisplayName
659 Debug.Assert(_name != null, "[CultureInfo.DisplayName] Always expect _name to be set");
660 return _cultureData.DisplayName;
664 /// <summary>
665 /// Returns the full name of the CultureInfo in the native language.
666 /// For example, if the CultureInfo is US English, "English
667 /// (United States)" will be returned.
668 /// </summary>
669 public virtual string NativeName => _cultureData.NativeName;
671 /// <summary>
672 /// Returns the full name of the CultureInfo in English.
673 /// For example, if the CultureInfo is US English, "English
674 /// (United States)" will be returned.
675 /// </summary>
676 public virtual string EnglishName => _cultureData.EnglishName;
678 /// <summary>
679 /// ie: en
680 /// </summary>
681 public virtual string TwoLetterISOLanguageName => _cultureData.TwoLetterISOLanguageName;
683 /// <summary>
684 /// ie: eng
685 /// </summary>
686 public virtual string ThreeLetterISOLanguageName => _cultureData.ThreeLetterISOLanguageName;
688 /// <summary>
689 /// Returns the 3 letter windows language name for the current instance. eg: "ENU"
690 /// The ISO names are much preferred
691 /// </summary>
692 public virtual string ThreeLetterWindowsLanguageName => _cultureData.ThreeLetterWindowsLanguageName;
694 /// <summary>
695 /// Gets the CompareInfo for this culture.
696 /// </summary>
697 public virtual CompareInfo CompareInfo
701 if (_compareInfo == null)
703 // Since CompareInfo's don't have any overrideable properties, get the CompareInfo from
704 // the Non-Overridden CultureInfo so that we only create one CompareInfo per culture
705 _compareInfo = UseUserOverride
706 ? GetCultureInfo(_name).CompareInfo
707 : new CompareInfo(this);
709 return _compareInfo;
713 /// <summary>
714 /// Gets the TextInfo for this culture.
715 /// </summary>
716 public virtual TextInfo TextInfo
720 if (_textInfo == null)
722 // Make a new textInfo
723 TextInfo tempTextInfo = new TextInfo(_cultureData);
724 tempTextInfo.SetReadOnlyState(_isReadOnly);
725 _textInfo = tempTextInfo;
727 return _textInfo;
731 public override bool Equals(object? value)
733 if (object.ReferenceEquals(this, value))
735 return true;
738 if (value is CultureInfo that)
740 // using CompareInfo to verify the data passed through the constructor
741 // CultureInfo(String cultureName, String textAndCompareCultureName)
742 return Name.Equals(that.Name) && CompareInfo.Equals(that.CompareInfo);
745 return false;
748 public override int GetHashCode()
750 return Name.GetHashCode() + CompareInfo.GetHashCode();
754 /// <summary>
755 /// Implements object.ToString(). Returns the name of the CultureInfo,
756 /// eg. "de-DE_phoneb", "en-US", or "fj-FJ".
757 /// </summary>
758 public override string ToString() => _name;
760 public virtual object? GetFormat(Type? formatType)
762 if (formatType == typeof(NumberFormatInfo))
764 return NumberFormat;
766 if (formatType == typeof(DateTimeFormatInfo))
768 return DateTimeFormat;
771 return null;
774 public virtual bool IsNeutralCulture => _cultureData.IsNeutralCulture;
776 public CultureTypes CultureTypes
780 CultureTypes types = 0;
782 if (_cultureData.IsNeutralCulture)
784 types |= CultureTypes.NeutralCultures;
786 else
788 types |= CultureTypes.SpecificCultures;
791 types |= _cultureData.IsWin32Installed ? CultureTypes.InstalledWin32Cultures : 0;
793 // Disable warning 618: System.Globalization.CultureTypes.FrameworkCultures' is obsolete
794 #pragma warning disable 618
795 types |= _cultureData.IsFramework ? CultureTypes.FrameworkCultures : 0;
796 #pragma warning restore 618
798 types |= _cultureData.IsSupplementalCustomCulture ? CultureTypes.UserCustomCulture : 0;
799 types |= _cultureData.IsReplacementCulture ? CultureTypes.ReplacementCultures | CultureTypes.UserCustomCulture : 0;
801 return types;
805 public virtual NumberFormatInfo NumberFormat
809 if (_numInfo == null)
811 NumberFormatInfo temp = new NumberFormatInfo(_cultureData);
812 temp._isReadOnly = _isReadOnly;
813 Interlocked.CompareExchange(ref _numInfo, temp, null);
815 return _numInfo!;
819 if (value == null)
821 throw new ArgumentNullException(nameof(value));
824 VerifyWritable();
825 _numInfo = value;
829 /// <summary>
830 /// Create a DateTimeFormatInfo, and fill in the properties according to
831 /// the CultureID.
832 /// </summary>
833 public virtual DateTimeFormatInfo DateTimeFormat
837 if (_dateTimeInfo == null)
839 // Change the calendar of DTFI to the specified calendar of this CultureInfo.
840 DateTimeFormatInfo temp = new DateTimeFormatInfo(_cultureData, this.Calendar);
841 temp._isReadOnly = _isReadOnly;
842 Interlocked.CompareExchange(ref _dateTimeInfo, temp, null);
844 return _dateTimeInfo!;
849 if (value == null)
851 throw new ArgumentNullException(nameof(value));
854 VerifyWritable();
855 _dateTimeInfo = value;
859 public void ClearCachedData()
861 // reset the default culture values
862 s_userDefaultCulture = GetUserDefaultCulture();
863 s_userDefaultUICulture = GetUserDefaultUICulture();
865 RegionInfo.s_currentRegionInfo = null;
866 #pragma warning disable 0618 // disable the obsolete warning
867 TimeZone.ResetTimeZone();
868 #pragma warning restore 0618
869 TimeZoneInfo.ClearCachedData();
870 s_cachedCulturesByLcid = null;
871 s_cachedCulturesByName = null;
873 CultureData.ClearCachedData();
876 /// <summary>
877 /// Map a Win32 CALID to an instance of supported calendar.
878 /// </summary>
879 /// <remarks>
880 /// Shouldn't throw exception since the calType value is from our data
881 /// table or from Win32 registry.
882 /// If we are in trouble (like getting a weird value from Win32
883 /// registry), just return the GregorianCalendar.
884 /// </remarks>
885 internal static Calendar GetCalendarInstance(CalendarId calType)
887 if (calType == CalendarId.GREGORIAN)
889 return new GregorianCalendar();
892 return GetCalendarInstanceRare(calType);
895 /// <summary>
896 /// This function exists as a shortcut to prevent us from loading all of the non-gregorian
897 /// calendars unless they're required.
898 /// </summary>
899 internal static Calendar GetCalendarInstanceRare(CalendarId calType)
901 Debug.Assert(calType != CalendarId.GREGORIAN, "calType!=CalendarId.GREGORIAN");
903 switch (calType)
905 case CalendarId.GREGORIAN_US: // Gregorian (U.S.) calendar
906 case CalendarId.GREGORIAN_ME_FRENCH: // Gregorian Middle East French calendar
907 case CalendarId.GREGORIAN_ARABIC: // Gregorian Arabic calendar
908 case CalendarId.GREGORIAN_XLIT_ENGLISH: // Gregorian Transliterated English calendar
909 case CalendarId.GREGORIAN_XLIT_FRENCH: // Gregorian Transliterated French calendar
910 return new GregorianCalendar((GregorianCalendarTypes)calType);
911 case CalendarId.TAIWAN: // Taiwan Era calendar
912 return new TaiwanCalendar();
913 case CalendarId.JAPAN: // Japanese Emperor Era calendar
914 return new JapaneseCalendar();
915 case CalendarId.KOREA: // Korean Tangun Era calendar
916 return new KoreanCalendar();
917 case CalendarId.THAI: // Thai calendar
918 return new ThaiBuddhistCalendar();
919 case CalendarId.HIJRI: // Hijri (Arabic Lunar) calendar
920 return new HijriCalendar();
921 case CalendarId.HEBREW: // Hebrew (Lunar) calendar
922 return new HebrewCalendar();
923 case CalendarId.UMALQURA:
924 return new UmAlQuraCalendar();
925 case CalendarId.PERSIAN:
926 return new PersianCalendar();
928 return new GregorianCalendar();
931 /// <summary>
932 /// Return/set the default calendar used by this culture.
933 /// This value can be overridden by regional option if this is a current culture.
934 /// </summary>
935 public virtual Calendar Calendar
939 if (_calendar == null)
941 Debug.Assert(_cultureData.CalendarIds.Length > 0, "_cultureData.CalendarIds.Length > 0");
942 // Get the default calendar for this culture. Note that the value can be
943 // from registry if this is a user default culture.
944 Calendar newObj = _cultureData.DefaultCalendar;
946 Interlocked.MemoryBarrier();
947 newObj.SetReadOnlyState(_isReadOnly);
948 _calendar = newObj;
950 return _calendar;
954 /// <summary>
955 /// Return an array of the optional calendar for this culture.
956 /// </summary>
957 public virtual Calendar[] OptionalCalendars
961 // This property always returns a new copy of the calendar array.
962 CalendarId[] calID = _cultureData.CalendarIds;
963 Calendar[] cals = new Calendar[calID.Length];
964 for (int i = 0; i < cals.Length; i++)
966 cals[i] = GetCalendarInstance(calID[i]);
968 return cals;
972 public bool UseUserOverride => _cultureData.UseUserOverride;
974 public CultureInfo GetConsoleFallbackUICulture()
976 CultureInfo? temp = _consoleFallbackCulture;
977 if (temp == null)
979 temp = CreateSpecificCulture(_cultureData.SCONSOLEFALLBACKNAME);
980 temp._isReadOnly = true;
981 _consoleFallbackCulture = temp;
983 return temp;
986 public virtual object Clone()
988 CultureInfo ci = (CultureInfo)MemberwiseClone();
989 ci._isReadOnly = false;
991 // If this is exactly our type, we can make certain optimizations so that we don't allocate NumberFormatInfo or DTFI unless
992 // they've already been allocated. If this is a derived type, we'll take a more generic codepath.
993 if (!_isInherited)
995 if (_dateTimeInfo != null)
997 ci._dateTimeInfo = (DateTimeFormatInfo)_dateTimeInfo.Clone();
999 if (_numInfo != null)
1001 ci._numInfo = (NumberFormatInfo)_numInfo.Clone();
1004 else
1006 ci.DateTimeFormat = (DateTimeFormatInfo)this.DateTimeFormat.Clone();
1007 ci.NumberFormat = (NumberFormatInfo)this.NumberFormat.Clone();
1010 if (_textInfo != null)
1012 ci._textInfo = (TextInfo)_textInfo.Clone();
1015 if (_calendar != null)
1017 ci._calendar = (Calendar)_calendar.Clone();
1020 return ci;
1023 public static CultureInfo ReadOnly(CultureInfo ci)
1025 if (ci == null)
1027 throw new ArgumentNullException(nameof(ci));
1030 if (ci.IsReadOnly)
1032 return ci;
1034 CultureInfo newInfo = (CultureInfo)(ci.MemberwiseClone());
1036 if (!ci.IsNeutralCulture)
1038 // If this is exactly our type, we can make certain optimizations so that we don't allocate NumberFormatInfo or DTFI unless
1039 // they've already been allocated. If this is a derived type, we'll take a more generic codepath.
1040 if (!ci._isInherited)
1042 if (ci._dateTimeInfo != null)
1044 newInfo._dateTimeInfo = DateTimeFormatInfo.ReadOnly(ci._dateTimeInfo);
1046 if (ci._numInfo != null)
1048 newInfo._numInfo = NumberFormatInfo.ReadOnly(ci._numInfo);
1051 else
1053 newInfo.DateTimeFormat = DateTimeFormatInfo.ReadOnly(ci.DateTimeFormat);
1054 newInfo.NumberFormat = NumberFormatInfo.ReadOnly(ci.NumberFormat);
1058 if (ci._textInfo != null)
1060 newInfo._textInfo = TextInfo.ReadOnly(ci._textInfo);
1063 if (ci._calendar != null)
1065 newInfo._calendar = Calendar.ReadOnly(ci._calendar);
1068 // Don't set the read-only flag too early.
1069 // We should set the read-only flag here. Otherwise, info.DateTimeFormat will not be able to set.
1070 newInfo._isReadOnly = true;
1072 return newInfo;
1076 public bool IsReadOnly => _isReadOnly;
1078 private void VerifyWritable()
1080 if (_isReadOnly)
1082 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1086 /// <summary>
1087 /// For resource lookup, we consider a culture the invariant culture by name equality.
1088 /// We perform this check frequently during resource lookup, so adding a property for
1089 /// improved readability.
1090 /// </summary>
1091 internal bool HasInvariantCultureName => Name == InvariantCulture.Name;
1093 /// <summary>
1094 /// Gets a cached copy of the specified culture from an internal
1095 /// hashtable (or creates it if not found). (LCID version)
1096 /// </summary>
1097 public static CultureInfo GetCultureInfo(int culture)
1099 if (culture <= 0)
1101 throw new ArgumentOutOfRangeException(nameof(culture), SR.ArgumentOutOfRange_NeedPosNum);
1104 Dictionary<int, CultureInfo> lcidTable = CachedCulturesByLcid;
1105 CultureInfo? result;
1107 lock (lcidTable)
1109 if (lcidTable.TryGetValue(culture, out result))
1111 return result;
1117 result = new CultureInfo(culture, useUserOverride: false) { _isReadOnly = true };
1119 catch (ArgumentException)
1121 throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported);
1124 lock (lcidTable)
1126 lcidTable[culture] = result;
1129 return result;
1132 /// <summary>
1133 /// Gets a cached copy of the specified culture from an internal
1134 /// hashtable (or creates it if not found). (Named version)
1135 /// </summary>
1136 public static CultureInfo GetCultureInfo(string name)
1138 // Make sure we have a valid, non-zero length string as name
1139 if (name is null)
1141 throw new ArgumentNullException(nameof(name));
1144 name = CultureData.AnsiToLower(name);
1145 Dictionary<string, CultureInfo> nameTable = CachedCulturesByName;
1146 CultureInfo? result;
1148 lock (nameTable)
1150 if (nameTable.TryGetValue(name, out result))
1152 return result;
1156 result = CreateCultureInfoNoThrow(name, useUserOverride: false) ??
1157 throw new CultureNotFoundException(nameof(name), name, SR.Argument_CultureNotSupported);
1158 result._isReadOnly = true;
1160 // Remember our name as constructed. Do NOT use alternate sort name versions because
1161 // we have internal state representing the sort (so someone would get the wrong cached version).
1162 name = CultureData.AnsiToLower(result._name);
1164 lock (nameTable)
1166 nameTable[name] = result;
1169 return result;
1172 /// <summary>
1173 /// Gets a cached copy of the specified culture from an internal
1174 /// hashtable (or creates it if not found).
1175 /// </summary>
1176 public static CultureInfo GetCultureInfo(string name, string altName)
1178 if (name is null)
1180 throw new ArgumentNullException(nameof(name));
1182 if (altName is null)
1184 throw new ArgumentNullException(nameof(altName));
1187 name = CultureData.AnsiToLower(name);
1188 altName = CultureData.AnsiToLower(altName);
1189 string nameAndAltName = name + "\xfffd" + altName;
1190 Dictionary<string, CultureInfo> nameTable = CachedCulturesByName;
1191 CultureInfo? result;
1193 lock (nameTable)
1195 if (nameTable.TryGetValue(nameAndAltName, out result))
1197 return result;
1203 result = new CultureInfo(name, altName) { _isReadOnly = true };
1204 result.TextInfo.SetReadOnlyState(readOnly: true); // TextInfo object is already created; we need to set it as read only.
1206 catch (ArgumentException)
1208 throw new CultureNotFoundException("name/altName", SR.Format(SR.Argument_OneOfCulturesNotSupported, name, altName));
1211 lock (nameTable)
1213 nameTable[nameAndAltName] = result;
1216 return result;
1219 private static Dictionary<string, CultureInfo> CachedCulturesByName
1223 Dictionary<string, CultureInfo>? cache = s_cachedCulturesByName;
1224 if (cache is null)
1226 cache = new Dictionary<string, CultureInfo>();
1227 cache = Interlocked.CompareExchange(ref s_cachedCulturesByName, cache, null) ?? cache;
1230 return cache;
1234 private static Dictionary<int, CultureInfo> CachedCulturesByLcid
1238 Dictionary<int, CultureInfo>? cache = s_cachedCulturesByLcid;
1239 if (cache is null)
1241 cache = new Dictionary<int, CultureInfo>();
1242 cache = Interlocked.CompareExchange(ref s_cachedCulturesByLcid, cache, null) ?? cache;
1245 return cache;
1249 public static CultureInfo GetCultureInfoByIetfLanguageTag(string name)
1251 // Disallow old zh-CHT/zh-CHS names
1252 if (name == "zh-CHT" || name == "zh-CHS")
1254 throw new CultureNotFoundException(nameof(name), SR.Format(SR.Argument_CultureIetfNotSupported, name));
1257 CultureInfo ci = GetCultureInfo(name);
1259 // Disallow alt sorts and es-es_TS
1260 if (ci.LCID > 0xffff || ci.LCID == 0x040a)
1262 throw new CultureNotFoundException(nameof(name), SR.Format(SR.Argument_CultureIetfNotSupported, name));
1265 return ci;