File: src\libraries\System.Private.CoreLib\src\System\Globalization\CultureInfo.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
////////////////////////////////////////////////////////////////////////////
//
//
//
//  Purpose:  This class represents the software preferences of a particular
//            culture or community.  It includes information such as the
//            language, writing system, and a calendar used by the culture
//            as well as methods for common operations such as printing
//            dates and sorting strings.
//
//
//
//  !!!! NOTE WHEN CHANGING THIS CLASS !!!!
//
//  If adding or removing members to this class, please update CultureInfoBaseObject
//  in ndp/clr/src/vm/object.h. Note, the "actual" layout of the class may be
//  different than the order in which members are declared. For instance, all
//  reference types will come first in the class before value types (like ints, bools, etc)
//  regardless of the order in which they are declared. The best way to see the
//  actual order of the class is to do a !dumpobj on an instance of the managed
//  object inside of the debugger.
//
////////////////////////////////////////////////////////////////////////////
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
 
namespace System.Globalization
{
    /// <summary>
    /// This class represents the software preferences of a particular culture
    /// or community. It includes information such as the language, writing
    /// system and a calendar used by the culture as well as methods for
    /// common operations such as printing dates and sorting strings.
    /// </summary>
    /// <remarks>
    /// !!!! NOTE WHEN CHANGING THIS CLASS !!!!
    /// If adding or removing members to this class, please update
    /// CultureInfoBaseObject in ndp/clr/src/vm/object.h. Note, the "actual"
    /// layout of the class may be different than the order in which members
    /// are declared. For instance, all reference types will come first in the
    /// class before value types (like ints, bools, etc) regardless of the
    /// order in which they are declared. The best way to see the actual
    /// order of the class is to do a !dumpobj on an instance of the managed
    /// object inside of the debugger.
    /// </remarks>
    public partial class CultureInfo : IFormatProvider, ICloneable
    {
        // We use an RFC4646 type string to construct CultureInfo.
        // This string is stored in _name and is authoritative.
        // We use the _cultureData to get the data for our object
 
        private bool _isReadOnly;
        private CompareInfo? _compareInfo;
        private TextInfo? _textInfo;
        internal NumberFormatInfo? _numInfo;
        internal DateTimeFormatInfo? _dateTimeInfo;
        private Calendar? _calendar;
        //
        // The CultureData instance that we are going to read data from.
        // For supported culture, this will be the CultureData instance that read data from mscorlib assembly.
        // For customized culture, this will be the CultureData instance that read data from user customized culture binary file.
        //
        internal CultureData _cultureData;
 
        internal bool _isInherited;
 
        private CultureInfo? _consoleFallbackCulture;
 
        // Names are confusing.  Here are 3 names we have:
        //
        //  new CultureInfo()   _name          _nonSortName    _sortName
        //      en-US           en-US           en-US           en-US
        //      de-de_phoneb    de-DE_phoneb    de-DE           de-DE_phoneb
        //      fj-fj (custom)  fj-FJ           fj-FJ           en-US (if specified sort is en-US)
        //      en              en
        //
        // Note that in Silverlight we ask the OS for the text and sort behavior, so the
        // textinfo and compareinfo names are the same as the name
 
        // This has a de-DE, de-DE_phoneb or fj-FJ style name
        internal string _name;
 
        // This will hold the non sorting name to be returned from CultureInfo.Name property.
        // This has a de-DE style name even for de-DE_phoneb type cultures
        private string? _nonSortName;
 
        // This will hold the sorting name to be returned from CultureInfo.SortName property.
        // This might be completely unrelated to the culture name if a custom culture.  Ie en-US for fj-FJ.
        // Otherwise its the sort name, ie: de-DE or de-DE_phoneb
        private string? _sortName;
 
        // Get the current user default culture. This one is almost always used, so we create it by default.
        private static volatile CultureInfo? s_userDefaultCulture;
 
        // The culture used in the user interface. This is mostly used to load correct localized resources.
        private static volatile CultureInfo? s_userDefaultUICulture;
 
        // The Invariant culture;
        private static readonly CultureInfo s_InvariantCultureInfo = new CultureInfo(CultureData.Invariant, isReadOnly: true);
 
        // These are defaults that we use if a thread has not opted into having an explicit culture
        private static volatile CultureInfo? s_DefaultThreadCurrentUICulture;
        private static volatile CultureInfo? s_DefaultThreadCurrentCulture;
 
        [ThreadStatic]
        private static CultureInfo? s_currentThreadCulture;
        [ThreadStatic]
        private static CultureInfo? s_currentThreadUICulture;
 
        private static AsyncLocal<CultureInfo>? s_asyncLocalCurrentCulture;
        private static AsyncLocal<CultureInfo>? s_asyncLocalCurrentUICulture;
 
        private static void AsyncLocalSetCurrentCulture(AsyncLocalValueChangedArgs<CultureInfo> args)
        {
            s_currentThreadCulture = args.CurrentValue;
        }
 
