//
// System.Globalization.CultureInfo.cs
//
// Miguel de Icaza (miguel@ximian.com)
// Dick Porter (dick@ximian.com)
//
// (C) 2001, 2002, 2003 Ximian, Inc. (http://www.ximian.com)
//
//
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System.Collections;
using System.Threading;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace System.Globalization{
[System.Runtime.InteropServices.ComVisible (true)]
[Serializable]
public class CultureInfo : ICloneable, IFormatProvider
{
static volatile CultureInfo invariant_culture_info;
static object shared_table_lock = new object ();
internal static int BootstrapCultureID;
const int NumOptionalCalendars = 5;
const int GregorianTypeMask = 0x00FFFFFF;
const int CalendarTypeBits = 24;
#pragma warning disable 169, 649
bool m_isReadOnly;
int cultureID;
[NonSerialized]
int parent_lcid;
[NonSerialized]
int specific_lcid;
[NonSerialized]
int datetime_index;
[NonSerialized]
int number_index;
bool m_useUserOverride;
[NonSerialized]
volatile NumberFormatInfo numInfo;
volatile DateTimeFormatInfo dateTimeInfo;
volatile TextInfo textInfo;
private string m_name;
[NonSerialized]
private string displayname;
[NonSerialized]
private string englishname;
[NonSerialized]
private string nativename;
[NonSerialized]
private string iso3lang;
[NonSerialized]
private string iso2lang;
[NonSerialized]
private string icu_name;
[NonSerialized]
private string win3lang;
[NonSerialized]
private string territory;
volatile CompareInfo compareInfo;
[NonSerialized]
private unsafe readonly int *calendar_data;
[NonSerialized]
private unsafe readonly void *textinfo_data;
[NonSerialized]
private Calendar [] optional_calendars;
[NonSerialized]
CultureInfo parent_culture;
int m_dataItem; // MS.NET serializes this.
Calendar calendar; // MS.NET serializes this.
#pragma warning restore 169, 649
// Deserialized instances will set this to false
[NonSerialized]
bool constructed;
[NonSerialized]
// Used by Thread.set_CurrentCulture
internal byte[] cached_serialized_form;
const int InvariantCultureId = 0x7F;
private static readonly string MSG_READONLY = "This instance is read only";
static public CultureInfo InvariantCulture {
get {
return invariant_culture_info;
}
}
static CultureInfo ()
{
invariant_culture_info = new CultureInfo (InvariantCultureId, false, true);
}
public static CultureInfo CreateSpecificCulture (string name)
{
if (name == null) {
throw new ArgumentNullException ("name");
}
if (name == String.Empty)
return InvariantCulture;
CultureInfo ci = new CultureInfo ();
if (!ConstructInternalLocaleFromSpecificName (ci, name.ToLowerInvariant ()))
throw new ArgumentException ("Culture name " + name +
" is not supported.", name);
return ci;
}
public static CultureInfo CurrentCulture
{
get {
return Thread.CurrentThread.CurrentCulture;
}
}
public static CultureInfo CurrentUICulture
{
get {
return Thread.CurrentThread.CurrentUICulture;
}
}
internal static CultureInfo ConstructCurrentCulture ()
{
CultureInfo ci = new CultureInfo ();
if (!ConstructInternalLocaleFromCurrentLocale (ci))
ci = InvariantCulture;
BootstrapCultureID = ci.cultureID;
return ci;
}
internal static CultureInfo ConstructCurrentUICulture ()
{
return ConstructCurrentCulture ();
}
// it is used for RegionInfo.
internal string Territory {
get { return territory; }
}
#if !NET_2_1
// FIXME: It is implemented, but would be hell slow.
[ComVisible (false)]
public CultureTypes CultureTypes {
get {
CultureTypes ret = (CultureTypes) 0;
foreach (CultureTypes v in Enum.GetValues (typeof (CultureTypes)))
if (Array.IndexOf (GetCultures (v), this) >= 0)
ret |= v;
return ret;
}
}
[ComVisible (false)]
public CultureInfo GetConsoleFallbackUICulture ()
{
// as documented in MSDN ...
switch (Name) {
case "ar": case "ar-BH": case "ar-EG": case "ar-IQ":
case "ar-JO": case "ar-KW": case "ar-LB": case "ar-LY":
case "ar-QA": case "ar-SA": case "ar-SY": case "ar-AE":
case "ar-YE":
case "dv": case "dv-MV":
case "fa": case "fa-IR":
case "gu": case "gu-IN":
case "he": case "he-IL":
case "hi": case "hi-IN":
case "kn": case "kn-IN":
case "kok": case "kok-IN":
case "mr": case "mr-IN":
case "pa": case "pa-IN":
case "sa": case "sa-IN":
case "syr": case "syr-SY":
case "ta": case "ta-IN":
case "te": case "te-IN":
case "th": case "th-TH":
case "ur": case "ur-PK":
case "vi": case "vi-VN":
return GetCultureInfo ("en");
case "ar-DZ": case "ar-MA": case "ar-TN":
return GetCultureInfo ("fr");
}
return (CultureTypes & CultureTypes.WindowsOnlyCultures) != 0 ? CultureInfo.InvariantCulture : this;
}
[ComVisible (false)]
public string IetfLanguageTag {
// There could be more consistent way to implement
// it, but right now it works just fine with this...
get {
switch (Name) {
case "zh-CHS":
return "zh-Hans";
case "zh-CHT":
return "zh-Hant";
default:
return Name;
}
}
}
// For specific cultures it basically returns LCID.
// For neutral cultures it is mapped to the default(?) specific
// culture, where the LCID of the specific culture seems to be
// n + 1024 by default. zh-CHS is the only exception which is
// mapped to 2052, not 1028 (zh-CHT is mapped to 1028 instead).
// There are very few exceptions, here I simply list them here.
// It is Windows-specific property anyways, so no worthy of
// trying to do some complex things with locale-builder.
[ComVisible (false)]
public virtual int KeyboardLayoutId {
get {
switch (LCID) {
case 4: // zh-CHS (neutral)
return 2052;
case 1034: // es-ES Spanish 2
return 3082;
case 31748: // zh-CHT (neutral)
return 1028;
case 31770: // sr (neutral)
return 2074;
default:
return LCID < 1024 ? LCID + 1024 : LCID;
}
}
}
#endif
public virtual int LCID {
get {
return cultureID;
}
}
public virtual string Name {
get {
#if MOONLIGHT
if (m_name == "zh-CHS")
return "zh-Hans";
if (m_name == "zh-CHT")
return "zh-Hant";
#endif
return(m_name);
}
}
public virtual string NativeName
{
get {
if (!constructed) Construct ();
return(nativename);
}
}
public virtual Calendar Calendar
{
get { return DateTimeFormat.Calendar; }
}
public virtual Calendar[] OptionalCalendars
{
get {
if (optional_calendars == null) {
lock (this) {
if (optional_calendars == null)
ConstructCalendars ();
}
}
return optional_calendars;
}
}
public virtual CultureInfo Parent
{
get {
if (parent_culture == null) {
if (!constructed)
Construct ();
if (parent_lcid == cultureID)
return null;
if (parent_lcid == InvariantCultureId)
parent_culture = InvariantCulture;
else if (cultureID == InvariantCultureId)
parent_culture = this;
else
parent_culture = new CultureInfo (parent_lcid);
}
return parent_culture;
}
}
public virtual TextInfo TextInfo
{
get {
if (textInfo == null) {
if (!constructed) Construct ();
lock (this) {
if(textInfo == null) {
textInfo = CreateTextInfo (m_isReadOnly);
}
}
}
return(textInfo);
}
}
public virtual string ThreeLetterISOLanguageName
{
get {
if (!constructed) Construct ();
return(iso3lang);
}
}
public virtual string ThreeLetterWindowsLanguageName
{
get {
if (!constructed) Construct ();
return(win3lang);
}
}
public virtual string TwoLetterISOLanguageName
{
get {
if (!constructed) Construct ();
return(iso2lang);
}
}
public bool UseUserOverride
{
get {
return m_useUserOverride;
}
}
internal string IcuName {
get {
if (!constructed) Construct ();
return icu_name;
}
}
public void ClearCachedData()
{
Thread.CurrentThread.CurrentCulture = null;
Thread.CurrentThread.CurrentUICulture = null;
}
public virtual object Clone()
{
if (!constructed) Construct ();
CultureInfo ci=(CultureInfo)MemberwiseClone ();
ci.m_isReadOnly=false;
ci.cached_serialized_form=null;
if (!IsNeutralCulture) {
ci.NumberFormat = (NumberFormatInfo)NumberFormat.Clone ();
ci.DateTimeFormat = (DateTimeFormatInfo)DateTimeFormat.Clone ();
}
return(ci);
}
public override bool Equals (object value)
{
CultureInfo b = value as CultureInfo;
if (b != null)
return b.cultureID == cultureID;
return false;
}
#if !MOONLIGHT
public static CultureInfo[] GetCultures(CultureTypes types)
{
bool neutral=((types & CultureTypes.NeutralCultures)!=0);
bool specific=((types & CultureTypes.SpecificCultures)!=0);
bool installed=((types & CultureTypes.InstalledWin32Cultures)!=0); // TODO
CultureInfo [] infos = internal_get_cultures (neutral, specific, installed);
// The runtime returns a NULL in the first position of the array when
// 'neutral' is true. We fill it in with a clone of InvariantCulture
// since it must not be read-only
if (neutral && infos.Length > 0 && infos [0] == null) {
infos [0] = (CultureInfo) InvariantCulture.Clone ();
}
return infos;
}
#endif
public override int GetHashCode()
{
return cultureID;
}
public static CultureInfo ReadOnly(CultureInfo ci)
{
if(ci==null) {
throw new ArgumentNullException("ci");
}
if(ci.m_isReadOnly) {
return(ci);
} else {
CultureInfo new_ci=(CultureInfo)ci.Clone ();
new_ci.m_isReadOnly=true;
if (new_ci.numInfo != null)
new_ci.numInfo = NumberFormatInfo.ReadOnly (new_ci.numInfo);
if (new_ci.dateTimeInfo != null)
new_ci.dateTimeInfo = DateTimeFormatInfo.ReadOnly (new_ci.dateTimeInfo);
// TextInfo doesn't have a ReadOnly method in 1.1...
if (new_ci.textInfo != null)
new_ci.textInfo = TextInfo.ReadOnly (new_ci.textInfo);
return(new_ci);
}
}
public override string ToString()
{
return(m_name);
}
public virtual CompareInfo CompareInfo
{
get {
if(compareInfo==null) {
if (!constructed)
Construct ();
lock (this) {
if(compareInfo==null) {
compareInfo=new CompareInfo (this);
}
}
}
return(compareInfo);
}
}
internal static bool IsIDNeutralCulture (int lcid)
{
bool ret;
if (!internal_is_lcid_neutral (lcid, out ret))
throw new ArgumentException (String.Format ("Culture id 0x{:x4} is not supported.", lcid));
return ret;
}
public virtual bool IsNeutralCulture {
get {
if (!constructed) Construct ();
if (cultureID == InvariantCultureId)
return false;
return ((cultureID & 0xff00) == 0 || specific_lcid == 0);
}
}
internal void CheckNeutral ()
{
#if !MOONLIGHT
if (IsNeutralCulture) {
throw new NotSupportedException ("Culture \"" + m_name + "\" is " +
"a neutral culture. It can not be used in formatting " +
"and parsing and therefore cannot be set as the thread's " +
"current culture.");
}
#endif
}
public virtual NumberFormatInfo NumberFormat {
get {
if (!constructed) Construct ();
CheckNeutral ();
if (numInfo == null){
lock (this){
if (numInfo == null) {
numInfo = new NumberFormatInfo (m_isReadOnly);
construct_number_format ();
}
}
}
return numInfo;
}
set {
if (!constructed) Construct ();
if (m_isReadOnly) throw new InvalidOperationException(MSG_READONLY);
if (value == null)
throw new ArgumentNullException ("NumberFormat");
numInfo = value;
}
}
public virtual DateTimeFormatInfo DateTimeFormat
{
get
{
if (!constructed) Construct ();
CheckNeutral ();
if (dateTimeInfo == null)
{
lock (this)
{
if (dateTimeInfo == null) {
dateTimeInfo = new DateTimeFormatInfo(m_isReadOnly);
construct_datetime_format ();
if (optional_calendars != null)
dateTimeInfo.Calendar = optional_calendars [0];
}
}
}
return dateTimeInfo;
}
set
{
if (!constructed) Construct ();
if (m_isReadOnly) throw new InvalidOperationException(MSG_READONLY);
if (value == null)
throw new ArgumentNullException ("DateTimeFormat");
dateTimeInfo = value;
}
}
public virtual string DisplayName
{
get {
if (!constructed) Construct ();
return(displayname);
}
}
public virtual string EnglishName
{
get {
if (!constructed) Construct ();
return(englishname);
}
}
public static CultureInfo InstalledUICulture
{
get { return GetCultureInfo (BootstrapCultureID); }
}
public bool IsReadOnly
{
get {
return(m_isReadOnly);
}
}
//
// IFormatProvider implementation
//
public virtual object GetFormat( Type formatType )
{
object format = null;
if ( formatType == typeof(NumberFormatInfo) )
format = NumberFormat;
else if ( formatType == typeof(DateTimeFormatInfo) )
format = DateTimeFormat;
return format;
}
void Construct ()
{
construct_internal_locale_from_lcid (cultureID);
constructed = true;
}
bool ConstructInternalLocaleFromName (string locale)
{
// It is sort of hack to get those new pseudo-alias
// culture names that are not supported in good old
// Windows.
#if MOONLIGHT
if (locale == "zh-chs" || locale == "zh-cht")
return false;
#endif
switch (locale) {
case "zh-hans":
locale = "zh-chs";
break;
case "zh-hant":
locale = "zh-cht";
break;
}
if (!construct_internal_locale_from_name (locale))
return false;
return true;
}
bool ConstructInternalLocaleFromLcid (int lcid)
{
if (!construct_internal_locale_from_lcid (lcid))
return false;
return true;
}
static bool ConstructInternalLocaleFromSpecificName (CultureInfo ci, string name)
{
if (!construct_internal_locale_from_specific_name (ci, name))
return false;
return true;
}
static bool ConstructInternalLocaleFromCurrentLocale (CultureInfo ci)
{
if (!construct_internal_locale_from_current_locale (ci))
return false;
return true;
}
[MethodImplAttribute (MethodImplOptions.InternalCall)]
private extern bool construct_internal_locale_from_lcid (int lcid);
[MethodImplAttribute (MethodImplOptions.InternalCall)]
private extern bool construct_internal_locale_from_name (string name);
[MethodImplAttribute (MethodImplOptions.InternalCall)]
private extern static bool construct_internal_locale_from_specific_name (CultureInfo ci,
string name);
[MethodImplAttribute (MethodImplOptions.InternalCall)]
private extern static bool construct_internal_locale_from_current_locale (CultureInfo ci);
[MethodImplAttribute (MethodImplOptions.InternalCall)]
private extern static CultureInfo [] internal_get_cultures (bool neutral, bool specific, bool installed);
[MethodImplAttribute (MethodImplOptions.InternalCall)]
private extern void construct_datetime_format ();
[MethodImplAttribute (MethodImplOptions.InternalCall)]
private extern void construct_number_format ();
// Returns false if the culture can not be found, sets is_neutral if it is
[MethodImplAttribute (MethodImplOptions.InternalCall)]
private extern static bool internal_is_lcid_neutral (int lcid, out bool is_neutral);
private void ConstructInvariant (bool read_only)
{
cultureID = InvariantCultureId;
/* NumberFormatInfo defaults to the invariant data */
numInfo=NumberFormatInfo.InvariantInfo;
/* DateTimeFormatInfo defaults to the invariant data */
dateTimeInfo=DateTimeFormatInfo.InvariantInfo;
if (!read_only) {
numInfo = (NumberFormatInfo) numInfo.Clone ();
dateTimeInfo = (DateTimeFormatInfo) dateTimeInfo.Clone ();
}
textInfo = CreateTextInfo (read_only);
m_name=String.Empty;
displayname=
englishname=
nativename="Invariant Language (Invariant Country)";
iso3lang="IVL";
iso2lang="iv";
icu_name="en_US_POSIX";
win3lang="IVL";
}
private unsafe TextInfo CreateTextInfo (bool readOnly)
{
return new TextInfo (this, cultureID, this.textinfo_data, readOnly);
}
public CultureInfo (int culture) : this (culture, true) {}
public CultureInfo (int culture, bool useUserOverride) :
this (culture, useUserOverride, false) {}
private CultureInfo (int culture, bool useUserOverride, bool read_only)
{
if (culture < 0)
throw new ArgumentOutOfRangeException ("culture", "Positive "
+ "number required.");
constructed = true;
m_isReadOnly = read_only;
m_useUserOverride = useUserOverride;
if (culture == InvariantCultureId) {
/* Short circuit the invariant culture */
ConstructInvariant (read_only);
return;
}
if (!ConstructInternalLocaleFromLcid (culture))
throw new ArgumentException (
String.Format ("Culture ID {0} (0x{0:X4}) is not a " +
"supported culture.", culture), "culture");
}
public CultureInfo (string name) : this (name, true) {}
public CultureInfo (string name, bool useUserOverride) :
this (name, useUserOverride, false) {}
private CultureInfo (string name, bool useUserOverride, bool read_only)
{
if (name == null)
throw new ArgumentNullException ("name");
constructed = true;
m_isReadOnly = read_only;
m_useUserOverride = useUserOverride;
if (name.Length == 0) {
/* Short circuit the invariant culture */
ConstructInvariant (read_only);
return;
}
if (!ConstructInternalLocaleFromName (name.ToLowerInvariant ()))
throw new ArgumentException ("Culture name " + name +
" is not supported.", "name");
}
// This is used when creating by specific name and creating by
// current locale so we can initialize the object without
// doing any member initialization
private CultureInfo () { constructed = true; }
static Hashtable shared_by_number, shared_by_name;
static void insert_into_shared_tables (CultureInfo c)
{
if (shared_by_number == null){
shared_by_number = new Hashtable ();
shared_by_name = new Hashtable ();
}
shared_by_number [c.cultureID] = c;
shared_by_name [c.m_name] = c;
}
public static CultureInfo GetCultureInfo (int culture)
{
CultureInfo c;
lock (shared_table_lock){
if (shared_by_number != null){
c = shared_by_number [culture] as CultureInfo;
if (c != null)
return (CultureInfo) c;
}
c = new CultureInfo (culture, false, true);
insert_into_shared_tables (c);
return c;
}
}
public static CultureInfo GetCultureInfo (string name)
{
if (name == null)
throw new ArgumentNullException ("name");
CultureInfo c;
lock (shared_table_lock){
if (shared_by_name != null){
c = shared_by_name [name] as CultureInfo;
if (c != null)
return (CultureInfo) c;
}
c = new CultureInfo (name, false, true);
insert_into_shared_tables (c);
return c;
}
}
[MonoTODO ("Currently it ignores the altName parameter")]
public static CultureInfo GetCultureInfo (string name, string altName) {
if (name == null)
throw new ArgumentNullException ("null");
if (altName == null)
throw new ArgumentNullException ("null");
return GetCultureInfo (name);
}
public static CultureInfo GetCultureInfoByIetfLanguageTag (string name)
{
// There could be more consistent way to implement
// it, but right now it works just fine with this...
switch (name) {
case "zh-Hans":
return GetCultureInfo ("zh-CHS");
case "zh-Hant":
return GetCultureInfo ("zh-CHT");
default:
return GetCultureInfo (name);
}
}
// used in runtime (icall.c) to construct CultureInfo for
// AssemblyName of assemblies
internal static CultureInfo CreateCulture (string name, bool reference)
{
bool read_only;
bool use_user_override;
bool invariant = name.Length == 0;
if (reference) {
use_user_override = invariant ? false : true;
read_only = false;
} else {
read_only = false;
use_user_override = invariant ? false : true;
}
return new CultureInfo (name, use_user_override, read_only);
}
unsafe internal void ConstructCalendars ()
{
if (calendar_data == null) {
optional_calendars = new Calendar [] {new GregorianCalendar (GregorianCalendarTypes.Localized)};
return;
}
optional_calendars = new Calendar [NumOptionalCalendars];
for (int i=0; i<NumOptionalCalendars; i++) {
Calendar cal = null;
int caldata = *(calendar_data + i);
int caltype = (caldata >> CalendarTypeBits);
switch (caltype) {
case 0:
GregorianCalendarTypes greg_type;
greg_type = (GregorianCalendarTypes) (caldata & GregorianTypeMask);
cal = new GregorianCalendar (greg_type);
break;
case 1:
cal = new HijriCalendar ();
break;
case 2:
cal = new ThaiBuddhistCalendar ();
break;
default:
throw new Exception ("invalid calendar type: " + caldata);
}
optional_calendars [i] = cal;
}
}
}
}
|