Fix IDE0025 (use expression body for properties)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Globalization / CultureInfo.cs
blob245bf4953e462dc56617010850ff70f5dab32564
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 readonly object _lock = new object();
138 private static volatile Dictionary<string, CultureInfo>? s_NameCachedCultures;
139 private static volatile Dictionary<int, CultureInfo>? s_LcidCachedCultures;
141 // The parent culture.
142 private CultureInfo? _parent;
144 // LOCALE constants of interest to us internally and privately for LCID functions
145 // (ie: avoid using these and use names if possible)
146 internal const int LOCALE_NEUTRAL = 0x0000;
147 private const int LOCALE_USER_DEFAULT = 0x0400;
148 private const int LOCALE_SYSTEM_DEFAULT = 0x0800;
149 internal const int LOCALE_CUSTOM_UNSPECIFIED = 0x1000;
150 internal const int LOCALE_CUSTOM_DEFAULT = 0x0c00;
151 internal const int LOCALE_INVARIANT = 0x007F;
153 private static CultureInfo InitializeUserDefaultCulture()
155 Interlocked.CompareExchange(ref s_userDefaultCulture, GetUserDefaultCulture(), null);
156 return s_userDefaultCulture!;
159 private static CultureInfo InitializeUserDefaultUICulture()
161 Interlocked.CompareExchange(ref s_userDefaultUICulture, GetUserDefaultUICulture(), null);
162 return s_userDefaultUICulture!;
165 public CultureInfo(string name) : this(name, true)
169 public CultureInfo(string name, bool useUserOverride)
171 if (name == null)
173 throw new ArgumentNullException(nameof(name));
176 // Get our data providing record
177 CultureData? cultureData = CultureData.GetCultureData(name, useUserOverride);
179 if (cultureData == null)
181 throw new CultureNotFoundException(nameof(name), name, SR.Argument_CultureNotSupported);
184 _cultureData = cultureData;
185 _name = _cultureData.CultureName;
186 _isInherited = GetType() != typeof(CultureInfo);
189 private CultureInfo(CultureData cultureData, bool isReadOnly = false)
191 Debug.Assert(cultureData != null);
192 _cultureData = cultureData;
193 _name = cultureData.CultureName;
194 _isInherited = false;
195 _isReadOnly = isReadOnly;
198 private static CultureInfo? CreateCultureInfoNoThrow(string name, bool useUserOverride)
200 Debug.Assert(name != null);
201 CultureData? cultureData = CultureData.GetCultureData(name, useUserOverride);
202 if (cultureData == null)
204 return null;
207 return new CultureInfo(cultureData);
210 public CultureInfo(int culture) : this(culture, true)
214 public CultureInfo(int culture, bool useUserOverride)
216 // We don't check for other invalid LCIDS here...
217 if (culture < 0)
219 throw new ArgumentOutOfRangeException(nameof(culture), SR.ArgumentOutOfRange_NeedPosNum);
222 switch (culture)
224 case LOCALE_CUSTOM_DEFAULT:
225 case LOCALE_SYSTEM_DEFAULT:
226 case LOCALE_NEUTRAL:
227 case LOCALE_USER_DEFAULT:
228 case LOCALE_CUSTOM_UNSPECIFIED:
229 // Can't support unknown custom cultures and we do not support neutral or
230 // non-custom user locales.
231 throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported);
232 default:
233 // Now see if this LCID is supported in the system default CultureData table.
234 _cultureData = CultureData.GetCultureData(culture, useUserOverride);
235 break;
237 _isInherited = GetType() != typeof(CultureInfo);
238 _name = _cultureData.CultureName;
241 /// <summary>
242 /// Constructor called by SQL Server's special munged culture - creates a culture with
243 /// a TextInfo and CompareInfo that come from a supplied alternate source. This object
244 /// is ALWAYS read-only.
245 /// Note that we really cannot use an LCID version of this override as the cached
246 /// name we create for it has to include both names, and the logic for this is in
247 /// the GetCultureInfo override *only*.
248 /// </summary>
249 internal CultureInfo(string cultureName, string textAndCompareCultureName)
251 if (cultureName == null)
253 throw new ArgumentNullException(nameof(cultureName), SR.ArgumentNull_String);
256 CultureData? cultureData = CultureData.GetCultureData(cultureName, false);
257 if (cultureData == null)
259 throw new CultureNotFoundException(nameof(cultureName), cultureName, SR.Argument_CultureNotSupported);
262 _cultureData = cultureData;
264 _name = _cultureData.CultureName;
266 CultureInfo altCulture = GetCultureInfo(textAndCompareCultureName);
267 _compareInfo = altCulture.CompareInfo;
268 _textInfo = altCulture.TextInfo;
271 /// <summary>
272 /// We do this to try to return the system UI language and the default user languages
273 /// This method will fallback if this fails (like Invariant)
274 /// </summary>
275 private static CultureInfo GetCultureByName(string name)
279 return new CultureInfo(name)
281 _isReadOnly = true
284 catch (ArgumentException)
286 return InvariantCulture;
290 /// <summary>
291 /// Return a specific culture. A tad irrelevent now since we always
292 /// return valid data for neutral locales.
294 /// Note that there's interesting behavior that tries to find a
295 /// smaller name, ala RFC4647, if we can't find a bigger name.
296 /// That doesn't help with things like "zh" though, so the approach
297 /// is of questionable value
298 /// </summary>
299 public static CultureInfo CreateSpecificCulture(string name)
301 CultureInfo? culture;
305 culture = new CultureInfo(name);
307 catch (ArgumentException)
309 // When CultureInfo throws this exception, it may be because someone passed the form
310 // like "az-az" because it came out of an http accept lang. We should try a little
311 // parsing to perhaps fall back to "az" here and use *it* to create the neutral.
312 culture = null;
313 for (int idx = 0; idx < name.Length; idx++)
315 if ('-' == name[idx])
319 culture = new CultureInfo(name.Substring(0, idx));
320 break;
322 catch (ArgumentException)
324 // throw the original exception so the name in the string will be right
325 throw;
330 if (culture == null)
332 // nothing to save here; throw the original exception
333 throw;
337 // In the most common case, they've given us a specific culture, so we'll just return that.
338 if (!(culture.IsNeutralCulture))
340 return culture;
343 return new CultureInfo(culture._cultureData.SpecificCultureName);
346 internal static bool VerifyCultureName(string cultureName, bool throwException)
348 // This function is used by ResourceManager.GetResourceFileName().
349 // ResourceManager searches for resource using CultureInfo.Name,
350 // so we should check against CultureInfo.Name.
351 for (int i = 0; i < cultureName.Length; i++)
353 char c = cultureName[i];
354 // TODO: Names can only be RFC4646 names (ie: a-zA-Z0-9) while this allows any unicode letter/digit
355 if (char.IsLetterOrDigit(c) || c == '-' || c == '_')
357 continue;
359 if (throwException)
361 throw new ArgumentException(SR.Format(SR.Argument_InvalidResourceCultureName, cultureName));
363 return false;
365 return true;
368 internal static bool VerifyCultureName(CultureInfo culture, bool throwException)
370 // If we have an instance of one of our CultureInfos, the user can't have changed the
371 // name and we know that all names are valid in files.
372 if (!culture._isInherited)
374 return true;
377 return VerifyCultureName(culture.Name, throwException);
380 /// <summary>
381 /// This instance provides methods based on the current user settings.
382 /// These settings are volatile and may change over the lifetime of the
383 /// thread.
384 /// </summary>
385 /// <remarks>
386 /// We use the following order to return CurrentCulture and CurrentUICulture
387 /// o Use WinRT to return the current user profile language
388 /// o use current thread culture if the user already set one using CurrentCulture/CurrentUICulture
389 /// o use thread culture if the user already set one using DefaultThreadCurrentCulture
390 /// or DefaultThreadCurrentUICulture
391 /// o Use NLS default user culture
392 /// o Use NLS default system culture
393 /// o Use Invariant culture
394 /// </remarks>
395 public static CultureInfo CurrentCulture
399 #if ENABLE_WINRT
400 WinRTInteropCallbacks callbacks = WinRTInterop.UnsafeCallbacks;
401 if (callbacks != null && callbacks.IsAppxModel())
403 return (CultureInfo)callbacks.GetUserDefaultCulture();
405 #endif
406 #if FEATURE_APPX
407 if (ApplicationModel.IsUap)
409 CultureInfo? culture = GetCultureInfoForUserPreferredLanguageInAppX();
410 if (culture != null)
411 return culture;
413 #endif
415 return s_currentThreadCulture ??
416 s_DefaultThreadCurrentCulture ??
417 s_userDefaultCulture ??
418 InitializeUserDefaultCulture();
422 if (value == null)
424 throw new ArgumentNullException(nameof(value));
427 #if ENABLE_WINRT
428 WinRTInteropCallbacks callbacks = WinRTInterop.UnsafeCallbacks;
429 if (callbacks != null && callbacks.IsAppxModel())
431 callbacks.SetGlobalDefaultCulture(value);
432 return;
434 #endif
435 #if FEATURE_APPX
436 if (ApplicationModel.IsUap)
438 if (SetCultureInfoForUserPreferredLanguageInAppX(value))
440 // successfully set the culture, otherwise fallback to legacy path
441 return;
444 #endif
446 if (s_asyncLocalCurrentCulture == null)
448 Interlocked.CompareExchange(ref s_asyncLocalCurrentCulture, new AsyncLocal<CultureInfo>(AsyncLocalSetCurrentCulture), null);
450 s_asyncLocalCurrentCulture!.Value = value;
454 public static CultureInfo CurrentUICulture
458 #if ENABLE_WINRT
459 WinRTInteropCallbacks callbacks = WinRTInterop.UnsafeCallbacks;
460 if (callbacks != null && callbacks.IsAppxModel())
462 return (CultureInfo)callbacks.GetUserDefaultCulture();
464 #endif
465 #if FEATURE_APPX
466 if (ApplicationModel.IsUap)
468 CultureInfo? culture = GetCultureInfoForUserPreferredLanguageInAppX();
469 if (culture != null)
470 return culture;
472 #endif
474 return s_currentThreadUICulture ??
475 s_DefaultThreadCurrentUICulture ??
476 UserDefaultUICulture;
480 if (value == null)
482 throw new ArgumentNullException(nameof(value));
485 CultureInfo.VerifyCultureName(value, true);
487 #if ENABLE_WINRT
488 WinRTInteropCallbacks callbacks = WinRTInterop.UnsafeCallbacks;
489 if (callbacks != null && callbacks.IsAppxModel())
491 callbacks.SetGlobalDefaultCulture(value);
492 return;
494 #endif
495 #if FEATURE_APPX
496 if (ApplicationModel.IsUap)
498 if (SetCultureInfoForUserPreferredLanguageInAppX(value))
500 // successfully set the culture, otherwise fallback to legacy path
501 return;
504 #endif
506 if (s_asyncLocalCurrentUICulture == null)
508 Interlocked.CompareExchange(ref s_asyncLocalCurrentUICulture, new AsyncLocal<CultureInfo>(AsyncLocalSetCurrentUICulture), null);
511 // this one will set s_currentThreadUICulture too
512 s_asyncLocalCurrentUICulture!.Value = value;
516 internal static CultureInfo UserDefaultUICulture => s_userDefaultUICulture ?? InitializeUserDefaultUICulture();
518 public static CultureInfo InstalledUICulture => s_userDefaultCulture ?? InitializeUserDefaultCulture();
520 public static CultureInfo? DefaultThreadCurrentCulture
522 get => s_DefaultThreadCurrentCulture;
525 // If you add pre-conditions to this method, check to see if you also need to
526 // add them to Thread.CurrentCulture.set.
527 s_DefaultThreadCurrentCulture = value;
531 public static CultureInfo? DefaultThreadCurrentUICulture
533 get => s_DefaultThreadCurrentUICulture;
536 // If they're trying to use a Culture with a name that we can't use in resource lookup,
537 // don't even let them set it on the thread.
539 // If you add more pre-conditions to this method, check to see if you also need to
540 // add them to Thread.CurrentUICulture.set.
542 if (value != null)
544 CultureInfo.VerifyCultureName(value, true);
547 s_DefaultThreadCurrentUICulture = value;
551 /// <summary>
552 /// This instance provides methods, for example for casing and sorting,
553 /// that are independent of the system and current user settings. It
554 /// should be used only by processes such as some system services that
555 /// require such invariant results (eg. file systems). In general,
556 /// the results are not linguistically correct and do not match any
557 /// culture info.
558 /// </summary>
559 public static CultureInfo InvariantCulture
563 Debug.Assert(s_InvariantCultureInfo != null);
564 return s_InvariantCultureInfo;
568 /// <summary>
569 /// Return the parent CultureInfo for the current instance.
570 /// </summary>
571 public virtual CultureInfo Parent
575 if (_parent == null)
577 CultureInfo culture;
578 string parentName = _cultureData.ParentName;
580 if (string.IsNullOrEmpty(parentName))
582 culture = InvariantCulture;
584 else
586 culture = CreateCultureInfoNoThrow(parentName, _cultureData.UseUserOverride) ??
587 // For whatever reason our IPARENT or SPARENT wasn't correct, so use invariant
588 // We can't allow ourselves to fail. In case of custom cultures the parent of the
589 // current custom culture isn't installed.
590 InvariantCulture;
593 Interlocked.CompareExchange<CultureInfo?>(ref _parent, culture, null);
595 return _parent!;
599 public virtual int LCID => _cultureData.LCID;
601 public virtual int KeyboardLayoutId => _cultureData.KeyboardLayoutId;
603 public static CultureInfo[] GetCultures(CultureTypes types)
605 // internally we treat UserCustomCultures as Supplementals but v2
606 // treats as Supplementals and Replacements
607 if ((types & CultureTypes.UserCustomCulture) == CultureTypes.UserCustomCulture)
609 types |= CultureTypes.ReplacementCultures;
611 return CultureData.GetCultures(types);
614 /// <summary>
615 /// Returns the full name of the CultureInfo. The name is in format like
616 /// "en-US" This version does NOT include sort information in the name.
617 /// </summary>
618 public virtual string Name
622 // We return non sorting name here.
623 if (_nonSortName == null)
625 _nonSortName = _cultureData.Name ?? string.Empty;
627 return _nonSortName;
631 /// <summary>
632 /// This one has the sort information (ie: de-DE_phoneb)
633 /// </summary>
634 internal string SortName
638 if (_sortName == null)
640 _sortName = _cultureData.SortName;
643 return _sortName;
647 public string IetfLanguageTag =>
648 // special case the compatibility cultures
649 Name switch
651 "zh-CHT" => "zh-Hant",
652 "zh-CHS" => "zh-Hans",
653 _ => Name,
656 /// <summary>
657 /// Returns the full name of the CultureInfo in the localized language.
658 /// For example, if the localized language of the runtime is Spanish and the CultureInfo is
659 /// US English, "Ingles (Estados Unidos)" will be returned.
660 /// </summary>
661 public virtual string DisplayName
665 Debug.Assert(_name != null, "[CultureInfo.DisplayName] Always expect _name to be set");
666 return _cultureData.DisplayName;
670 /// <summary>
671 /// Returns the full name of the CultureInfo in the native language.
672 /// For example, if the CultureInfo is US English, "English
673 /// (United States)" will be returned.
674 /// </summary>
675 public virtual string NativeName => _cultureData.NativeName;
677 /// <summary>
678 /// Returns the full name of the CultureInfo in English.
679 /// For example, if the CultureInfo is US English, "English
680 /// (United States)" will be returned.
681 /// </summary>
682 public virtual string EnglishName => _cultureData.EnglishName;
684 /// <summary>
685 /// ie: en
686 /// </summary>
687 public virtual string TwoLetterISOLanguageName => _cultureData.TwoLetterISOLanguageName;
689 /// <summary>
690 /// ie: eng
691 /// </summary>
692 public virtual string ThreeLetterISOLanguageName => _cultureData.ThreeLetterISOLanguageName;
694 /// <summary>
695 /// Returns the 3 letter windows language name for the current instance. eg: "ENU"
696 /// The ISO names are much preferred
697 /// </summary>
698 public virtual string ThreeLetterWindowsLanguageName => _cultureData.ThreeLetterWindowsLanguageName;
700 // CompareInfo Read-Only Property
701 /// <summary>
702 /// Gets the CompareInfo for this culture.
703 /// </summary>
704 public virtual CompareInfo CompareInfo
708 if (_compareInfo == null)
710 // Since CompareInfo's don't have any overrideable properties, get the CompareInfo from
711 // the Non-Overridden CultureInfo so that we only create one CompareInfo per culture
712 _compareInfo = UseUserOverride
713 ? GetCultureInfo(_name).CompareInfo
714 : new CompareInfo(this);
716 return _compareInfo;
720 /// <summary>
721 /// Gets the TextInfo for this culture.
722 /// </summary>
723 public virtual TextInfo TextInfo
727 if (_textInfo == null)
729 // Make a new textInfo
730 TextInfo tempTextInfo = new TextInfo(_cultureData);
731 tempTextInfo.SetReadOnlyState(_isReadOnly);
732 _textInfo = tempTextInfo;
734 return _textInfo;
738 public override bool Equals(object? value)
740 if (object.ReferenceEquals(this, value))
742 return true;
745 if (value is CultureInfo that)
747 // using CompareInfo to verify the data passed through the constructor
748 // CultureInfo(String cultureName, String textAndCompareCultureName)
749 return Name.Equals(that.Name) && CompareInfo.Equals(that.CompareInfo);
752 return false;
755 public override int GetHashCode()
757 return Name.GetHashCode() + CompareInfo.GetHashCode();
761 /// <summary>
762 /// Implements object.ToString(). Returns the name of the CultureInfo,
763 /// eg. "de-DE_phoneb", "en-US", or "fj-FJ".
764 /// </summary>
765 public override string ToString() => _name;
767 public virtual object? GetFormat(Type? formatType)
769 if (formatType == typeof(NumberFormatInfo))
771 return NumberFormat;
773 if (formatType == typeof(DateTimeFormatInfo))
775 return DateTimeFormat;
778 return null;
781 public virtual bool IsNeutralCulture => _cultureData.IsNeutralCulture;
783 public CultureTypes CultureTypes
787 CultureTypes types = 0;
789 if (_cultureData.IsNeutralCulture)
791 types |= CultureTypes.NeutralCultures;
793 else
795 types |= CultureTypes.SpecificCultures;
798 types |= _cultureData.IsWin32Installed ? CultureTypes.InstalledWin32Cultures : 0;
800 // Disable warning 618: System.Globalization.CultureTypes.FrameworkCultures' is obsolete
801 #pragma warning disable 618
802 types |= _cultureData.IsFramework ? CultureTypes.FrameworkCultures : 0;
803 #pragma warning restore 618
805 types |= _cultureData.IsSupplementalCustomCulture ? CultureTypes.UserCustomCulture : 0;
806 types |= _cultureData.IsReplacementCulture ? CultureTypes.ReplacementCultures | CultureTypes.UserCustomCulture : 0;
808 return types;
812 public virtual NumberFormatInfo NumberFormat
816 if (_numInfo == null)
818 NumberFormatInfo temp = new NumberFormatInfo(_cultureData);
819 temp._isReadOnly = _isReadOnly;
820 Interlocked.CompareExchange(ref _numInfo, temp, null);
822 return _numInfo!;
826 if (value == null)
828 throw new ArgumentNullException(nameof(value));
831 VerifyWritable();
832 _numInfo = value;
836 /// <summary>
837 /// Create a DateTimeFormatInfo, and fill in the properties according to
838 /// the CultureID.
839 /// </summary>
840 public virtual DateTimeFormatInfo DateTimeFormat
844 if (_dateTimeInfo == null)
846 // Change the calendar of DTFI to the specified calendar of this CultureInfo.
847 DateTimeFormatInfo temp = new DateTimeFormatInfo(_cultureData, this.Calendar);
848 temp._isReadOnly = _isReadOnly;
849 Interlocked.CompareExchange(ref _dateTimeInfo, temp, null);
851 return _dateTimeInfo!;
856 if (value == null)
858 throw new ArgumentNullException(nameof(value));
861 VerifyWritable();
862 _dateTimeInfo = value;
866 public void ClearCachedData()
868 // reset the default culture values
869 s_userDefaultCulture = GetUserDefaultCulture();
870 s_userDefaultUICulture = GetUserDefaultUICulture();
872 RegionInfo.s_currentRegionInfo = null;
873 #pragma warning disable 0618 // disable the obsolete warning
874 TimeZone.ResetTimeZone();
875 #pragma warning restore 0618
876 TimeZoneInfo.ClearCachedData();
877 s_LcidCachedCultures = null;
878 s_NameCachedCultures = null;
880 CultureData.ClearCachedData();
883 /// <summary>
884 /// Map a Win32 CALID to an instance of supported calendar.
885 /// </summary>
886 /// <remarks>
887 /// Shouldn't throw exception since the calType value is from our data
888 /// table or from Win32 registry.
889 /// If we are in trouble (like getting a weird value from Win32
890 /// registry), just return the GregorianCalendar.
891 /// </remarks>
892 internal static Calendar GetCalendarInstance(CalendarId calType)
894 if (calType == CalendarId.GREGORIAN)
896 return new GregorianCalendar();
899 return GetCalendarInstanceRare(calType);
902 /// <summary>
903 /// This function exists as a shortcut to prevent us from loading all of the non-gregorian
904 /// calendars unless they're required.
905 /// </summary>
906 internal static Calendar GetCalendarInstanceRare(CalendarId calType)
908 Debug.Assert(calType != CalendarId.GREGORIAN, "calType!=CalendarId.GREGORIAN");
910 switch (calType)
912 case CalendarId.GREGORIAN_US: // Gregorian (U.S.) calendar
913 case CalendarId.GREGORIAN_ME_FRENCH: // Gregorian Middle East French calendar
914 case CalendarId.GREGORIAN_ARABIC: // Gregorian Arabic calendar
915 case CalendarId.GREGORIAN_XLIT_ENGLISH: // Gregorian Transliterated English calendar
916 case CalendarId.GREGORIAN_XLIT_FRENCH: // Gregorian Transliterated French calendar
917 return new GregorianCalendar((GregorianCalendarTypes)calType);
918 case CalendarId.TAIWAN: // Taiwan Era calendar
919 return new TaiwanCalendar();
920 case CalendarId.JAPAN: // Japanese Emperor Era calendar
921 return new JapaneseCalendar();
922 case CalendarId.KOREA: // Korean Tangun Era calendar
923 return new KoreanCalendar();
924 case CalendarId.THAI: // Thai calendar
925 return new ThaiBuddhistCalendar();
926 case CalendarId.HIJRI: // Hijri (Arabic Lunar) calendar
927 return new HijriCalendar();
928 case CalendarId.HEBREW: // Hebrew (Lunar) calendar
929 return new HebrewCalendar();
930 case CalendarId.UMALQURA:
931 return new UmAlQuraCalendar();
932 case CalendarId.PERSIAN:
933 return new PersianCalendar();
935 return new GregorianCalendar();
938 /// <summary>
939 /// Return/set the default calendar used by this culture.
940 /// This value can be overridden by regional option if this is a current culture.
941 /// </summary>
942 public virtual Calendar Calendar
946 if (_calendar == null)
948 Debug.Assert(_cultureData.CalendarIds.Length > 0, "_cultureData.CalendarIds.Length > 0");
949 // Get the default calendar for this culture. Note that the value can be
950 // from registry if this is a user default culture.
951 Calendar newObj = _cultureData.DefaultCalendar;
953 Interlocked.MemoryBarrier();
954 newObj.SetReadOnlyState(_isReadOnly);
955 _calendar = newObj;
957 return _calendar;
961 /// <summary>
962 /// Return an array of the optional calendar for this culture.
963 /// </summary>
964 public virtual Calendar[] OptionalCalendars
968 // This property always returns a new copy of the calendar array.
969 CalendarId[] calID = _cultureData.CalendarIds;
970 Calendar[] cals = new Calendar[calID.Length];
971 for (int i = 0; i < cals.Length; i++)
973 cals[i] = GetCalendarInstance(calID[i]);
975 return cals;
979 public bool UseUserOverride => _cultureData.UseUserOverride;
981 public CultureInfo GetConsoleFallbackUICulture()
983 CultureInfo? temp = _consoleFallbackCulture;
984 if (temp == null)
986 temp = CreateSpecificCulture(_cultureData.SCONSOLEFALLBACKNAME);
987 temp._isReadOnly = true;
988 _consoleFallbackCulture = temp;
990 return temp;
993 public virtual object Clone()
995 CultureInfo ci = (CultureInfo)MemberwiseClone();
996 ci._isReadOnly = false;
998 // If this is exactly our type, we can make certain optimizations so that we don't allocate NumberFormatInfo or DTFI unless
999 // they've already been allocated. If this is a derived type, we'll take a more generic codepath.
1000 if (!_isInherited)
1002 if (_dateTimeInfo != null)
1004 ci._dateTimeInfo = (DateTimeFormatInfo)_dateTimeInfo.Clone();
1006 if (_numInfo != null)
1008 ci._numInfo = (NumberFormatInfo)_numInfo.Clone();
1011 else
1013 ci.DateTimeFormat = (DateTimeFormatInfo)this.DateTimeFormat.Clone();
1014 ci.NumberFormat = (NumberFormatInfo)this.NumberFormat.Clone();
1017 if (_textInfo != null)
1019 ci._textInfo = (TextInfo)_textInfo.Clone();
1022 if (_calendar != null)
1024 ci._calendar = (Calendar)_calendar.Clone();
1027 return ci;
1030 public static CultureInfo ReadOnly(CultureInfo ci)
1032 if (ci == null)
1034 throw new ArgumentNullException(nameof(ci));
1037 if (ci.IsReadOnly)
1039 return ci;
1041 CultureInfo newInfo = (CultureInfo)(ci.MemberwiseClone());
1043 if (!ci.IsNeutralCulture)
1045 // If this is exactly our type, we can make certain optimizations so that we don't allocate NumberFormatInfo or DTFI unless
1046 // they've already been allocated. If this is a derived type, we'll take a more generic codepath.
1047 if (!ci._isInherited)
1049 if (ci._dateTimeInfo != null)
1051 newInfo._dateTimeInfo = DateTimeFormatInfo.ReadOnly(ci._dateTimeInfo);
1053 if (ci._numInfo != null)
1055 newInfo._numInfo = NumberFormatInfo.ReadOnly(ci._numInfo);
1058 else
1060 newInfo.DateTimeFormat = DateTimeFormatInfo.ReadOnly(ci.DateTimeFormat);
1061 newInfo.NumberFormat = NumberFormatInfo.ReadOnly(ci.NumberFormat);
1065 if (ci._textInfo != null)
1067 newInfo._textInfo = TextInfo.ReadOnly(ci._textInfo);
1070 if (ci._calendar != null)
1072 newInfo._calendar = Calendar.ReadOnly(ci._calendar);
1075 // Don't set the read-only flag too early.
1076 // We should set the read-only flag here. Otherwise, info.DateTimeFormat will not be able to set.
1077 newInfo._isReadOnly = true;
1079 return newInfo;
1083 public bool IsReadOnly => _isReadOnly;
1085 private void VerifyWritable()
1087 if (_isReadOnly)
1089 throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
1093 /// <summary>
1094 /// For resource lookup, we consider a culture the invariant culture by name equality.
1095 /// We perform this check frequently during resource lookup, so adding a property for
1096 /// improved readability.
1097 /// </summary>
1098 internal bool HasInvariantCultureName => Name == InvariantCulture.Name;
1100 /// <summary>
1101 /// Helper function overloads of GetCachedReadOnlyCulture. If lcid is 0, we use the name.
1102 /// If lcid is -1, use the altName and create one of those special SQL cultures.
1103 /// </summary>
1104 internal static CultureInfo? GetCultureInfoHelper(int lcid, string? name, string? altName)
1106 // retval is our return value.
1107 CultureInfo? retval;
1109 // Temporary hashtable for the names.
1110 Dictionary<string, CultureInfo>? tempNameHT = s_NameCachedCultures;
1112 if (name != null)
1114 name = CultureData.AnsiToLower(name);
1117 if (altName != null)
1119 altName = CultureData.AnsiToLower(altName);
1122 // We expect the same result for both hashtables, but will test individually for added safety.
1123 if (tempNameHT == null)
1125 tempNameHT = new Dictionary<string, CultureInfo>();
1127 else
1129 // If we are called by name, check if the object exists in the hashtable. If so, return it.
1130 if (lcid == -1 || lcid == 0)
1132 Debug.Assert(name != null && (lcid != -1 || altName != null));
1133 bool ret;
1134 lock (_lock)
1136 ret = tempNameHT.TryGetValue(lcid == 0 ? name! : name! + '\xfffd' + altName!, out retval);
1139 if (ret && retval != null)
1141 return retval;
1146 // Next, the Lcid table.
1147 Dictionary<int, CultureInfo>? tempLcidHT = s_LcidCachedCultures;
1149 if (tempLcidHT == null)
1151 // Case insensitive is not an issue here, save the constructor call.
1152 tempLcidHT = new Dictionary<int, CultureInfo>();
1154 else
1156 // If we were called by Lcid, check if the object exists in the table. If so, return it.
1157 if (lcid > 0)
1159 bool ret;
1160 lock (_lock)
1162 ret = tempLcidHT.TryGetValue(lcid, out retval);
1164 if (ret && retval != null)
1166 return retval;
1171 // We now have two temporary hashtables and the desired object was not found.
1172 // We'll construct it. We catch any exceptions from the constructor call and return null.
1175 switch (lcid)
1177 case -1:
1178 // call the private constructor
1179 Debug.Assert(name != null && altName != null);
1180 retval = new CultureInfo(name!, altName!);
1181 break;
1183 case 0:
1184 Debug.Assert(name != null);
1185 retval = new CultureInfo(name!, false);
1186 break;
1188 default:
1189 retval = new CultureInfo(lcid, false);
1190 break;
1193 catch (ArgumentException)
1195 return null;
1198 // Set it to read-only
1199 retval._isReadOnly = true;
1201 if (lcid == -1)
1203 lock (_lock)
1205 // This new culture will be added only to the name hash table.
1206 tempNameHT[name + '\xfffd' + altName] = retval;
1208 // when lcid == -1 then TextInfo object is already get created and we need to set it as read only.
1209 retval.TextInfo.SetReadOnlyState(true);
1211 else if (lcid == 0)
1213 // Remember our name (as constructed). Do NOT use alternate sort name versions because
1214 // we have internal state representing the sort. (So someone would get the wrong cached version)
1215 string newName = CultureData.AnsiToLower(retval._name);
1217 // We add this new culture info object to both tables.
1218 lock (_lock)
1220 tempNameHT[newName] = retval;
1223 else
1225 lock (_lock)
1227 tempLcidHT[lcid] = retval;
1231 // Copy the two hashtables to the corresponding member variables. This will potentially overwrite
1232 // new tables simultaneously created by a new thread, but maximizes thread safety.
1233 if (-1 != lcid)
1235 // Only when we modify the lcid hash table, is there a need to overwrite.
1236 s_LcidCachedCultures = tempLcidHT;
1239 s_NameCachedCultures = tempNameHT;
1241 // Finally, return our new CultureInfo object.
1242 return retval;
1245 /// <summary>
1246 /// Gets a cached copy of the specified culture from an internal
1247 /// hashtable (or creates it if not found). (LCID version)
1248 /// </summary>
1249 public static CultureInfo GetCultureInfo(int culture)
1251 // Must check for -1 now since the helper function uses the value to signal
1252 // the altCulture code path for SQL Server.
1253 // Also check for zero as this would fail trying to add as a key to the hash.
1254 if (culture <= 0)
1256 throw new ArgumentOutOfRangeException(nameof(culture), SR.ArgumentOutOfRange_NeedPosNum);
1258 CultureInfo? retval = GetCultureInfoHelper(culture, null, null);
1259 if (null == retval)
1261 throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported);
1263 return retval;
1266 /// <summary>
1267 /// Gets a cached copy of the specified culture from an internal
1268 /// hashtable (or creates it if not found). (Named version)
1269 /// </summary>
1270 public static CultureInfo GetCultureInfo(string name)
1272 // Make sure we have a valid, non-zero length string as name
1273 if (name == null)
1275 throw new ArgumentNullException(nameof(name));
1278 CultureInfo? retval = GetCultureInfoHelper(0, name, null);
1279 if (retval == null)
1281 throw new CultureNotFoundException(
1282 nameof(name), name, SR.Argument_CultureNotSupported);
1284 return retval;
1287 /// <summary>
1288 /// Gets a cached copy of the specified culture from an internal
1289 /// hashtable (or creates it if not found).
1290 /// </summary>
1291 public static CultureInfo GetCultureInfo(string name, string altName)
1293 if (name == null)
1295 throw new ArgumentNullException(nameof(name));
1297 if (altName == null)
1299 throw new ArgumentNullException(nameof(altName));
1302 CultureInfo? retval = GetCultureInfoHelper(-1, name, altName);
1303 if (retval == null)
1305 throw new CultureNotFoundException("name or altName",
1306 SR.Format(SR.Argument_OneOfCulturesNotSupported, name, altName));
1308 return retval;
1311 public static CultureInfo GetCultureInfoByIetfLanguageTag(string name)
1313 // Disallow old zh-CHT/zh-CHS names
1314 if (name == "zh-CHT" || name == "zh-CHS")
1316 throw new CultureNotFoundException(nameof(name), SR.Format(SR.Argument_CultureIetfNotSupported, name));
1319 CultureInfo ci = GetCultureInfo(name);
1321 // Disallow alt sorts and es-es_TS
1322 if (ci.LCID > 0xffff || ci.LCID == 0x040a)
1324 throw new CultureNotFoundException(nameof(name), SR.Format(SR.Argument_CultureIetfNotSupported, name));
1327 return ci;