        private static void AsyncLocalSetCurrentUICulture(AsyncLocalValueChangedArgs<CultureInfo> args)
        {
            s_currentThreadUICulture = args.CurrentValue;
        }
 
        private static Dictionary<string, CultureInfo>? s_cachedCulturesByName;
        private static Dictionary<int, CultureInfo>? s_cachedCulturesByLcid;
 
        // The parent culture.
        private CultureInfo? _parent;
 
        // LOCALE constants of interest to us internally and privately for LCID functions
        // (ie: avoid using these and use names if possible)
        internal const int LOCALE_NEUTRAL        = 0x0000;
        private  const int LOCALE_USER_DEFAULT   = 0x0400;
        private  const int LOCALE_SYSTEM_DEFAULT = 0x0800;
        internal const int LOCALE_CUSTOM_UNSPECIFIED = 0x1000;
        internal const int LOCALE_CUSTOM_DEFAULT  = 0x0c00;
        internal const int LOCALE_INVARIANT       = 0x007F;
 
        private static CultureInfo InitializeUserDefaultCulture()
        {
            Interlocked.CompareExchange(ref s_userDefaultCulture, GetUserDefaultCulture(), null);
            return s_userDefaultCulture!;
        }
 
        private static CultureInfo InitializeUserDefaultUICulture()
        {
            Interlocked.CompareExchange(ref s_userDefaultUICulture, GetUserDefaultUICulture(), null);
            return s_userDefaultUICulture!;
        }
 
        private static string GetCultureNotSupportedExceptionMessage() => GlobalizationMode.Invariant ? SR.Argument_CultureNotSupportedInInvariantMode : SR.Argument_CultureNotSupported;
 
        public CultureInfo(string name) : this(name, true)
        {
        }
 
        public CultureInfo(string name, bool useUserOverride)
        {
            ArgumentNullException.ThrowIfNull(name);
 
            // Get our data providing record
            _cultureData = CultureData.GetCultureData(name, useUserOverride) ??
                throw new CultureNotFoundException(nameof(name), name, GetCultureNotSupportedExceptionMessage());
            _name = _cultureData.CultureName;
            _isInherited = GetType() != typeof(CultureInfo);
        }
 
        private CultureInfo(CultureData cultureData, bool isReadOnly = false)
        {
            Debug.Assert(cultureData != null);
            _cultureData = cultureData;
            _name = cultureData.CultureName;
            _isReadOnly = isReadOnly;
        }
 
        private static CultureInfo? CreateCultureInfoNoThrow(string name, bool useUserOverride)
        {
            Debug.Assert(name != null);
            CultureData? cultureData = CultureData.GetCultureData(name, useUserOverride);
            if (cultureData == null)
            {
                return null;
            }
 
            return new CultureInfo(cultureData);
        }
 
        public CultureInfo(int culture) : this(culture, true)
        {
        }
 
        public CultureInfo(int culture, bool useUserOverride)
        {
            // We don't check for other invalid LCIDS here...
            ArgumentOutOfRangeException.ThrowIfNegative(culture);
 
            if (culture is LOCALE_CUSTOM_DEFAULT or LOCALE_SYSTEM_DEFAULT or LOCALE_NEUTRAL or LOCALE_USER_DEFAULT or LOCALE_CUSTOM_UNSPECIFIED)
            {
                // Can't support unknown custom cultures and we do not support neutral or
                throw new CultureNotFoundException(nameof(culture), culture, SR.Argument_CultureNotSupported);
            }
 
            // Now see if this LCID is supported in the system default CultureData table.
            _cultureData = CultureData.GetCultureData(culture, useUserOverride);
 
            _isInherited = GetType() != typeof(CultureInfo);
            _name = _cultureData.CultureName;
        }
 
        /// <summary>
        /// Constructor called by SQL Server's special munged culture - creates a culture with
        /// a TextInfo and CompareInfo that come from a supplied alternate source. This object
        /// is ALWAYS read-only.
        /// Note that we really cannot use an LCID version of this override as the cached
        /// name we create for it has to include both names, and the logic for this is in
        /// the GetCultureInfo override *only*.
        /// </summary>
        internal CultureInfo(string cultureName, string textAndCompareCultureName)
        {
            ArgumentNullException.ThrowIfNull(textAndCompareCultureName);
 
            CultureData? cultureData = CultureData.GetCultureData(cultureName, false) ??
                throw new CultureNotFoundException(nameof(cultureName), cultureName, GetCultureNotSupportedExceptionMessage());
 
            _cultureData = cultureData;
 
            _name = _cultureData.CultureName;
 
            CultureInfo altCulture = GetCultureInfo(textAndCompareCultureName);
            _compareInfo = altCulture.CompareInfo;
            _textInfo = altCulture.TextInfo;
        }
 
