Merge pull request #1668 from alexanderkyte/bug1856
[mono.git] / mcs / class / corlib / ReferenceSources / CultureData.cs
1 //
2 // CultureData.cs
3 //
4 // Authors:
5 //      Marek Safar  <marek.safar@gmail.com>
6 //
7 // Copyright (C) 2015 Xamarin Inc (http://www.xamarin.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System;
30 using System.Diagnostics.Contracts;
31 using System.Text;
32 using System.Threading;
33 using System.Runtime.InteropServices;
34 using System.Runtime.CompilerServices;
35
36 namespace System.Globalization
37 {
38         [StructLayout (LayoutKind.Sequential)]
39         class CultureData
40         {
41 #region Sync with object-internals.h
42                 // Time
43                 private String sAM1159; // (user can override) AM designator
44                 private String sPM2359; // (user can override) PM designator
45                 private String sTimeSeparator;
46                 private volatile String[] saLongTimes; // (user can override) time format
47                 private volatile String[] saShortTimes; // short time format
48
49                 // Calendar specific data
50                 private int iFirstDayOfWeek; // (user can override) first day of week (gregorian really)
51                 private int iFirstWeekOfYear; // (user can override) first week of year (gregorian really)
52 #endregion              
53         private volatile int[] waCalendars; // all available calendar type(s).  The first one is the default calendar
54
55         // Store for specific data about each calendar
56         private CalendarData[] calendars; // Store for specific calendar data
57
58                 // Language
59                 private String sISO639Language; // ISO 639 Language Name
60
61                 readonly string sRealName;
62
63                 bool bUseOverrides;
64
65                 // TODO: should query runtime with culture name for a list of culture's calendars
66                 int calendarId;
67
68                 int numberIndex;
69
70                 int iDefaultAnsiCodePage;
71                 int iDefaultOemCodePage;
72                 int iDefaultMacCodePage;
73                 int iDefaultEbcdicCodePage;
74                 bool isRightToLeft;
75                 string sListSeparator;
76
77                 private CultureData (string name)
78                 {
79                         this.sRealName = name;
80                 }
81
82                 static CultureData s_Invariant;
83
84                 public static CultureData Invariant {
85                         get {
86                                 if (s_Invariant == null) {
87                                         var invariant = new CultureData ("");
88
89                                         // Language
90                                         invariant.sISO639Language = "iv";                   // ISO 639 Language Name
91
92                                         // Time
93                                         invariant.sAM1159 = "AM";                   // AM designator
94                                         invariant.sPM2359 = "PM";                   // PM designator
95                                         invariant.sTimeSeparator = ":";
96                                         invariant.saLongTimes = new String[] { "HH:mm:ss" };                             // time format
97                                         invariant.saShortTimes = new String[] { "HH:mm", "hh:mm tt", "H:mm", "h:mm tt" }; // short time format
98
99                                         // Calendar specific data
100                                         invariant.iFirstDayOfWeek = 0;                      // first day of week
101                                         invariant.iFirstWeekOfYear = 0;                      // first week of year
102                                         invariant.waCalendars = new int[] { (int)CalendarId.GREGORIAN };       // all available calendar type(s).  The first one is the default calendar
103
104                                         // Store for specific data about each calendar
105                                 invariant.calendars = new CalendarData[CalendarData.MAX_CALENDARS];
106                                 invariant.calendars[0] = CalendarData.Invariant;
107
108                                         invariant.iDefaultAnsiCodePage = 1252;                   // default ansi code page ID (ACP)
109                                         invariant.iDefaultOemCodePage = 437;                    // default oem code page ID (OCP or OEM)
110                                         invariant.iDefaultMacCodePage = 10000;                  // default macintosh code page
111                                         invariant.iDefaultEbcdicCodePage = 037;                    // default EBCDIC code page
112
113                                         invariant.sListSeparator = ",";
114                                         
115                                         Interlocked.CompareExchange (ref s_Invariant, invariant, null);
116                                 }
117
118                                 return s_Invariant;
119                         }
120                 }
121
122                 public static CultureData GetCultureData (string cultureName, bool useUserOverride)
123                 {
124                         try {
125                                 var ci = new CultureInfo (cultureName, useUserOverride);
126                                 return ci.m_cultureData;
127                         } catch {
128                                 return null;
129                         }
130                 }
131
132                 public static CultureData GetCultureData (string cultureName, bool useUserOverride, int datetimeIndex, int calendarId, int numberIndex, string iso2lang,
133                         int ansiCodePage, int oemCodePage, int macCodePage, int ebcdicCodePage, bool rightToLeft, string listSeparator)
134                 {
135                         if (string.IsNullOrEmpty (cultureName))
136                                 return Invariant;
137
138                         var cd = new CultureData (cultureName);
139                         cd.fill_culture_data (datetimeIndex);
140                         cd.bUseOverrides = useUserOverride;
141                         cd.calendarId = calendarId;
142                         cd.numberIndex = numberIndex;
143                         cd.sISO639Language = iso2lang;
144                         cd.iDefaultAnsiCodePage = ansiCodePage;
145                         cd.iDefaultOemCodePage = oemCodePage;
146                         cd.iDefaultMacCodePage = macCodePage;
147                         cd.iDefaultEbcdicCodePage = ebcdicCodePage;
148                         cd.isRightToLeft = rightToLeft;
149                         cd.sListSeparator = listSeparator;
150                         return cd;
151                 }
152
153                 internal static CultureData GetCultureData (int culture, bool bUseUserOverride)
154                 {
155                         // Legacy path which we should never hit
156                         return null;
157                 }
158
159                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
160                 extern void fill_culture_data (int datetimeIndex);
161
162                 public CalendarData GetCalendar (int calendarId)
163                 {
164                         // arrays are 0 based, calendarIds are 1 based
165                         int calendarIndex = calendarId - 1;
166
167                         // Have to have calendars
168                         if (calendars == null)
169                         {
170                                 calendars = new CalendarData[CalendarData.MAX_CALENDARS];
171                         }
172
173                         var calendarData = calendars[calendarIndex];
174                         if (calendarData == null) {
175                                 calendarData = new CalendarData (sRealName, calendarId, bUseOverrides);
176                                 calendars [calendarIndex] = calendarData;
177                         }
178
179                         return calendarData;
180                 }
181
182                 internal String[] LongTimes {
183                         get {
184                                 return saLongTimes;
185                         }
186                 }
187
188                 internal String[] ShortTimes {
189                         get {
190                                 return saShortTimes;
191                         }
192                 }
193
194                 internal String SISO639LANGNAME {
195                         get {
196                                 return sISO639Language;
197                         }
198                 }
199
200                 internal int IFIRSTDAYOFWEEK {
201                         get {
202                                 return iFirstDayOfWeek;
203                         }
204                 }
205
206                 internal int IFIRSTWEEKOFYEAR {
207                         get {
208                                 return iFirstWeekOfYear;
209                         }
210                 }
211
212                 internal String SAM1159 {
213                         get {
214                                 return sAM1159;
215                         }
216                 }
217
218                 internal String SPM2359 {
219                         get {
220                                 return sPM2359;
221                         }
222                 }
223
224                 internal String TimeSeparator {
225                         get {
226                                 return sTimeSeparator;
227                         }
228                 }
229
230                 internal int[] CalendarIds {
231                         get {
232                                 if (this.waCalendars == null) {
233                                         // Need this specialization because GetJapaneseCalendarDTFI/GetTaiwanCalendarDTFI depend on
234                                         // optional calendars
235                                         switch (sISO639Language) {
236                                         case "ja":
237                                                 waCalendars = new int[] { calendarId, Calendar.CAL_JAPAN };
238                                                 break;
239                                         case "zh":
240                                                 waCalendars = new int[] { calendarId, Calendar.CAL_TAIWAN };
241                                                 break;
242                                         default:
243                                                 waCalendars = new int [] { calendarId };
244                                                 break;
245                                         }
246                                 }
247
248                                 return waCalendars;
249                         }
250                 }
251
252                 internal bool IsInvariantCulture {
253                         get {
254                                 return string.IsNullOrEmpty (sRealName);
255                         }
256                 }
257
258                 internal String CultureName {
259                         get {
260                                 return sRealName;
261                         }
262                 }
263
264                 internal String SCOMPAREINFO {
265                         get {
266                                 return "";
267                         }
268                 }
269
270                 internal String STEXTINFO {
271                         get {
272                                 return sRealName;
273                         }
274                 }
275
276                 internal int ILANGUAGE {
277                         get {
278                                 return 0;
279                         }
280                 }
281
282                 internal int IDEFAULTANSICODEPAGE {
283                         get {
284                                 return iDefaultAnsiCodePage;
285                         }
286                 }
287
288                 internal int IDEFAULTOEMCODEPAGE {
289                         get {
290                                 return iDefaultOemCodePage;
291                         }
292                 }
293
294                 internal int IDEFAULTMACCODEPAGE {
295                         get {
296                                 return iDefaultMacCodePage;
297                         }
298                 }
299
300                 internal int IDEFAULTEBCDICCODEPAGE {
301                         get {
302                                 return iDefaultEbcdicCodePage;
303                         }
304                 }
305
306                 internal bool IsRightToLeft {
307                         get {
308                                 return isRightToLeft;
309                         }
310                 }
311
312                 internal String SLIST {
313                         get {
314                                 return sListSeparator;
315                         }
316                 }
317
318 #region from reference sources
319
320         // Are overrides enabled?
321         internal bool UseUserOverride
322         {
323             get
324             {
325                 return this.bUseOverrides;
326             }
327         }
328
329                 // Native calendar names.  index of optional calendar - 1, empty if no optional calendar at that number
330                 internal String CalendarName(int calendarId)
331                 {
332                         // Get the calendar
333                         return GetCalendar(calendarId).sNativeName;
334                 }
335
336                 // All of our era names
337                 internal String[] EraNames(int calendarId)
338                 {
339                         Contract.Assert(calendarId > 0, "[CultureData.saEraNames] Expected Calendar.ID > 0");
340
341                         return this.GetCalendar(calendarId).saEraNames;
342                 }
343
344                 internal String[] AbbrevEraNames(int calendarId)
345                 {
346                         Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
347
348                         return this.GetCalendar(calendarId).saAbbrevEraNames;
349                 }
350
351                 internal String[] AbbreviatedEnglishEraNames(int calendarId)
352                 {
353                         Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
354
355                         return this.GetCalendar(calendarId).saAbbrevEnglishEraNames;
356                 }
357
358                 // (user can override default only) short date format
359                 internal String[] ShortDates(int calendarId)
360                 {
361                         return GetCalendar(calendarId).saShortDates;
362                 }
363
364                 // (user can override default only) long date format
365                 internal String[] LongDates(int calendarId)
366                 {
367                         return GetCalendar(calendarId).saLongDates;
368                 }
369
370                 // (user can override) date year/month format.
371                 internal String[] YearMonths(int calendarId)
372                 {
373                         return GetCalendar(calendarId).saYearMonths;
374                 }
375
376                 // day names
377                 internal string[] DayNames(int calendarId)
378                 {
379                         return GetCalendar(calendarId).saDayNames;
380                 }
381
382                 // abbreviated day names
383                 internal string[] AbbreviatedDayNames(int calendarId)
384                 {
385                         // Get abbreviated day names for this calendar from the OS if necessary
386                         return GetCalendar(calendarId).saAbbrevDayNames;
387                 }
388
389                 // The super short day names
390                 internal string[] SuperShortDayNames(int calendarId)
391                 {
392                         return GetCalendar(calendarId).saSuperShortDayNames;
393                 }
394
395                 // month names
396                 internal string[] MonthNames(int calendarId)
397                 {
398                         return GetCalendar(calendarId).saMonthNames;
399                 }
400
401                 // Genitive month names
402                 internal string[] GenitiveMonthNames(int calendarId)
403                 {
404                         return GetCalendar(calendarId).saMonthGenitiveNames;
405                 }
406
407                 // month names
408                 internal string[] AbbreviatedMonthNames(int calendarId)
409                 {
410                         return GetCalendar(calendarId).saAbbrevMonthNames;
411                 }
412
413                 // Genitive month names
414                 internal string[] AbbreviatedGenitiveMonthNames(int calendarId)
415                 {
416                         return GetCalendar(calendarId).saAbbrevMonthGenitiveNames;
417                 }
418
419                 // Leap year month names
420                 // Note: This only applies to Hebrew, and it basically adds a "1" to the 6th month name
421                 // the non-leap names skip the 7th name in the normal month name array
422                 internal string[] LeapYearMonthNames(int calendarId)
423                 {
424                         return GetCalendar(calendarId).saLeapYearMonthNames;
425                 }
426
427                 // month/day format (single string, no override)
428                 internal String MonthDay(int calendarId)
429                 {
430                         return GetCalendar(calendarId).sMonthDay;
431                 }
432
433                 // Date separator (derived from short date format)
434                 internal String DateSeparator(int calendarId)
435                 {
436                         return GetDateSeparator(ShortDates(calendarId)[0]);
437                 }
438
439                 // NOTE: this method is used through reflection by System.Globalization.CultureXmlParser.ReadDateElement()
440                 // and breaking changes here will not show up at build time, only at run time.
441                 static private String GetDateSeparator(String format)
442                 {
443                         // Date format separator (ie: / in 9/1/03)
444                         //
445                         // We calculate this from the provided short date
446                         //
447
448                         //
449                         //  Find the date separator so that we can pretend we know SDATE.
450                         //
451                         return GetSeparator(format, "dyM");
452                 }
453
454                 private static string GetSeparator(string format, string timeParts)
455                 {
456                         int index = IndexOfTimePart(format, 0, timeParts);
457
458                         if (index != -1)
459                         {
460                                 // Found a time part, find out when it changes
461                                 char cTimePart = format[index];
462
463                                 do
464                                 {
465                                         index++;
466                                 } while (index < format.Length && format[index] == cTimePart);
467
468                                 int separatorStart = index;
469
470                                 // Now we need to find the end of the separator
471                                 if (separatorStart < format.Length)
472                                 {
473                                         int separatorEnd = IndexOfTimePart(format, separatorStart, timeParts);
474                                         if (separatorEnd != -1)
475                                         {
476                                                 // From [separatorStart, count) is our string, except we need to unescape
477                                                 return UnescapeNlsString(format, separatorStart, separatorEnd - 1);
478                                         }
479                                 }
480                         }
481
482                         return String.Empty;
483                 }
484
485                 private static int IndexOfTimePart(string format, int startIndex, string timeParts)
486                 {
487                         Contract.Assert(startIndex >= 0, "startIndex cannot be negative");
488                         Contract.Assert(timeParts.IndexOfAny(new char[] { '\'', '\\' }) == -1, "timeParts cannot include quote characters");
489                         bool inQuote = false;
490                         for (int i = startIndex; i < format.Length; ++i)
491                         {
492                                 // See if we have a time Part
493                                 if (!inQuote && timeParts.IndexOf(format[i]) != -1)
494                                 {
495                                         return i;
496                                 }
497                                 switch (format[i])
498                                 {
499                                         case '\\':
500                                                 if (i + 1 < format.Length)
501                                                 {
502                                                         ++i;
503                                                         switch (format[i])
504                                                         {
505                                                                 case '\'':
506                                                                 case '\\':
507                                                                         break;
508                                                                 default:
509                                                                         --i; //backup since we will move over this next
510                                                                         break;
511                                                         }
512                                                 }
513                                                 break;
514                                         case '\'':
515                                                 inQuote = !inQuote;
516                                                 break;
517                                 }
518                         }
519
520                         return -1;
521                 }
522
523                 ////////////////////////////////////////////////////////////////////////////
524                 //
525                 // Unescape a NLS style quote string
526                 //
527                 // This removes single quotes:
528                 //      'fred' -> fred
529                 //      'fred -> fred
530                 //      fred' -> fred
531                 //      fred's -> freds
532                 //
533                 // This removes the first \ of escaped characters:
534                 //      fred\'s -> fred's
535                 //      a\\b -> a\b
536                 //      a\b -> ab
537                 //
538                 // We don't build the stringbuilder unless we find a ' or a \.  If we find a ' or a \, we
539                 // always build a stringbuilder because we need to remove the ' or \.
540                 //
541                 ////////////////////////////////////////////////////////////////////////////
542                 static private String UnescapeNlsString(String str, int start, int end)
543                 {
544                         Contract.Requires(str != null);
545                         Contract.Requires(start >= 0);
546                         Contract.Requires(end >= 0);
547                         StringBuilder result = null;
548
549                         for (int i = start; i < str.Length && i <= end; i++)
550                         {
551                                 switch (str[i])
552                                 {
553                                         case '\'':
554                                                 if (result == null)
555                                                 {
556                                                         result = new StringBuilder(str, start, i - start, str.Length);
557                                                 }
558                                                 break;
559                                         case '\\':
560                                                 if (result == null)
561                                                 {
562                                                         result = new StringBuilder(str, start, i - start, str.Length);
563                                                 }
564                                                 ++i;
565                                                 if (i < str.Length)
566                                                 {
567                                                         result.Append(str[i]);
568                                                 }
569                                                 break;
570                                         default:
571                                                 if (result != null)
572                                                 {
573                                                         result.Append(str[i]);
574                                                 }
575                                                 break;
576                                 }
577                         }
578
579                         if (result == null)
580                                 return (str.Substring(start, end - start + 1));
581
582                         return (result.ToString());
583                 }
584
585 #endregion
586
587
588                 static internal String[] ReescapeWin32Strings(String[] array)
589                 {
590                         return array;
591                 }
592
593                 static internal String ReescapeWin32String(String str)
594                 {
595                         return str;
596                 }
597
598                 internal static bool IsCustomCultureId(int cultureId)
599                 {
600                         return false;
601                 }
602
603                 internal void GetNFIValues (NumberFormatInfo nfi)
604                 {
605                         if (this.IsInvariantCulture)
606                         {
607                                 // Same as default values
608                         }
609                         else
610                         {
611                                 //
612                                 // We don't have information for the following four.  All cultures use
613                                 // the same value of the number formatting values.
614                                 //
615                                 // PercentDecimalDigits
616                                 // PercentDecimalSeparator
617                                 // PercentGroupSize
618                                 // PercentGroupSeparator
619                                 //
620                                 fill_number_data (nfi, numberIndex);
621                         }
622
623                         //
624                         // We don't have percent values, so use the number values
625                         //
626                         nfi.percentDecimalDigits = nfi.numberDecimalDigits;
627                         nfi.percentDecimalSeparator = nfi.numberDecimalSeparator;
628                         nfi.percentGroupSizes = nfi.numberGroupSizes;
629                         nfi.percentGroupSeparator = nfi.numberGroupSeparator;
630                 }
631
632                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
633                 extern static void fill_number_data (NumberFormatInfo nfi, int numberIndex);
634         }
635 }