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 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
)
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)
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...
218 throw new ArgumentOutOfRangeException(nameof(culture
), SR
.ArgumentOutOfRange_NeedPosNum
);
223 case LOCALE_CUSTOM_DEFAULT
:
224 case LOCALE_SYSTEM_DEFAULT
:
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
);
232 // Now see if this LCID is supported in the system default CultureData table.
233 _cultureData
= CultureData
.GetCultureData(culture
, useUserOverride
);
236 _isInherited
= GetType() != typeof(CultureInfo
);
237 _name
= _cultureData
.CultureName
;
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*.
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
;
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)
271 private static CultureInfo
GetCultureByName(string name
)
275 return new CultureInfo(name
)
280 catch (ArgumentException
)
282 return InvariantCulture
;
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
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.
309 for (int idx
= 0; idx
< name
.Length
; idx
++)
311 if ('-' == name
[idx
])
315 culture
= new CultureInfo(name
.Substring(0, idx
));
318 catch (ArgumentException
)
320 // throw the original exception so the name in the string will be right
328 // nothing to save here; throw the original exception
333 // In the most common case, they've given us a specific culture, so we'll just return that.
334 if (!(culture
.IsNeutralCulture
))
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
== '_')
357 throw new ArgumentException(SR
.Format(SR
.Argument_InvalidResourceCultureName
, cultureName
));
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
)
373 return VerifyCultureName(culture
.Name
, throwException
);
377 /// This instance provides methods based on the current user settings.
378 /// These settings are volatile and may change over the lifetime of the
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
391 public static CultureInfo CurrentCulture
396 WinRTInteropCallbacks callbacks
= WinRTInterop
.UnsafeCallbacks
;
397 if (callbacks
!= null && callbacks
.IsAppxModel())
399 return (CultureInfo
)callbacks
.GetUserDefaultCulture();
403 if (ApplicationModel
.IsUap
)
405 CultureInfo
? culture
= GetCultureInfoForUserPreferredLanguageInAppX();
411 return s_currentThreadCulture
??
412 s_DefaultThreadCurrentCulture
??
413 s_userDefaultCulture
??
414 InitializeUserDefaultCulture();
420 throw new ArgumentNullException(nameof(value));
424 WinRTInteropCallbacks callbacks
= WinRTInterop
.UnsafeCallbacks
;
425 if (callbacks
!= null && callbacks
.IsAppxModel())
427 callbacks
.SetGlobalDefaultCulture(value);
432 if (ApplicationModel
.IsUap
)
434 if (SetCultureInfoForUserPreferredLanguageInAppX(value))
436 // successfully set the culture, otherwise fallback to legacy path
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
455 WinRTInteropCallbacks callbacks
= WinRTInterop
.UnsafeCallbacks
;
456 if (callbacks
!= null && callbacks
.IsAppxModel())
458 return (CultureInfo
)callbacks
.GetUserDefaultCulture();
462 if (ApplicationModel
.IsUap
)
464 CultureInfo
? culture
= GetCultureInfoForUserPreferredLanguageInAppX();
470 return s_currentThreadUICulture
??
471 s_DefaultThreadCurrentUICulture
??
472 UserDefaultUICulture
;
478 throw new ArgumentNullException(nameof(value));
481 CultureInfo
.VerifyCultureName(value, true);
484 WinRTInteropCallbacks callbacks
= WinRTInterop
.UnsafeCallbacks
;
485 if (callbacks
!= null && callbacks
.IsAppxModel())
487 callbacks
.SetGlobalDefaultCulture(value);
492 if (ApplicationModel
.IsUap
)
494 if (SetCultureInfoForUserPreferredLanguageInAppX(value))
496 // successfully set the culture, otherwise fallback to legacy path
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
;
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.
538 CultureInfo
.VerifyCultureName(value, true);
541 s_DefaultThreadCurrentUICulture
= value;
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
553 public static CultureInfo InvariantCulture
557 Debug
.Assert(s_InvariantCultureInfo
!= null);
558 return s_InvariantCultureInfo
;
563 /// Return the parent CultureInfo for the current instance.
565 public virtual CultureInfo Parent
572 string parentName
= _cultureData
.ParentName
;
574 if (string.IsNullOrEmpty(parentName
))
576 culture
= InvariantCulture
;
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.
587 Interlocked
.CompareExchange
<CultureInfo
?>(ref _parent
, culture
, null);
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
);
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.
612 public virtual string Name
616 // We return non sorting name here.
617 if (_nonSortName
== null)
619 _nonSortName
= _cultureData
.Name
?? string.Empty
;
626 /// This one has the sort information (ie: de-DE_phoneb)
628 internal string SortName
632 if (_sortName
== null)
634 _sortName
= _cultureData
.SortName
;
641 public string IetfLanguageTag
=>
642 // special case the compatibility cultures
645 "zh-CHT" => "zh-Hant",
646 "zh-CHS" => "zh-Hans",
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.
655 public virtual string DisplayName
659 Debug
.Assert(_name
!= null, "[CultureInfo.DisplayName] Always expect _name to be set");
660 return _cultureData
.DisplayName
;
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.
669 public virtual string NativeName
=> _cultureData
.NativeName
;
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.
676 public virtual string EnglishName
=> _cultureData
.EnglishName
;
681 public virtual string TwoLetterISOLanguageName
=> _cultureData
.TwoLetterISOLanguageName
;
686 public virtual string ThreeLetterISOLanguageName
=> _cultureData
.ThreeLetterISOLanguageName
;
689 /// Returns the 3 letter windows language name for the current instance. eg: "ENU"
690 /// The ISO names are much preferred
692 public virtual string ThreeLetterWindowsLanguageName
=> _cultureData
.ThreeLetterWindowsLanguageName
;
695 /// Gets the CompareInfo for this culture.
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);
714 /// Gets the TextInfo for this culture.
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
;
731 public override bool Equals(object? value)
733 if (object.ReferenceEquals(this, value))
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
);
748 public override int GetHashCode()
750 return Name
.GetHashCode() + CompareInfo
.GetHashCode();
755 /// Implements object.ToString(). Returns the name of the CultureInfo,
756 /// eg. "de-DE_phoneb", "en-US", or "fj-FJ".
758 public override string ToString() => _name
;
760 public virtual object? GetFormat(Type
? formatType
)
762 if (formatType
== typeof(NumberFormatInfo
))
766 if (formatType
== typeof(DateTimeFormatInfo
))
768 return DateTimeFormat
;
774 public virtual bool IsNeutralCulture
=> _cultureData
.IsNeutralCulture
;
776 public CultureTypes CultureTypes
780 CultureTypes types
= 0;
782 if (_cultureData
.IsNeutralCulture
)
784 types
|= CultureTypes
.NeutralCultures
;
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;
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);
821 throw new ArgumentNullException(nameof(value));
830 /// Create a DateTimeFormatInfo, and fill in the properties according to
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
!;
851 throw new ArgumentNullException(nameof(value));
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();
877 /// Map a Win32 CALID to an instance of supported calendar.
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.
885 internal static Calendar
GetCalendarInstance(CalendarId calType
)
887 if (calType
== CalendarId
.GREGORIAN
)
889 return new GregorianCalendar();
892 return GetCalendarInstanceRare(calType
);
896 /// This function exists as a shortcut to prevent us from loading all of the non-gregorian
897 /// calendars unless they're required.
899 internal static Calendar
GetCalendarInstanceRare(CalendarId calType
)
901 Debug
.Assert(calType
!= CalendarId
.GREGORIAN
, "calType!=CalendarId.GREGORIAN");
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();
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.
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
);
955 /// Return an array of the optional calendar for this culture.
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
]);
972 public bool UseUserOverride
=> _cultureData
.UseUserOverride
;
974 public CultureInfo
GetConsoleFallbackUICulture()
976 CultureInfo
? temp
= _consoleFallbackCulture
;
979 temp
= CreateSpecificCulture(_cultureData
.SCONSOLEFALLBACKNAME
);
980 temp
._isReadOnly
= true;
981 _consoleFallbackCulture
= 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.
995 if (_dateTimeInfo
!= null)
997 ci
._dateTimeInfo
= (DateTimeFormatInfo
)_dateTimeInfo
.Clone();
999 if (_numInfo
!= null)
1001 ci
._numInfo
= (NumberFormatInfo
)_numInfo
.Clone();
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();
1023 public static CultureInfo
ReadOnly(CultureInfo ci
)
1027 throw new ArgumentNullException(nameof(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
);
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;
1076 public bool IsReadOnly
=> _isReadOnly
;
1078 private void VerifyWritable()
1082 throw new InvalidOperationException(SR
.InvalidOperation_ReadOnly
);
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.
1091 internal bool HasInvariantCultureName
=> Name
== InvariantCulture
.Name
;
1094 /// Gets a cached copy of the specified culture from an internal
1095 /// hashtable (or creates it if not found). (LCID version)
1097 public static CultureInfo
GetCultureInfo(int culture
)
1101 throw new ArgumentOutOfRangeException(nameof(culture
), SR
.ArgumentOutOfRange_NeedPosNum
);
1104 Dictionary
<int, CultureInfo
> lcidTable
= CachedCulturesByLcid
;
1105 CultureInfo
? result
;
1109 if (lcidTable
.TryGetValue(culture
, out result
))
1117 result
= new CultureInfo(culture
, useUserOverride
: false) { _isReadOnly = true }
;
1119 catch (ArgumentException
)
1121 throw new CultureNotFoundException(nameof(culture
), culture
, SR
.Argument_CultureNotSupported
);
1126 lcidTable
[culture
] = result
;
1133 /// Gets a cached copy of the specified culture from an internal
1134 /// hashtable (or creates it if not found). (Named version)
1136 public static CultureInfo
GetCultureInfo(string name
)
1138 // Make sure we have a valid, non-zero length string as name
1141 throw new ArgumentNullException(nameof(name
));
1144 name
= CultureData
.AnsiToLower(name
);
1145 Dictionary
<string, CultureInfo
> nameTable
= CachedCulturesByName
;
1146 CultureInfo
? result
;
1150 if (nameTable
.TryGetValue(name
, out 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
);
1166 nameTable
[name
] = result
;
1173 /// Gets a cached copy of the specified culture from an internal
1174 /// hashtable (or creates it if not found).
1176 public static CultureInfo
GetCultureInfo(string name
, string altName
)
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
;
1195 if (nameTable
.TryGetValue(nameAndAltName
, out 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
));
1213 nameTable
[nameAndAltName
] = result
;
1219 private static Dictionary
<string, CultureInfo
> CachedCulturesByName
1223 Dictionary
<string, CultureInfo
>? cache
= s_cachedCulturesByName
;
1226 cache
= new Dictionary
<string, CultureInfo
>();
1227 cache
= Interlocked
.CompareExchange(ref s_cachedCulturesByName
, cache
, null) ?? cache
;
1234 private static Dictionary
<int, CultureInfo
> CachedCulturesByLcid
1238 Dictionary
<int, CultureInfo
>? cache
= s_cachedCulturesByLcid
;
1241 cache
= new Dictionary
<int, CultureInfo
>();
1242 cache
= Interlocked
.CompareExchange(ref s_cachedCulturesByLcid
, cache
, null) ?? 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
));