        /// <summary>
        /// We do this to try to return the system UI language and the default user languages
        /// This method will fallback if this fails (like Invariant)
        /// </summary>
        private static CultureInfo GetCultureByName(string name)
        {
            try
            {
                return new CultureInfo(name)
                {
                    _isReadOnly = true
                };
            }
            catch (ArgumentException)
            {
                return InvariantCulture;
            }
        }
 
        /// <summary>
        /// Return a specific culture. A tad irrelevant now since we always
        /// return valid data for neutral locales.
        ///
        /// Note that there's interesting behavior that tries to find a
        /// smaller name, ala RFC4647, if we can't find a bigger name.
        /// That doesn't help with things like "zh" though, so the approach
        /// is of questionable value
        /// </summary>
        public static CultureInfo CreateSpecificCulture(string name)
        {
            CultureInfo? culture;
 
            try
            {
                culture = new CultureInfo(name);
            }
            catch (ArgumentException)
            {
                // When CultureInfo throws this exception, it may be because someone passed the form
                // like "az-az" because it came out of an http accept lang. We should try a little
                // parsing to perhaps fall back to "az" here and use *it* to create the neutral.
                culture = null;
                for (int idx = 0; idx < name.Length; idx++)
                {
                    if ('-' == name[idx])
                    {
                        try
                        {
                            culture = new CultureInfo(name.Substring(0, idx));
                            break;
                        }
                        catch (ArgumentException)
                        {
                            // throw the original exception so the name in the string will be right
                            throw;
                        }
                    }
                }
 
                if (culture == null)
                {
                    // nothing to save here; throw the original exception
                    throw;
                }
            }
 
            // In the most common case, they've given us a specific culture, so we'll just return that.
            if (!culture.IsNeutralCulture)
            {
                return culture;
            }
 
            return new CultureInfo(culture._cultureData.SpecificCultureName);
        }
 
        internal static bool VerifyCultureName(string cultureName, bool throwException)
        {
            // This function is used by ResourceManager.GetResourceFileName().
            // ResourceManager searches for resource using CultureInfo.Name,
            // so we should check against CultureInfo.Name.
            for (int i = 0; i < cultureName.Length; i++)
            {
                char c = cultureName[i];
                // TODO: Names can only be RFC4646 names (ie: a-zA-Z0-9) while this allows any unicode letter/digit
                if (char.IsLetterOrDigit(c) || c == '-' || c == '_')
                {
                    continue;
                }
                if (throwException)
                {
                    throw new ArgumentException(SR.Format(SR.Argument_InvalidResourceCultureName, cultureName));
                }
                return false;
            }
            return true;
        }
 
        internal static bool VerifyCultureName(CultureInfo culture, bool throwException)
        {
            // If we have an instance of one of our CultureInfos, the user can't have changed the
            // name and we know that all names are valid in files.
            if (!culture._isInherited)
            {
                return true;
            }
 
            return VerifyCultureName(culture.Name, throwException);
        }
 
        /// <summary>
        /// This instance provides methods based on the current user settings.
        /// These settings are volatile and may change over the lifetime of the
        /// thread.
        /// </summary>
        /// <remarks>
        /// We use the following order to return CurrentCulture and CurrentUICulture
        ///      o   Use WinRT to return the current user profile language
        ///      o   use current thread culture if the user already set one using CurrentCulture/CurrentUICulture
        ///      o   use thread culture if the user already set one using DefaultThreadCurrentCulture
        ///          or DefaultThreadCurrentUICulture
        ///      o   Use NLS default user culture
        ///      o   Use NLS default system culture
        ///      o   Use Invariant culture
        /// </remarks>
        public static CultureInfo CurrentCulture
        {
            get
            {
                return s_currentThreadCulture ??
                    s_DefaultThreadCurrentCulture ??
                    s_userDefaultCulture ??
                    InitializeUserDefaultCulture();
            }
            set
            {
                ArgumentNullException.ThrowIfNull(value);
 
                if (s_asyncLocalCurrentCulture == null)
                {
                    Interlocked.CompareExchange(ref s_asyncLocalCurrentCulture, new AsyncLocal<CultureInfo>(AsyncLocalSetCurrentCulture), null);
                }
                s_asyncLocalCurrentCulture!.Value = value;
            }
        }
 
        public static CultureInfo CurrentUICulture
        {
            get
            {
                return s_currentThreadUICulture ??
                    s_DefaultThreadCurrentUICulture ??
                    UserDefaultUICulture;
            }
            set
            {
                ArgumentNullException.ThrowIfNull(value);
 
                VerifyCultureName(value, true);
 
                if (s_asyncLocalCurrentUICulture == null)
                {
                    Interlocked.CompareExchange(ref s_asyncLocalCurrentUICulture, new AsyncLocal<CultureInfo>(AsyncLocalSetCurrentUICulture), null);
                }
 
                // this one will set s_currentThreadUICulture too
                s_asyncLocalCurrentUICulture!.Value = value;
            }
        }
 
        internal static CultureInfo UserDefaultUICulture => s_userDefaultUICulture ?? InitializeUserDefaultUICulture();
 
