2005-08-30 Sebastien Pouliot <sebastien@ximian.com>
[mono.git] / mcs / class / corlib / System.Globalization / CultureInfo.cs
1 //
2 // System.Globalization.CultureInfo.cs
3 //
4 // Miguel de Icaza (miguel@ximian.com)
5 // Dick Porter (dick@ximian.com)
6 //
7 // (C) 2001, 2002, 2003 Ximian, Inc. (http://www.ximian.com)
8 //
9
10 //
11 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 using System.Collections;
34 using System.Threading;
35 using System.Runtime.CompilerServices;
36
37 namespace System.Globalization
38 {
39         [Serializable]
40         public class CultureInfo : ICloneable, IFormatProvider
41         {
42                 static volatile CultureInfo invariant_culture_info;
43                 internal static int BootstrapCultureID;
44
45                 const int NumOptionalCalendars = 5;
46                 const int GregorianTypeMask = 0x00FFFFFF;
47                 const int CalendarTypeBits = 24;
48
49                 bool m_isReadOnly;
50                 int  cultureID;
51                 [NonSerialized]
52                 int parent_lcid;
53                 [NonSerialized]
54                 int specific_lcid;
55                 [NonSerialized]
56                 int datetime_index;
57                 [NonSerialized]
58                 int number_index;
59                 bool m_useUserOverride;
60                 volatile NumberFormatInfo numInfo;
61                 volatile DateTimeFormatInfo dateTimeInfo;
62                 volatile TextInfo textInfo;
63                 private string m_name;
64                 
65                 [NonSerialized]
66                 private string displayname;
67                 [NonSerialized]
68                 private string englishname;
69                 [NonSerialized]
70                 private string nativename;
71                 [NonSerialized]
72                 private string iso3lang;
73                 [NonSerialized]
74                 private string iso2lang;
75                 [NonSerialized]
76                 private string icu_name;
77                 [NonSerialized]
78                 private string win3lang;
79                 volatile CompareInfo compareInfo;
80                 [NonSerialized]
81                 private unsafe readonly int *calendar_data;
82                 [NonSerialized]
83                 private unsafe readonly void *textinfo_data;
84                 [NonSerialized]
85                 private Calendar [] optional_calendars;
86                                 
87                 int m_dataItem;         // MS.NET serializes this.
88                 Calendar calendar;      // MS.NET serializes this.
89                 
90                 // Deserialized instances will set this to false
91                 [NonSerialized]
92                 bool constructed;
93                 
94                 private static readonly string MSG_READONLY = "This instance is read only";
95                 
96                 static public CultureInfo InvariantCulture {
97                         get {
98                                 return invariant_culture_info;
99                         }
100                 }
101
102                 static CultureInfo ()
103                 {
104                         invariant_culture_info = new CultureInfo (0x7f, false);
105                         invariant_culture_info.m_isReadOnly = true;
106                 }
107                 
108                 public static CultureInfo CreateSpecificCulture (string name)
109                 {
110                         if (name == null) {
111                                 throw new ArgumentNullException ("name");
112                         }
113
114                         if (name == String.Empty)
115                                 return InvariantCulture;
116
117                         CultureInfo ci = new CultureInfo ();
118                         if (!ConstructInternalLocaleFromSpecificName (ci, name.ToLowerInvariant ()))
119                                 throw new ArgumentException ("Culture name " + name +
120                                                 " is not supported.", name);
121
122                         return ci;
123                 }
124
125                 public static CultureInfo CurrentCulture 
126                 {
127                         get {
128                                 return Thread.CurrentThread.CurrentCulture;
129                         }
130                 }
131
132                 public static CultureInfo CurrentUICulture 
133                 {
134                         get {
135                                 return Thread.CurrentThread.CurrentUICulture;
136                         }
137                 }
138
139                 internal static CultureInfo ConstructCurrentCulture ()
140                 {
141                         CultureInfo ci = new CultureInfo ();
142                         if (!ConstructInternalLocaleFromCurrentLocale (ci))
143                                 ci = InvariantCulture;
144                         BootstrapCultureID = ci.cultureID;
145                         return ci;
146                 }
147
148                 internal static CultureInfo ConstructCurrentUICulture ()
149                 {
150                         return ConstructCurrentCulture ();
151                 }
152
153                 public virtual int LCID {
154                         get {
155                                 return cultureID;
156                         }
157                 }
158
159                 public virtual string Name {
160                         get {
161                                 return(m_name);
162                         }
163                 }
164
165                 public virtual string NativeName
166                 {
167                         get {
168                                 if (!constructed) Construct ();
169                                 return(nativename);
170                         }
171                 }
172                 
173                 public virtual Calendar Calendar
174                 {
175                         get { return DateTimeFormat.Calendar; }
176                 }
177
178                 public virtual Calendar[] OptionalCalendars
179                 {
180                         get {
181                                 if (optional_calendars == null) {
182                                         lock (this) {
183                                                 if (optional_calendars == null)
184                                                         ConstructCalendars ();
185                                         }
186                                 }
187                                 return optional_calendars;
188                         }
189                 }
190
191                 public virtual CultureInfo Parent
192                 {
193                         get {
194                                 if (cultureID == 0x7f)
195                                         return this;
196                                 if (!constructed) Construct ();
197                                 if (parent_lcid == cultureID)
198                                         return null;
199                                 return new CultureInfo (parent_lcid);
200                         }
201                 }
202
203                 public unsafe virtual TextInfo TextInfo
204                 {
205                         get {
206                                 if (textInfo == null) {
207                                         lock (this) {
208                                                 if(textInfo == null) {
209                                                         textInfo = new TextInfo (this, cultureID, textinfo_data);
210                                                 }
211                                         }
212                                 }
213                                 
214                                 return(textInfo);
215                         }
216                 }
217
218                 public virtual string ThreeLetterISOLanguageName
219                 {
220                         get {
221                                 if (!constructed) Construct ();
222                                 return(iso3lang);
223                         }
224                 }
225
226                 public virtual string ThreeLetterWindowsLanguageName
227                 {
228                         get {
229                                 if (!constructed) Construct ();
230                                 return(win3lang);
231                         }
232                 }
233
234                 public virtual string TwoLetterISOLanguageName
235                 {
236                         get {
237                                 if (!constructed) Construct ();
238                                 return(iso2lang);
239                         }
240                 }
241
242                 public bool UseUserOverride
243                 {
244                         get {
245                                 return m_useUserOverride;
246                         }
247                 }
248
249                 internal string IcuName {
250                         get {
251                                 if (!constructed) Construct ();
252                                 return icu_name;
253                         }
254                 }
255
256                 public void ClearCachedData()
257                 {
258                         Thread.CurrentThread.CurrentCulture = null;
259                         Thread.CurrentThread.CurrentUICulture = null;
260                 }
261
262                 public virtual object Clone()
263                 {
264                         if (!constructed) Construct ();
265                         CultureInfo ci=(CultureInfo)MemberwiseClone ();
266                         ci.m_isReadOnly=false;
267                         ci.NumberFormat = (NumberFormatInfo)NumberFormat.Clone ();
268                         ci.DateTimeFormat = (DateTimeFormatInfo)DateTimeFormat.Clone ();
269                         return(ci);
270                 }
271
272                 public override bool Equals (object value)
273                 {
274                         CultureInfo b = value as CultureInfo;
275                         
276                         if (b != null)
277                                 return b.cultureID == cultureID;
278                         return false;
279                 }
280
281                 public static CultureInfo[] GetCultures(CultureTypes types)
282                 {
283                         bool neutral=((types & CultureTypes.NeutralCultures)!=0);
284                         bool specific=((types & CultureTypes.SpecificCultures)!=0);
285                         bool installed=((types & CultureTypes.InstalledWin32Cultures)!=0);  // TODO
286
287                         CultureInfo [] infos = internal_get_cultures (neutral, specific, installed);
288                         // The runtime returns a NULL in the first position of the array when
289                         // 'neutral' is true. We fill it in with the InvariantCulture.
290                         if (neutral && infos.Length > 0 && infos [0] == null)
291                                 infos [0] = InvariantCulture;
292
293                         return infos;
294                 }
295
296                 public override int GetHashCode()
297                 {
298                         return cultureID;
299                 }
300
301                 public static CultureInfo ReadOnly(CultureInfo ci)
302                 {
303                         if(ci==null) {
304                                 throw new ArgumentNullException("ci");
305                         }
306
307                         if(ci.m_isReadOnly) {
308                                 return(ci);
309                         } else {
310                                 CultureInfo new_ci=(CultureInfo)ci.Clone ();
311                                 new_ci.m_isReadOnly=true;
312                                 return(new_ci);
313                         }
314                 }
315
316                 public override string ToString()
317                 {
318                         return(m_name);
319                 }
320                 
321                 public virtual CompareInfo CompareInfo
322                 {
323                         get {
324                                 if (!constructed) Construct ();
325                                 if(compareInfo==null) {
326                                         lock (this) {
327                                                 if(compareInfo==null) {
328                                                         compareInfo=new CompareInfo (this);
329                                                 }
330                                         }
331                                 }
332                                 
333                                 return(compareInfo);
334                         }
335                 }
336
337                 internal static bool IsIDNeutralCulture (int lcid)
338                 {
339                         bool ret;
340                         if (!internal_is_lcid_neutral (lcid, out ret))
341                                 throw new ArgumentException (String.Format ("Culture id 0x{:x4} is not supported.", lcid));
342                                 
343                         return ret;
344                 }
345
346                 public virtual bool IsNeutralCulture {
347                         get {
348                                 if (!constructed) Construct ();
349                                 if (cultureID == 0x7f)
350                                         return false;
351
352                                 return ((cultureID & 0xff00) == 0 || specific_lcid == 0);
353                         }
354                 }
355
356                 internal void CheckNeutral ()
357                 {
358                         if (IsNeutralCulture) {
359                                 throw new NotSupportedException ("Culture \"" + m_name + "\" is " +
360                                                 "a neutral culture. It can not be used in formatting " +
361                                                 "and parsing and therefore cannot be set as the thread's " +
362                                                 "current culture.");
363                         }
364                 }
365
366                 public virtual NumberFormatInfo NumberFormat {
367                         get {
368                                 if (!constructed) Construct ();
369                                 CheckNeutral ();
370                                 if (numInfo == null){
371                                         lock (this){
372                                                 if (numInfo == null) {
373                                                         numInfo = new NumberFormatInfo ();
374                                                         construct_number_format ();
375                                                 }
376                                         }
377                                 }
378
379                                 return numInfo;
380                         }
381
382                         set {
383                                 if (!constructed) Construct ();
384                                 if (m_isReadOnly) throw new InvalidOperationException(MSG_READONLY);
385
386                                 if (value == null)
387                                         throw new ArgumentNullException ("NumberFormat");
388                                 
389                                 numInfo = value;
390                         }
391                 }
392
393                 public virtual DateTimeFormatInfo DateTimeFormat
394                 {
395                         get 
396                         {
397                                 if (!constructed) Construct ();
398                                 CheckNeutral ();
399                                 if (dateTimeInfo == null)
400                                 {
401                                         lock (this)
402                                         {
403                                                 if (dateTimeInfo == null) {
404                                                         dateTimeInfo = new DateTimeFormatInfo();
405                                                         construct_datetime_format ();
406                                                         if (optional_calendars != null)
407                                                                 dateTimeInfo.Calendar = optional_calendars [0];
408                                                 }
409                                         }
410                                 }
411
412                                 return dateTimeInfo;
413                         }
414
415                         set 
416                         {
417                                 if (!constructed) Construct ();
418                                 if (m_isReadOnly) throw new InvalidOperationException(MSG_READONLY);
419
420                                 if (value == null)
421                                         throw new ArgumentNullException ("DateTimeFormat");
422                                 
423                                 dateTimeInfo = value;
424                         }
425                 }
426
427                 public virtual string DisplayName
428                 {
429                         get {
430                                 if (!constructed) Construct ();
431                                 return(displayname);
432                         }
433                 }
434
435                 public virtual string EnglishName
436                 {
437                         get {
438                                 if (!constructed) Construct ();
439                                 return(englishname);
440                         }
441                 }
442
443                 [MonoTODO]
444                 public static CultureInfo InstalledUICulture
445                 {
446                         get {
447                                 return(null);
448                         }
449                 }
450
451                 public bool IsReadOnly 
452                 {
453                         get {
454                                 return(m_isReadOnly);
455                         }
456                 }
457                 
458
459                 // 
460                 // IFormatProvider implementation
461                 //
462                 public virtual object GetFormat( Type formatType )
463                 {
464                         object format = null;
465
466                         if ( formatType == typeof(NumberFormatInfo) )
467                                 format = NumberFormat;
468                         else if ( formatType == typeof(DateTimeFormatInfo) )
469                                 format = DateTimeFormat;
470                         
471                         return format;
472                 }
473                 
474                 void Construct ()
475                 {
476                         construct_internal_locale_from_lcid (cultureID);
477                         constructed = true;
478                 }
479
480                 bool ConstructInternalLocaleFromName (string locale)
481                 {
482                         if (!construct_internal_locale_from_name (locale))
483                                 return false;
484                         return true;
485                 }
486
487                 bool ConstructInternalLocaleFromLcid (int lcid)
488                 {
489                         if (!construct_internal_locale_from_lcid (lcid))
490                                 return false;
491                         return true;
492                 }
493
494                 static bool ConstructInternalLocaleFromSpecificName (CultureInfo ci, string name)
495                 {
496                         if (!construct_internal_locale_from_specific_name (ci, name))
497                                 return false;
498                         return true;
499                 }
500
501                 static bool ConstructInternalLocaleFromCurrentLocale (CultureInfo ci)
502                 {
503                         if (!construct_internal_locale_from_current_locale (ci))
504                                 return false;
505                         return true;
506                 }
507
508                 static CultureInfo [] GetCultures (bool neutral, bool specific, bool installed)
509                 {
510                         CultureInfo [] cis = internal_get_cultures (neutral, specific, installed);
511
512                         // The runtime returns a NULL in the first position of the array when
513                         // 'neutral' is true. We fill it in with the InvariantCulture.
514                         if (neutral && cis.Length > 0 && cis [0] == null)
515                                 cis [0] = InvariantCulture;
516
517                         return cis;
518                 }
519
520                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
521                 private extern bool construct_internal_locale_from_lcid (int lcid);
522
523                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
524                 private extern bool construct_internal_locale_from_name (string name);
525
526                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
527                 private extern static bool construct_internal_locale_from_specific_name (CultureInfo ci,
528                                 string name);
529
530                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
531                 private extern static bool construct_internal_locale_from_current_locale (CultureInfo ci);
532
533                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
534                 private extern static CultureInfo [] internal_get_cultures (bool neutral, bool specific, bool installed);
535
536                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
537                 private extern void construct_datetime_format ();
538
539                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
540                 private extern void construct_number_format ();
541
542                 // Returns false if the culture can not be found, sets is_neutral if it is
543                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
544                 private extern static bool internal_is_lcid_neutral (int lcid, out bool is_neutral);
545
546                 private unsafe void ConstructInvariant (bool use_user_override)
547                 {
548                         m_isReadOnly=false;
549                         cultureID=0x7f;
550                         this.m_useUserOverride=use_user_override;
551
552                         /* NumberFormatInfo defaults to the invariant data */
553                         numInfo=NumberFormatInfo.InvariantInfo;
554                         
555                         /* DateTimeFormatInfo defaults to the invariant data */
556                         dateTimeInfo=DateTimeFormatInfo.InvariantInfo;
557
558                         textInfo=new TextInfo (this, cultureID, this.textinfo_data);
559
560                         m_name="";
561                         displayname="Invariant Language (Invariant Country)";
562                         englishname="Invariant Language (Invariant Country)";
563                         nativename="Invariant Language (Invariant Country)";
564                         iso3lang="IVL";
565                         iso2lang="iv";
566                         icu_name="en_US_POSIX";
567                         win3lang="IVL";
568                 }
569                 
570                 public CultureInfo (int culture, bool use_user_override)
571                 {
572                         if (culture < 0)
573                                 throw new ArgumentOutOfRangeException ("Positive number required.",
574                                                 "culture");
575
576                         constructed = true;
577                         
578                         if(culture==0x007f) {
579                                 /* Short circuit the invariant culture */
580                                 ConstructInvariant (use_user_override);
581                                 return;
582                         }
583
584                         if (!ConstructInternalLocaleFromLcid (culture))
585                                 throw new ArgumentException (
586                                         String.Format ("Culture ID {0} (0x{0:X4}) is not a " +
587                                                         "supported culture.", culture), "culture");
588                 }
589
590                 public CultureInfo (int culture) : this (culture, false) {}
591                 
592                 public CultureInfo (string name, bool use_user_override)
593                 {
594                         if (name == null)
595                                 throw new ArgumentNullException ();
596
597                         constructed = true;
598                         
599                         if(name=="") {
600                                 /* Short circuit the invariant culture */
601                                 ConstructInvariant (use_user_override);
602                                 return;
603                         }
604
605                         if (!ConstructInternalLocaleFromName (name.ToLowerInvariant ()))
606                                 throw new ArgumentException ("Culture name " + name +
607                                                 " is not supported.", "name");
608                 }
609
610                 public CultureInfo (string name) : this (name, true) {}
611
612                 // This is used when creating by specific name and creating by
613                 // current locale so we can initialize the object without
614                 // doing any member initialization
615                 private CultureInfo () { constructed = true; } 
616
617                 unsafe internal void ConstructCalendars ()
618                 {
619                         if (calendar_data == null) {
620                                 optional_calendars = new Calendar [] {new GregorianCalendar (GregorianCalendarTypes.Localized)};
621                                 return;
622                         }
623
624                         optional_calendars = new Calendar [NumOptionalCalendars];
625
626                         for (int i=0; i<NumOptionalCalendars; i++) {
627                                 Calendar cal = null;
628                                 int caldata = *(calendar_data + i);
629                                 int caltype = (caldata >> CalendarTypeBits);
630                                 switch (caltype) {
631                                 case 0:
632                                         GregorianCalendarTypes greg_type;
633                                         greg_type = (GregorianCalendarTypes) (caldata & GregorianTypeMask);
634                                         cal = new GregorianCalendar (greg_type);
635                                         break;
636                                 case 1:
637                                         cal = new HijriCalendar ();
638                                         break;
639                                 case 2:
640                                         cal = new ThaiBuddhistCalendar ();
641                                         break;
642                                 default:
643                                         throw new Exception ("invalid calendar type:  " + caldata);
644                                 }
645                                 optional_calendars [i] = cal;
646                         }
647                 }
648         }
649 }