Merge pull request #1632 from alexanderkyte/bug24118
[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                 private CultureData (string name)
69                 {
70                         this.sRealName = name;
71                 }
72
73                 static CultureData s_Invariant;
74
75                 public static CultureData Invariant {
76                         get {
77                                 if (s_Invariant == null) {
78                                         var invariant = new CultureData ("");
79
80                                         // Language
81                                         invariant.sISO639Language = "iv";                   // ISO 639 Language Name
82
83                                         // Time
84                                         invariant.sAM1159 = "AM";                   // AM designator
85                                         invariant.sPM2359 = "PM";                   // PM designator
86                                         invariant.sTimeSeparator = ":";
87                                         invariant.saLongTimes = new String[] { "HH:mm:ss" };                             // time format
88                                         invariant.saShortTimes = new String[] { "HH:mm", "hh:mm tt", "H:mm", "h:mm tt" }; // short time format
89
90                                         // Calendar specific data
91                                         invariant.iFirstDayOfWeek = 0;                      // first day of week
92                                         invariant.iFirstWeekOfYear = 0;                      // first week of year
93                                         invariant.waCalendars = new int[] { (int)CalendarId.GREGORIAN };       // all available calendar type(s).  The first one is the default calendar
94
95                                         // Store for specific data about each calendar
96                                 invariant.calendars = new CalendarData[CalendarData.MAX_CALENDARS];
97                                 invariant.calendars[0] = CalendarData.Invariant;
98                                         
99                                         Interlocked.CompareExchange (ref s_Invariant, invariant, null);
100                                 }
101
102                                 return s_Invariant;
103                         }
104                 }
105
106                 public static CultureData GetCultureData (string cultureName, bool useUserOverride)
107                 {
108                         try {
109                                 var ci = new CultureInfo (cultureName, useUserOverride);
110                                 return ci.m_cultureData;
111                         } catch {
112                                 return null;
113                         }
114                 }
115
116                 public static CultureData GetCultureData (string cultureName, bool useUserOverride, int datetimeIndex, int calendarId, string iso2lang)
117                 {
118                         if (string.IsNullOrEmpty (cultureName))
119                                 return Invariant;
120
121                         var cd = new CultureData (cultureName);
122                         cd.fill_culture_data (datetimeIndex);
123                         cd.bUseOverrides = useUserOverride;
124                         cd.calendarId = calendarId;
125                         cd.sISO639Language = iso2lang;
126                         return cd;
127                 }
128
129                 internal static CultureData GetCultureData (int culture, bool bUseUserOverride)
130                 {
131                         // Legacy path which we should never hit
132                         return null;
133                 }
134
135                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
136                 extern void fill_culture_data (int datetimeIndex);
137
138                 public CalendarData GetCalendar (int calendarId)
139                 {
140             // arrays are 0 based, calendarIds are 1 based
141                         int calendarIndex = calendarId - 1;
142
143                         // Have to have calendars
144                         if (calendars == null)
145                         {
146                                 calendars = new CalendarData[CalendarData.MAX_CALENDARS];
147                         }
148
149                         var calendarData = calendars[calendarIndex];
150                         if (calendarData == null) {
151                                 calendarData = new CalendarData (sRealName, calendarId, bUseOverrides);
152                                 calendars [calendarIndex] = calendarData;
153                         }
154
155                         return calendarData;
156                 }
157
158                 internal String[] LongTimes {
159                         get {
160                                 return saLongTimes;
161                         }
162                 }
163
164                 internal String[] ShortTimes {
165                         get {
166                                 return saShortTimes;
167                         }
168                 }
169
170                 internal String SISO639LANGNAME {
171                         get {
172                                 return sISO639Language;
173                         }
174                 }
175
176                 internal int IFIRSTDAYOFWEEK {
177                         get {
178                                 return iFirstDayOfWeek;
179                         }
180                 }
181
182                 internal int IFIRSTWEEKOFYEAR {
183                         get {
184                                 return iFirstWeekOfYear;
185                         }
186                 }
187
188                 internal String SAM1159 {
189                         get {
190                                 return sAM1159;
191                         }
192                 }
193
194                 internal String SPM2359 {
195                         get {
196                                 return sPM2359;
197                         }
198                 }
199
200                 internal String TimeSeparator {
201                         get {
202                                 return sTimeSeparator;
203                         }
204                 }
205
206                 internal int[] CalendarIds {
207                         get {
208                                 if (this.waCalendars == null) {
209                                         // Need this specialization because GetJapaneseCalendarDTFI/GetTaiwanCalendarDTFI depend on
210                                         // optional calendars
211                                         switch (sISO639Language) {
212                                         case "ja":
213                                                 waCalendars = new int[] { calendarId, Calendar.CAL_JAPAN };
214                                                 break;
215                                         case "zh":
216                                                 waCalendars = new int[] { calendarId, Calendar.CAL_TAIWAN };
217                                                 break;
218                                         default:
219                                                 waCalendars = new int [] { calendarId };
220                                                 break;
221                                         }
222                                 }
223
224                                 return waCalendars;
225                         }
226                 }
227
228         internal String CultureName {
229             get {
230                 return sRealName;
231             }
232         }
233
234          internal String SCOMPAREINFO {
235                 get {
236                         return "";
237                 }
238         }
239
240                 internal int ILANGUAGE {
241                         get {
242                                 return 0;
243                         }
244                 }
245
246 #region from reference sources
247
248         // Are overrides enabled?
249         internal bool UseUserOverride
250         {
251             get
252             {
253                 return this.bUseOverrides;
254             }
255         }
256
257                 // Native calendar names.  index of optional calendar - 1, empty if no optional calendar at that number
258                 internal String CalendarName(int calendarId)
259                 {
260                         // Get the calendar
261                         return GetCalendar(calendarId).sNativeName;
262                 }
263
264                 // All of our era names
265                 internal String[] EraNames(int calendarId)
266                 {
267                         Contract.Assert(calendarId > 0, "[CultureData.saEraNames] Expected Calendar.ID > 0");
268
269                         return this.GetCalendar(calendarId).saEraNames;
270                 }
271
272                 internal String[] AbbrevEraNames(int calendarId)
273                 {
274                         Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
275
276                         return this.GetCalendar(calendarId).saAbbrevEraNames;
277                 }
278
279                 internal String[] AbbreviatedEnglishEraNames(int calendarId)
280                 {
281                         Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
282
283                         return this.GetCalendar(calendarId).saAbbrevEnglishEraNames;
284                 }
285
286                 // (user can override default only) short date format
287                 internal String[] ShortDates(int calendarId)
288                 {
289                         return GetCalendar(calendarId).saShortDates;
290                 }
291
292                 // (user can override default only) long date format
293                 internal String[] LongDates(int calendarId)
294                 {
295                         return GetCalendar(calendarId).saLongDates;
296                 }
297
298                 // (user can override) date year/month format.
299                 internal String[] YearMonths(int calendarId)
300                 {
301                         return GetCalendar(calendarId).saYearMonths;
302                 }
303
304                 // day names
305                 internal string[] DayNames(int calendarId)
306                 {
307                         return GetCalendar(calendarId).saDayNames;
308                 }
309
310                 // abbreviated day names
311                 internal string[] AbbreviatedDayNames(int calendarId)
312                 {
313                         // Get abbreviated day names for this calendar from the OS if necessary
314                         return GetCalendar(calendarId).saAbbrevDayNames;
315                 }
316
317                 // The super short day names
318                 internal string[] SuperShortDayNames(int calendarId)
319                 {
320                         return GetCalendar(calendarId).saSuperShortDayNames;
321                 }
322
323                 // month names
324                 internal string[] MonthNames(int calendarId)
325                 {
326                         return GetCalendar(calendarId).saMonthNames;
327                 }
328
329                 // Genitive month names
330                 internal string[] GenitiveMonthNames(int calendarId)
331                 {
332                         return GetCalendar(calendarId).saMonthGenitiveNames;
333                 }
334
335                 // month names
336                 internal string[] AbbreviatedMonthNames(int calendarId)
337                 {
338                         return GetCalendar(calendarId).saAbbrevMonthNames;
339                 }
340
341                 // Genitive month names
342                 internal string[] AbbreviatedGenitiveMonthNames(int calendarId)
343                 {
344                         return GetCalendar(calendarId).saAbbrevMonthGenitiveNames;
345                 }
346
347                 // Leap year month names
348                 // Note: This only applies to Hebrew, and it basically adds a "1" to the 6th month name
349                 // the non-leap names skip the 7th name in the normal month name array
350                 internal string[] LeapYearMonthNames(int calendarId)
351                 {
352                         return GetCalendar(calendarId).saLeapYearMonthNames;
353                 }
354
355                 // month/day format (single string, no override)
356                 internal String MonthDay(int calendarId)
357                 {
358                         return GetCalendar(calendarId).sMonthDay;
359                 }
360
361                 // Date separator (derived from short date format)
362                 internal String DateSeparator(int calendarId)
363                 {
364                         return GetDateSeparator(ShortDates(calendarId)[0]);
365                 }
366
367                 // NOTE: this method is used through reflection by System.Globalization.CultureXmlParser.ReadDateElement()
368                 // and breaking changes here will not show up at build time, only at run time.
369                 static private String GetDateSeparator(String format)
370                 {
371                         // Date format separator (ie: / in 9/1/03)
372                         //
373                         // We calculate this from the provided short date
374                         //
375
376                         //
377                         //  Find the date separator so that we can pretend we know SDATE.
378                         //
379                         return GetSeparator(format, "dyM");
380                 }
381
382                 private static string GetSeparator(string format, string timeParts)
383                 {
384                         int index = IndexOfTimePart(format, 0, timeParts);
385
386                         if (index != -1)
387                         {
388                                 // Found a time part, find out when it changes
389                                 char cTimePart = format[index];
390
391                                 do
392                                 {
393                                         index++;
394                                 } while (index < format.Length && format[index] == cTimePart);
395
396                                 int separatorStart = index;
397
398                                 // Now we need to find the end of the separator
399                                 if (separatorStart < format.Length)
400                                 {
401                                         int separatorEnd = IndexOfTimePart(format, separatorStart, timeParts);
402                                         if (separatorEnd != -1)
403                                         {
404                                                 // From [separatorStart, count) is our string, except we need to unescape
405                                                 return UnescapeNlsString(format, separatorStart, separatorEnd - 1);
406                                         }
407                                 }
408                         }
409
410                         return String.Empty;
411                 }
412
413                 private static int IndexOfTimePart(string format, int startIndex, string timeParts)
414                 {
415                         Contract.Assert(startIndex >= 0, "startIndex cannot be negative");
416                         Contract.Assert(timeParts.IndexOfAny(new char[] { '\'', '\\' }) == -1, "timeParts cannot include quote characters");
417                         bool inQuote = false;
418                         for (int i = startIndex; i < format.Length; ++i)
419                         {
420                                 // See if we have a time Part
421                                 if (!inQuote && timeParts.IndexOf(format[i]) != -1)
422                                 {
423                                         return i;
424                                 }
425                                 switch (format[i])
426                                 {
427                                         case '\\':
428                                                 if (i + 1 < format.Length)
429                                                 {
430                                                         ++i;
431                                                         switch (format[i])
432                                                         {
433                                                                 case '\'':
434                                                                 case '\\':
435                                                                         break;
436                                                                 default:
437                                                                         --i; //backup since we will move over this next
438                                                                         break;
439                                                         }
440                                                 }
441                                                 break;
442                                         case '\'':
443                                                 inQuote = !inQuote;
444                                                 break;
445                                 }
446                         }
447
448                         return -1;
449                 }
450
451                 ////////////////////////////////////////////////////////////////////////////
452                 //
453                 // Unescape a NLS style quote string
454                 //
455                 // This removes single quotes:
456                 //      'fred' -> fred
457                 //      'fred -> fred
458                 //      fred' -> fred
459                 //      fred's -> freds
460                 //
461                 // This removes the first \ of escaped characters:
462                 //      fred\'s -> fred's
463                 //      a\\b -> a\b
464                 //      a\b -> ab
465                 //
466                 // We don't build the stringbuilder unless we find a ' or a \.  If we find a ' or a \, we
467                 // always build a stringbuilder because we need to remove the ' or \.
468                 //
469                 ////////////////////////////////////////////////////////////////////////////
470                 static private String UnescapeNlsString(String str, int start, int end)
471                 {
472                         Contract.Requires(str != null);
473                         Contract.Requires(start >= 0);
474                         Contract.Requires(end >= 0);
475                         StringBuilder result = null;
476
477                         for (int i = start; i < str.Length && i <= end; i++)
478                         {
479                                 switch (str[i])
480                                 {
481                                         case '\'':
482                                                 if (result == null)
483                                                 {
484                                                         result = new StringBuilder(str, start, i - start, str.Length);
485                                                 }
486                                                 break;
487                                         case '\\':
488                                                 if (result == null)
489                                                 {
490                                                         result = new StringBuilder(str, start, i - start, str.Length);
491                                                 }
492                                                 ++i;
493                                                 if (i < str.Length)
494                                                 {
495                                                         result.Append(str[i]);
496                                                 }
497                                                 break;
498                                         default:
499                                                 if (result != null)
500                                                 {
501                                                         result.Append(str[i]);
502                                                 }
503                                                 break;
504                                 }
505                         }
506
507                         if (result == null)
508                                 return (str.Substring(start, end - start + 1));
509
510                         return (result.ToString());
511                 }
512
513 #endregion
514
515
516                 static internal String[] ReescapeWin32Strings(String[] array)
517                 {
518                         return array;
519                 }
520
521                 static internal String ReescapeWin32String(String str)
522                 {
523                         return str;
524                 }
525         }
526 }