        public static CultureInfo InstalledUICulture => s_userDefaultCulture ?? InitializeUserDefaultCulture();
 
        public static CultureInfo? DefaultThreadCurrentCulture
        {
            get => s_DefaultThreadCurrentCulture;
            set =>
                // If you add pre-conditions to this method, check to see if you also need to
                // add them to Thread.CurrentCulture.set.
                s_DefaultThreadCurrentCulture = value;
        }
 
        public static CultureInfo? DefaultThreadCurrentUICulture
        {
            get => s_DefaultThreadCurrentUICulture;
            set
            {
                // If they're trying to use a Culture with a name that we can't use in resource lookup,
                // don't even let them set it on the thread.
 
                // If you add more pre-conditions to this method, check to see if you also need to
                // add them to Thread.CurrentUICulture.set.
 
                if (value != null)
                {
                    VerifyCultureName(value, true);
                }
 
                s_DefaultThreadCurrentUICulture = value;
            }
        }
 
        /// <summary>
        /// This instance provides methods, for example for casing and sorting,
        /// that are independent of the system and current user settings.  It
        /// should be used only by processes such as some system services that
        /// require such invariant results (eg. file systems).  In general,
        /// the results are not linguistically correct and do not match any
        /// culture info.
        /// </summary>
        public static CultureInfo InvariantCulture
        {
            get
            {
                Debug.Assert(s_InvariantCultureInfo != null, "[CultureInfo.InvariantCulture] s_InvariantCultureInfo is null");
                return s_InvariantCultureInfo;
            }
        }
 
        /// <summary>
        /// Return the parent CultureInfo for the current instance.
        /// </summary>
        public virtual CultureInfo Parent
        {
            get
            {
                if (_parent == null)
                {
                    CultureInfo culture;
                    string parentName = _cultureData.ParentName;
 
                    if (parentName == "zh")
                    {
                        if (_name.Length == 5 && _name[2] == '-')
                        {
                            // We need to keep the parent chain for the zh cultures as follows to preserve the resource lookup compatibility
                            //      zh-CN -> zh-Hans -> zh -> Invariant
                            //      zh-HK -> zh-Hant -> zh -> Invariant
                            //      zh-MO -> zh-Hant -> zh -> Invariant
                            //      zh-SG -> zh-Hans -> zh -> Invariant
                            //      zh-TW -> zh-Hant -> zh -> Invariant
 
                            if ((_name[3] == 'C' && _name[4] == 'N') || // zh-CN
                                (_name[3] == 'S' && _name[4] == 'G'))   // zh-SG
                            {
                                parentName = "zh-Hans";
                            }
                            else if ((_name[3] == 'H' && _name[4] == 'K') ||   // zh-HK
                                    (_name[3] == 'M' && _name[4] == 'O') ||    // zh-MO
                                    (_name[3] == 'T' && _name[4] == 'W'))      // zh-TW
                            {
                                parentName = "zh-Hant";
                            }
                        }
                        else if (_name.Length > 8 && _name.AsSpan(2, 4) is "-Han" && _name[7] == '-') // cultures like zh-Hant-* and zh-Hans-*
                        {
                            if (_name[6] == 't') // zh-Hant-*
                            {
                                parentName = "zh-Hant";
                            }
                            else if (_name[6] == 's') // zh-Hans-*
                            {
                                parentName = "zh-Hans";
                            }
                        }
                    }
 
                    if (string.IsNullOrEmpty(parentName))
                    {
                        culture = InvariantCulture;
                    }
                    else
                    {
                        culture = CreateCultureInfoNoThrow(parentName, _cultureData.UseUserOverride) ??
                            // For whatever reason our IPARENT or SPARENT wasn't correct, so use invariant
                            // We can't allow ourselves to fail.  In case of custom cultures the parent of the
                            // current custom culture isn't installed.
                            InvariantCulture;
                    }
 
                    Interlocked.CompareExchange<CultureInfo?>(ref _parent, culture, null);
                }
                return _parent!;
            }
        }
 
        public virtual int LCID => _cultureData.LCID;
 
        public virtual int KeyboardLayoutId => _cultureData.KeyboardLayoutId;
 
        public static CultureInfo[] GetCultures(CultureTypes types)
        {
            // internally we treat UserCustomCultures as Supplementals but v2
            // treats as Supplementals and Replacements
            if ((types & CultureTypes.UserCustomCulture) == CultureTypes.UserCustomCulture)
            {
                types |= CultureTypes.ReplacementCultures;
            }
            return CultureData.GetCultures(types);
        }
 
        /// <summary>
        /// Returns the full name of the CultureInfo. The name is in format like
        /// "en-US" This version does NOT include sort information in the name.
        /// </summary>
        public virtual string Name => _nonSortName ??= (_cultureData.Name ?? string.Empty);
 
        /// <summary>
        /// This one has the sort information (ie: de-DE_phoneb)
        /// </summary>
        internal string SortName => _sortName ??= _cultureData.SortName;
 
