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 ////////////////////////////////////////////////////////////////////////////
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
;
33 using Internal
.Runtime
.Augments
;
36 namespace System
.Globalization
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.
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.
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)
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
;
120 private static CultureInfo
? s_currentThreadCulture
;
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
)
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)
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...
219 throw new ArgumentOutOfRangeException(nameof(culture
), SR
.ArgumentOutOfRange_NeedPosNum
);
224 case LOCALE_CUSTOM_DEFAULT
:
225 case LOCALE_SYSTEM_DEFAULT
:
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
);
233 // Now see if this LCID is supported in the system default CultureData table.
234 _cultureData
= CultureData
.GetCultureData(culture
, useUserOverride
);
237 _isInherited
= GetType() != typeof(CultureInfo
);
238 _name
= _cultureData
.CultureName
;
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*.
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
;
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)
275 private static CultureInfo
GetCultureByName(string name
)
279 return new CultureInfo(name
)
284 catch (ArgumentException
)
286 return InvariantCulture
;
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
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.
313 for (int idx
= 0; idx
< name
.Length
; idx
++)
315 if ('-' == name
[idx
])
319 culture
= new CultureInfo(name
.Substring(0, idx
));
322 catch (ArgumentException
)
324 // throw the original exception so the name in the string will be right
332 // nothing to save here; throw the original exception
337 // In the most common case, they've given us a specific culture, so we'll just return that.
338 if (!(culture
.IsNeutralCulture
))
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
== '_')
361 throw new ArgumentException(SR
.Format(SR
.Argument_InvalidResourceCultureName
, cultureName
));
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
)
377 return VerifyCultureName(culture
.Name
, throwException
);
381 /// This instance provides methods based on the current user settings.
382 /// These settings are volatile and may change over the lifetime of the
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
395 public static CultureInfo CurrentCulture
400 WinRTInteropCallbacks callbacks
= WinRTInterop
.UnsafeCallbacks
;
401 if (callbacks
!= null && callbacks
.IsAppxModel())
403 return (CultureInfo
)callbacks
.GetUserDefaultCulture();
407 if (ApplicationModel
.IsUap
)
409 CultureInfo
? culture
= GetCultureInfoForUserPreferredLanguageInAppX();
415 return s_currentThreadCulture
??
416 s_DefaultThreadCurrentCulture
??
417 s_userDefaultCulture
??
418 InitializeUserDefaultCulture();
424 throw new ArgumentNullException(nameof(value));
428 WinRTInteropCallbacks callbacks
= WinRTInterop
.UnsafeCallbacks
;
429 if (callbacks
!= null && callbacks
.IsAppxModel())
431 callbacks
.SetGlobalDefaultCulture(value);
436 if (ApplicationModel
.IsUap
)
438 if (SetCultureInfoForUserPreferredLanguageInAppX(value))
440 // successfully set the culture, otherwise fallback to legacy path
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
459 WinRTInteropCallbacks callbacks
= WinRTInterop
.UnsafeCallbacks
;
460 if (callbacks
!= null && callbacks
.IsAppxModel())
462 return (CultureInfo
)callbacks
.GetUserDefaultCulture();
466 if (ApplicationModel
.IsUap
)
468 CultureInfo
? culture
= GetCultureInfoForUserPreferredLanguageInAppX();
474 return s_currentThreadUICulture
??
475 s_DefaultThreadCurrentUICulture
??
476 UserDefaultUICulture
;
482 throw new ArgumentNullException(nameof(value));
485 CultureInfo
.VerifyCultureName(value, true);
488 WinRTInteropCallbacks callbacks
= WinRTInterop
.UnsafeCallbacks
;
489 if (callbacks
!= null && callbacks
.IsAppxModel())
491 callbacks
.SetGlobalDefaultCulture(value);
496 if (ApplicationModel
.IsUap
)
498 if (SetCultureInfoForUserPreferredLanguageInAppX(value))
500 // successfully set the culture, otherwise fallback to legacy path
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.
544 CultureInfo
.VerifyCultureName(value, true);
547 s_DefaultThreadCurrentUICulture
= value;
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
559 public static CultureInfo InvariantCulture
563 Debug
.Assert(s_InvariantCultureInfo
!= null);
564 return s_InvariantCultureInfo
;
569 /// Return the parent CultureInfo for the current instance.
571 public virtual CultureInfo Parent
578 string parentName
= _cultureData
.ParentName
;
580 if (string.IsNullOrEmpty(parentName
))
582 culture
= InvariantCulture
;
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.
593 Interlocked
.CompareExchange
<CultureInfo
?>(ref _parent
, culture
, null);
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
);
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.
618 public virtual string Name
622 // We return non sorting name here.
623 if (_nonSortName
== null)
625 _nonSortName
= _cultureData
.Name
?? string.Empty
;
632 /// This one has the sort information (ie: de-DE_phoneb)
634 internal string SortName
638 if (_sortName
== null)
640 _sortName
= _cultureData
.SortName
;
647 public string IetfLanguageTag
=>
648 // special case the compatibility cultures
651 "zh-CHT" => "zh-Hant",
652 "zh-CHS" => "zh-Hans",
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.
661 public virtual string DisplayName
665 Debug
.Assert(_name
!= null, "[CultureInfo.DisplayName] Always expect _name to be set");
666 return _cultureData
.DisplayName
;
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.
675 public virtual string NativeName
=> _cultureData
.NativeName
;
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.
682 public virtual string EnglishName
=> _cultureData
.EnglishName
;
687 public virtual string TwoLetterISOLanguageName
=> _cultureData
.TwoLetterISOLanguageName
;
692 public virtual string ThreeLetterISOLanguageName
=> _cultureData
.ThreeLetterISOLanguageName
;
695 /// Returns the 3 letter windows language name for the current instance. eg: "ENU"
696 /// The ISO names are much preferred
698 public virtual string ThreeLetterWindowsLanguageName
=> _cultureData
.ThreeLetterWindowsLanguageName
;
700 // CompareInfo Read-Only Property
702 /// Gets the CompareInfo for this culture.
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);
721 /// Gets the TextInfo for this culture.
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
;
738 public override bool Equals(object? value)
740 if (object.ReferenceEquals(this, value))
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
);
755 public override int GetHashCode()
757 return Name
.GetHashCode() + CompareInfo
.GetHashCode();
762 /// Implements object.ToString(). Returns the name of the CultureInfo,
763 /// eg. "de-DE_phoneb", "en-US", or "fj-FJ".
765 public override string ToString() => _name
;
767 public virtual object? GetFormat(Type
? formatType
)
769 if (formatType
== typeof(NumberFormatInfo
))
773 if (formatType
== typeof(DateTimeFormatInfo
))
775 return DateTimeFormat
;
781 public virtual bool IsNeutralCulture
=> _cultureData
.IsNeutralCulture
;
783 public CultureTypes CultureTypes
787 CultureTypes types
= 0;
789 if (_cultureData
.IsNeutralCulture
)
791 types
|= CultureTypes
.NeutralCultures
;
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;
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);
828 throw new ArgumentNullException(nameof(value));
837 /// Create a DateTimeFormatInfo, and fill in the properties according to
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
!;
858 throw new ArgumentNullException(nameof(value));
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();
884 /// Map a Win32 CALID to an instance of supported calendar.
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.
892 internal static Calendar
GetCalendarInstance(CalendarId calType
)
894 if (calType
== CalendarId
.GREGORIAN
)
896 return new GregorianCalendar();
899 return GetCalendarInstanceRare(calType
);
903 /// This function exists as a shortcut to prevent us from loading all of the non-gregorian
904 /// calendars unless they're required.
906 internal static Calendar
GetCalendarInstanceRare(CalendarId calType
)
908 Debug
.Assert(calType
!= CalendarId
.GREGORIAN
, "calType!=CalendarId.GREGORIAN");
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();
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.
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
);
962 /// Return an array of the optional calendar for this culture.
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
]);
979 public bool UseUserOverride
=> _cultureData
.UseUserOverride
;
981 public CultureInfo
GetConsoleFallbackUICulture()
983 CultureInfo
? temp
= _consoleFallbackCulture
;
986 temp
= CreateSpecificCulture(_cultureData
.SCONSOLEFALLBACKNAME
);
987 temp
._isReadOnly
= true;
988 _consoleFallbackCulture
= 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.
1002 if (_dateTimeInfo
!= null)
1004 ci
._dateTimeInfo
= (DateTimeFormatInfo
)_dateTimeInfo
.Clone();
1006 if (_numInfo
!= null)
1008 ci
._numInfo
= (NumberFormatInfo
)_numInfo
.Clone();
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();
1030 public static CultureInfo
ReadOnly(CultureInfo ci
)
1034 throw new ArgumentNullException(nameof(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
);
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;
1083 public bool IsReadOnly
=> _isReadOnly
;
1085 private void VerifyWritable()
1089 throw new InvalidOperationException(SR
.InvalidOperation_ReadOnly
);
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.
1098 internal bool HasInvariantCultureName
=> Name
== InvariantCulture
.Name
;
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.
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
;
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
>();
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));
1136 ret
= tempNameHT
.TryGetValue(lcid
== 0 ? name
! : name
! + '\xfffd' + altName
!, out retval
);
1139 if (ret
&& retval
!= null)
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
>();
1156 // If we were called by Lcid, check if the object exists in the table. If so, return it.
1162 ret
= tempLcidHT
.TryGetValue(lcid
, out retval
);
1164 if (ret
&& retval
!= null)
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.
1178 // call the private constructor
1179 Debug
.Assert(name
!= null && altName
!= null);
1180 retval
= new CultureInfo(name
!, altName
!);
1184 Debug
.Assert(name
!= null);
1185 retval
= new CultureInfo(name
!, false);
1189 retval
= new CultureInfo(lcid
, false);
1193 catch (ArgumentException
)
1198 // Set it to read-only
1199 retval
._isReadOnly
= true;
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);
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.
1220 tempNameHT
[newName
] = retval
;
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.
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.
1246 /// Gets a cached copy of the specified culture from an internal
1247 /// hashtable (or creates it if not found). (LCID version)
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.
1256 throw new ArgumentOutOfRangeException(nameof(culture
), SR
.ArgumentOutOfRange_NeedPosNum
);
1258 CultureInfo
? retval
= GetCultureInfoHelper(culture
, null, null);
1261 throw new CultureNotFoundException(nameof(culture
), culture
, SR
.Argument_CultureNotSupported
);
1267 /// Gets a cached copy of the specified culture from an internal
1268 /// hashtable (or creates it if not found). (Named version)
1270 public static CultureInfo
GetCultureInfo(string name
)
1272 // Make sure we have a valid, non-zero length string as name
1275 throw new ArgumentNullException(nameof(name
));
1278 CultureInfo
? retval
= GetCultureInfoHelper(0, name
, null);
1281 throw new CultureNotFoundException(
1282 nameof(name
), name
, SR
.Argument_CultureNotSupported
);
1288 /// Gets a cached copy of the specified culture from an internal
1289 /// hashtable (or creates it if not found).
1291 public static CultureInfo
GetCultureInfo(string name
, string altName
)
1295 throw new ArgumentNullException(nameof(name
));
1297 if (altName
== null)
1299 throw new ArgumentNullException(nameof(altName
));
1302 CultureInfo
? retval
= GetCultureInfoHelper(-1, name
, altName
);
1305 throw new CultureNotFoundException("name or altName",
1306 SR
.Format(SR
.Argument_OneOfCulturesNotSupported
, name
, altName
));
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
));