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