        /// <summary>
        /// The culture name to use to interop with the underlying native globalization libraries like ICU or Windows NLS APIs.
        /// For example, we can have the name de_DE@collation=phonebook when using ICU for the German culture de-DE with the phonebook sorting behavior.
        /// </summary>
        internal string? InteropName => _cultureData.InteropName;
 
        public string IetfLanguageTag =>
                // special case the compatibility cultures
                Name switch
                {
                    "zh-CHT" => "zh-Hant",
                    "zh-CHS" => "zh-Hans",
                    _ => Name,
                };
 
        /// <summary>
        /// Returns the full name of the CultureInfo in the localized language.
        /// For example, if the localized language of the runtime is Spanish and the CultureInfo is
        /// US English, "Ingles (Estados Unidos)" will be returned.
        /// </summary>
        public virtual string DisplayName
        {
            get
            {
                Debug.Assert(_name != null, "[CultureInfo.DisplayName] Always expect _name to be set");
                return _cultureData.DisplayName;
            }
        }
 
        /// <summary>
        /// Returns the full name of the CultureInfo in the native language.
        /// For example, if the CultureInfo is US English, "English
        /// (United States)" will be returned.
        /// </summary>
        public virtual string NativeName => _cultureData.NativeName;
 
        /// <summary>
        /// Returns the full name of the CultureInfo in English.
        /// For example, if the CultureInfo is US English, "English
        /// (United States)" will be returned.
        /// </summary>
        public virtual string EnglishName => _cultureData.EnglishName;
 
        /// <summary>
        /// ie: en
        /// </summary>
        public virtual string TwoLetterISOLanguageName => _cultureData.TwoLetterISOLanguageName;
 
        /// <summary>
        /// ie: eng
        /// </summary>
        public virtual string ThreeLetterISOLanguageName => _cultureData.ThreeLetterISOLanguageName;
 
        /// <summary>
        /// Returns the 3 letter windows language name for the current instance.  eg: "ENU"
        /// The ISO names are much preferred
        /// </summary>
        public virtual string ThreeLetterWindowsLanguageName => _cultureData.ThreeLetterWindowsLanguageName;
 
        /// <summary>
        /// Gets the CompareInfo for this culture.
        /// </summary>
        public virtual CompareInfo CompareInfo => _compareInfo ??=
            // Since CompareInfo's don't have any overrideable properties, get the CompareInfo from
            // the Non-Overridden CultureInfo so that we only create one CompareInfo per culture
            (UseUserOverride ? GetCultureInfo(_name).CompareInfo : new CompareInfo(this));
 
        /// <summary>
        /// Gets the TextInfo for this culture.
        /// </summary>
        public virtual TextInfo TextInfo
        {
            get
            {
                if (_textInfo == null)
                {
                    // Make a new textInfo
                    TextInfo tempTextInfo = new TextInfo(_cultureData);
                    tempTextInfo.SetReadOnlyState(_isReadOnly);
                    _textInfo = tempTextInfo;
                }
                return _textInfo;
            }
        }
 
        public override bool Equals([NotNullWhen(true)] object? value)
        {
            if (ReferenceEquals(this, value))
            {
                return true;
            }
 
            if (value is CultureInfo that)
            {
                // using CompareInfo to verify the data passed through the constructor
                // CultureInfo(String cultureName, String textAndCompareCultureName)
                return Name.Equals(that.Name) && CompareInfo.Equals(that.CompareInfo);
            }
 
            return false;
        }
 
        public override int GetHashCode()
        {
            return Name.GetHashCode() + CompareInfo.GetHashCode();
        }
 
        /// <summary>
        /// Implements object.ToString(). Returns the name of the CultureInfo,
        /// eg. "de-DE_phoneb", "en-US", or "fj-FJ".
        /// </summary>
        public override string ToString() => _name;
 
        public virtual object? GetFormat(Type? formatType)
        {
            if (formatType == typeof(NumberFormatInfo))
            {
                return NumberFormat;
            }
            if (formatType == typeof(DateTimeFormatInfo))
            {
                return DateTimeFormat;
            }
 
            return null;
        }
 
        public virtual bool IsNeutralCulture => _cultureData.IsNeutralCulture;
 
        public CultureTypes CultureTypes
        {
            get
            {
                CultureTypes types = _cultureData.IsNeutralCulture ?
                    CultureTypes.NeutralCultures :
                    CultureTypes.SpecificCultures;
 
                if (CultureData.IsWin32Installed)
                {
                    types |= CultureTypes.InstalledWin32Cultures;
                }
 
                if (_cultureData.IsSupplementalCustomCulture)
                {
                    types |= CultureTypes.UserCustomCulture;
                }
 
                if (_cultureData.IsReplacementCulture)
                {
                    types |= CultureTypes.ReplacementCultures;
                }
 
                return types;
            }
        }
 
        public virtual NumberFormatInfo NumberFormat
        {
            get
            {
                if (_numInfo == null)
                {
                    NumberFormatInfo temp = new NumberFormatInfo(_cultureData);
                    temp._isReadOnly = _isReadOnly;
                    Interlocked.CompareExchange(ref _numInfo, temp, null);
                }
                return _numInfo!;
            }
            set
            {
                ArgumentNullException.ThrowIfNull(value);
 
                VerifyWritable();
                _numInfo = value;
            }
        }
 
        /// <summary>
        /// Create a DateTimeFormatInfo, and fill in the properties according to
        /// the CultureID.
        /// </summary>
        public virtual DateTimeFormatInfo DateTimeFormat
        {
            get
            {
                if (_dateTimeInfo == null)
                {
                    // Change the calendar of DTFI to the specified calendar of this CultureInfo.
                    DateTimeFormatInfo temp = new DateTimeFormatInfo(_cultureData, this.Calendar);
                    temp._isReadOnly = _isReadOnly;
                    Interlocked.CompareExchange(ref _dateTimeInfo, temp, null);
                }
                return _dateTimeInfo!;
            }
 
            set
            {
                ArgumentNullException.ThrowIfNull(value);
 
                VerifyWritable();
                _dateTimeInfo = value;
            }
        }
 
        public void ClearCachedData()
        {
            // reset the default culture values
#if TARGET_WINDOWS
            UserDefaultLocaleName = GetUserDefaultLocaleName();
#endif
            s_userDefaultCulture = GetUserDefaultCulture();
            s_userDefaultUICulture = GetUserDefaultUICulture();
 
            RegionInfo.s_currentRegionInfo = null;
#pragma warning disable 0618 // disable the obsolete warning
            TimeZone.ResetTimeZone();
#pragma warning restore 0618
            TimeZoneInfo.ClearCachedData();
            s_cachedCulturesByLcid = null;
            s_cachedCulturesByName = null;
 
            CultureData.ClearCachedData();
        }
 
        /// <summary>
        /// Map a Win32 CALID to an instance of supported calendar.
        /// </summary>
        /// <remarks>
        /// Shouldn't throw exception since the calType value is from our data
        /// table or from Win32 registry.
        /// If we are in trouble (like getting a weird value from Win32
        /// registry), just return the GregorianCalendar.
        /// </remarks>
        internal static Calendar GetCalendarInstance(CalendarId calType)
        {
            Debug.Assert(!GlobalizationMode.Invariant);
 
            if (calType == CalendarId.GREGORIAN)
            {
                return new GregorianCalendar();
            }
 
            return GetCalendarInstanceRare(calType);
        }
 
        /// <summary>
        /// This function exists as a shortcut to prevent us from loading all of the non-gregorian
        /// calendars unless they're required.
        /// </summary>
        internal static Calendar GetCalendarInstanceRare(CalendarId calType)
        {
            Debug.Assert(calType != CalendarId.GREGORIAN, "calType!=CalendarId.GREGORIAN");
 
            return calType switch
            {
                CalendarId.GREGORIAN_US or CalendarId.GREGORIAN_ME_FRENCH or CalendarId.GREGORIAN_ARABIC or CalendarId.GREGORIAN_XLIT_ENGLISH or CalendarId.GREGORIAN_XLIT_FRENCH => new GregorianCalendar((GregorianCalendarTypes)calType),
                CalendarId.TAIWAN => new TaiwanCalendar(),
                CalendarId.JAPAN => new JapaneseCalendar(),
                CalendarId.KOREA => new KoreanCalendar(),
                CalendarId.THAI => new ThaiBuddhistCalendar(),
                CalendarId.HIJRI => new HijriCalendar(),
                CalendarId.HEBREW => new HebrewCalendar(),
                CalendarId.UMALQURA => new UmAlQuraCalendar(),
                CalendarId.PERSIAN => new PersianCalendar(),
                _ => new GregorianCalendar(),
            };
        }
 
        /// <summary>
        /// Return/set the default calendar used by this culture.
        /// This value can be overridden by regional option if this is a current culture.
        /// </summary>
        public virtual Calendar Calendar
        {
            get
            {
                if (_calendar == null)
                {
                    // Get the default calendar for this culture.  Note that the value can be
                    // from registry if this is a user default culture.
                    Calendar newObj = _cultureData.DefaultCalendar;
 
                    Interlocked.MemoryBarrier();
                    newObj.SetReadOnlyState(_isReadOnly);
                    _calendar = newObj;
                }
                return _calendar;
            }
        }
 
        /// <summary>
        /// Return an array of the optional calendar for this culture.
        /// </summary>
        public virtual Calendar[] OptionalCalendars
        {
            get
            {
                // This property always returns a new copy of the calendar array.
                if (GlobalizationMode.Invariant)
                {
                    return new[] { new GregorianCalendar() };
                }
 
                CalendarId[] calID = _cultureData.CalendarIds;
                Calendar[] cals = new Calendar[calID.Length];
                for (int i = 0; i < cals.Length; i++)
                {
                    cals[i] = GetCalendarInstance(calID[i]);
                }
                return cals;
            }
        }
 
        public bool UseUserOverride => _cultureData.UseUserOverride;
 
        public CultureInfo GetConsoleFallbackUICulture()
        {
            CultureInfo? temp = _consoleFallbackCulture;
            if (temp == null)
            {
                temp = CreateSpecificCulture(_cultureData.SCONSOLEFALLBACKNAME);
                temp._isReadOnly = true;
                _consoleFallbackCulture = temp;
            }
            return temp;
        }
 
        public virtual object Clone()
        {
            CultureInfo ci = (CultureInfo)MemberwiseClone();
            ci._isReadOnly = false;
 
            // If this is exactly our type, we can make certain optimizations so that we don't allocate NumberFormatInfo or DTFI unless
            // they've already been allocated.  If this is a derived type, we'll take a more generic codepath.
            if (!_isInherited)
            {
                if (_dateTimeInfo != null)
                {
                    ci._dateTimeInfo = (DateTimeFormatInfo)_dateTimeInfo.Clone();
                }
                if (_numInfo != null)
                {
                    ci._numInfo = (NumberFormatInfo)_numInfo.Clone();
                }
            }
            else
            {
                ci.DateTimeFormat = (DateTimeFormatInfo)this.DateTimeFormat.Clone();
                ci.NumberFormat = (NumberFormatInfo)this.NumberFormat.Clone();
            }
 
            if (_textInfo != null)
            {
                ci._textInfo = (TextInfo)_textInfo.Clone();
            }
 
            if (_dateTimeInfo != null && _dateTimeInfo.Calendar == _calendar)
            {
                // Usually when we access CultureInfo.DateTimeFormat first time, we create the DateTimeFormatInfo object
                // using CultureInfo.Calendar. i.e. CultureInfo.DateTimeInfo.Calendar == CultureInfo.calendar.
                // When cloning CultureInfo, if we know it's still the case that CultureInfo.DateTimeInfo.Calendar == CultureInfo.calendar
                // then we can keep the same behavior for the cloned object and no need to create another calendar object.
                ci._calendar = ci.DateTimeFormat.Calendar;
            }
            else if (_calendar != null)
            {
                ci._calendar = (Calendar)_calendar.Clone();
            }
 
            return ci;
        }
 
        public static CultureInfo ReadOnly(CultureInfo ci)
        {
            ArgumentNullException.ThrowIfNull(ci);
 
            if (ci.IsReadOnly)
            {
                return ci;
            }
            CultureInfo newInfo = (CultureInfo)(ci.MemberwiseClone());
 
            if (!ci.IsNeutralCulture)
            {
                // If this is exactly our type, we can make certain optimizations so that we don't allocate NumberFormatInfo or DTFI unless
                // they've already been allocated.  If this is a derived type, we'll take a more generic codepath.
                if (!ci._isInherited)
                {
                    if (ci._dateTimeInfo != null)
                    {
                        newInfo._dateTimeInfo = DateTimeFormatInfo.ReadOnly(ci._dateTimeInfo);
                    }
                    if (ci._numInfo != null)
                    {
                        newInfo._numInfo = NumberFormatInfo.ReadOnly(ci._numInfo);
                    }
                }
                else
                {
                    newInfo.DateTimeFormat = DateTimeFormatInfo.ReadOnly(ci.DateTimeFormat);
                    newInfo.NumberFormat = NumberFormatInfo.ReadOnly(ci.NumberFormat);
                }
            }
 
            if (ci._textInfo != null)
            {
                newInfo._textInfo = TextInfo.ReadOnly(ci._textInfo);
            }
 
            if (ci._calendar != null)
            {
                newInfo._calendar = Calendar.ReadOnly(ci._calendar);
            }
 
            // Don't set the read-only flag too early.
            // We should set the read-only flag here.  Otherwise, info.DateTimeFormat will not be able to set.
            newInfo._isReadOnly = true;
 
            return newInfo;
        }
 
        public bool IsReadOnly => _isReadOnly;
 
        private void VerifyWritable()
        {
            if (_isReadOnly)
            {
                throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
            }
        }
 
        /// <summary>
        /// For resource lookup, we consider a culture the invariant culture by name equality.
        /// We perform this check frequently during resource lookup, so adding a property for
        /// improved readability.
        /// </summary>
        internal bool HasInvariantCultureName => Name == InvariantCulture.Name;
 
        /// <summary>
        /// Gets a cached copy of the specified culture from an internal
        /// hashtable (or creates it if not found). (LCID version)
        /// </summary>
        public static CultureInfo GetCultureInfo(int culture)
        {
            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(culture);
 
            Dictionary<int, CultureInfo> lcidTable = CachedCulturesByLcid;
            CultureInfo? result;
 
            lock (lcidTable)
            {
                if (lcidTable.TryGetValue(culture, out result))
                {
                    return result;
                }
            }
 
            try
            {
                result = new CultureInfo(culture, useUserOverride: false) { _isReadOnly = true };
            }
            catch (ArgumentException)
            {
                throw new CultureNotFoundException(nameof(culture), culture, GetCultureNotSupportedExceptionMessage());
            }
 
            lock (lcidTable)
            {
                lcidTable[culture] = result;
            }
 
            return result;
        }
 
        /// <summary>
        /// Gets a cached copy of the specified culture from an internal
        /// hashtable (or creates it if not found). (Named version)
        /// </summary>
        public static CultureInfo GetCultureInfo(string name)
        {
            ArgumentNullException.ThrowIfNull(name);
 
            name = CultureData.AnsiToLower(name);
            Dictionary<string, CultureInfo> nameTable = CachedCulturesByName;
            CultureInfo? result;
 
            lock (nameTable)
            {
                if (nameTable.TryGetValue(name, out result))
                {
                    return result;
                }
            }
 
            result = CreateCultureInfoNoThrow(name, useUserOverride: false) ??
                throw new CultureNotFoundException(nameof(name), name, GetCultureNotSupportedExceptionMessage());
            result._isReadOnly = true;
 
            // Remember our name as constructed.  Do NOT use alternate sort name versions because
            // we have internal state representing the sort (so someone would get the wrong cached version).
            name = CultureData.AnsiToLower(result._name);
 
            lock (nameTable)
            {
                nameTable[name] = result;
            }
 
            return result;
        }
 
        /// <summary>
        /// Gets a cached copy of the specified culture from an internal
        /// hashtable (or creates it if not found).
        /// </summary>
        public static CultureInfo GetCultureInfo(string name, string altName)
        {
            ArgumentNullException.ThrowIfNull(name);
            ArgumentNullException.ThrowIfNull(altName);
 
            name = CultureData.AnsiToLower(name);
            altName = CultureData.AnsiToLower(altName);
            string nameAndAltName = name + "\xfffd" + altName;
            Dictionary<string, CultureInfo> nameTable = CachedCulturesByName;
            CultureInfo? result;
 
            lock (nameTable)
            {
                if (nameTable.TryGetValue(nameAndAltName, out result))
                {
                    return result;
                }
            }
 
            try
            {
                result = new CultureInfo(name, altName) { _isReadOnly = true };
                result.TextInfo.SetReadOnlyState(readOnly: true); // TextInfo object is already created; we need to set it as read only.
            }
            catch (ArgumentException)
            {
#pragma warning disable CA2208 // Instantiate argument exceptions correctly, combination of arguments used
                throw new CultureNotFoundException("name/altName", SR.Format(SR.Argument_OneOfCulturesNotSupported, name, altName));
#pragma warning restore CA2208
            }
 
            lock (nameTable)
            {
                nameTable[nameAndAltName] = result;
            }
 
            return result;
        }
 
        public static CultureInfo GetCultureInfo(string name, bool predefinedOnly)
        {
            ArgumentNullException.ThrowIfNull(name);
 
            if (predefinedOnly && !GlobalizationMode.Invariant)
            {
                if (GlobalizationMode.UseNls ? !CultureData.NlsIsEnsurePredefinedLocaleName(name) : !CultureData.IcuIsEnsurePredefinedLocaleName(name))
                {
                    throw new CultureNotFoundException(nameof(name), name, SR.Format(SR.Argument_InvalidPredefinedCultureName, name));
                }
            }
 
            return GetCultureInfo(name);
        }
 
        private static Dictionary<string, CultureInfo> CachedCulturesByName
        {
            get
            {
                Dictionary<string, CultureInfo>? cache = s_cachedCulturesByName;
                if (cache is null)
                {
                    cache = new Dictionary<string, CultureInfo>();
                    cache = Interlocked.CompareExchange(ref s_cachedCulturesByName, cache, null) ?? cache;
                }
 
                return cache;
            }
        }
 
        private static Dictionary<int, CultureInfo> CachedCulturesByLcid
        {
            get
            {
                Dictionary<int, CultureInfo>? cache = s_cachedCulturesByLcid;
                if (cache is null)
                {
                    cache = new Dictionary<int, CultureInfo>();
                    cache = Interlocked.CompareExchange(ref s_cachedCulturesByLcid, cache, null) ?? cache;
                }
 
                return cache;
            }
        }
 
        public static CultureInfo GetCultureInfoByIetfLanguageTag(string name)
        {
            // Disallow old zh-CHT/zh-CHS names
            if (name == "zh-CHT" || name == "zh-CHS")
            {
                throw new CultureNotFoundException(nameof(name), SR.Format(SR.Argument_CultureIetfNotSupported, name));
            }
 
            CultureInfo ci = GetCultureInfo(name);
 
            // Disallow alt sorts and es-es_TS
            if (ci.LCID > 0xffff || ci.LCID == 0x040a)
            {
                throw new CultureNotFoundException(nameof(name), SR.Format(SR.Argument_CultureIetfNotSupported, name));
            }
 
            return ci;
        }
    }
}