5 // Marek Safar <marek.safar@gmail.com>
7 // Copyright (C) 2015 Xamarin Inc (http://www.xamarin.com)
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
30 using System.Diagnostics.Contracts;
32 using System.Threading;
33 using System.Runtime.InteropServices;
34 using System.Runtime.CompilerServices;
36 namespace System.Globalization
38 [StructLayout (LayoutKind.Sequential)]
41 #region Sync with object-internals.h
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
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)
53 private volatile int[] waCalendars; // all available calendar type(s). The first one is the default calendar
55 // Store for specific data about each calendar
56 private CalendarData[] calendars; // Store for specific calendar data
59 private String sISO639Language; // ISO 639 Language Name
61 readonly string sRealName;
65 // TODO: should query runtime with culture name for a list of culture's calendars
70 int iDefaultAnsiCodePage;
71 int iDefaultOemCodePage;
72 int iDefaultMacCodePage;
73 int iDefaultEbcdicCodePage;
75 string sListSeparator;
77 private CultureData (string name)
79 this.sRealName = name;
82 static CultureData s_Invariant;
84 public static CultureData Invariant {
86 if (s_Invariant == null) {
87 var invariant = new CultureData ("");
90 invariant.sISO639Language = "iv"; // ISO 639 Language Name
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
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
104 // Store for specific data about each calendar
105 invariant.calendars = new CalendarData[CalendarData.MAX_CALENDARS];
106 invariant.calendars[0] = CalendarData.Invariant;
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
113 invariant.sListSeparator = ",";
115 Interlocked.CompareExchange (ref s_Invariant, invariant, null);
122 public static CultureData GetCultureData (string cultureName, bool useUserOverride)
125 var ci = new CultureInfo (cultureName, useUserOverride);
126 return ci.m_cultureData;
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)
135 if (string.IsNullOrEmpty (cultureName))
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;
153 internal static CultureData GetCultureData (int culture, bool bUseUserOverride)
155 // Legacy path which we should never hit
159 [MethodImplAttribute (MethodImplOptions.InternalCall)]
160 extern void fill_culture_data (int datetimeIndex);
162 public CalendarData GetCalendar (int calendarId)
164 // arrays are 0 based, calendarIds are 1 based
165 int calendarIndex = calendarId - 1;
167 // Have to have calendars
168 if (calendars == null)
170 calendars = new CalendarData[CalendarData.MAX_CALENDARS];
173 var calendarData = calendars[calendarIndex];
174 if (calendarData == null) {
175 calendarData = new CalendarData (sRealName, calendarId, bUseOverrides);
176 calendars [calendarIndex] = calendarData;
182 internal String[] LongTimes {
188 internal String[] ShortTimes {
194 internal String SISO639LANGNAME {
196 return sISO639Language;
200 internal int IFIRSTDAYOFWEEK {
202 return iFirstDayOfWeek;
206 internal int IFIRSTWEEKOFYEAR {
208 return iFirstWeekOfYear;
212 internal String SAM1159 {
218 internal String SPM2359 {
224 internal String TimeSeparator {
226 return sTimeSeparator;
230 internal int[] CalendarIds {
232 if (this.waCalendars == null) {
233 // Need this specialization because GetJapaneseCalendarDTFI/GetTaiwanCalendarDTFI depend on
234 // optional calendars
235 switch (sISO639Language) {
237 waCalendars = new int[] { calendarId, Calendar.CAL_JAPAN };
240 waCalendars = new int[] { calendarId, Calendar.CAL_TAIWAN };
243 waCalendars = new int [] { calendarId };
252 internal bool IsInvariantCulture {
254 return string.IsNullOrEmpty (sRealName);
258 internal String CultureName {
264 internal String SCOMPAREINFO {
270 internal String STEXTINFO {
276 internal int ILANGUAGE {
282 internal int IDEFAULTANSICODEPAGE {
284 return iDefaultAnsiCodePage;
288 internal int IDEFAULTOEMCODEPAGE {
290 return iDefaultOemCodePage;
294 internal int IDEFAULTMACCODEPAGE {
296 return iDefaultMacCodePage;
300 internal int IDEFAULTEBCDICCODEPAGE {
302 return iDefaultEbcdicCodePage;
306 internal bool IsRightToLeft {
308 return isRightToLeft;
312 internal String SLIST {
314 return sListSeparator;
318 #region from reference sources
320 // Are overrides enabled?
321 internal bool UseUserOverride
325 return this.bUseOverrides;
329 // Native calendar names. index of optional calendar - 1, empty if no optional calendar at that number
330 internal String CalendarName(int calendarId)
333 return GetCalendar(calendarId).sNativeName;
336 // All of our era names
337 internal String[] EraNames(int calendarId)
339 Contract.Assert(calendarId > 0, "[CultureData.saEraNames] Expected Calendar.ID > 0");
341 return this.GetCalendar(calendarId).saEraNames;
344 internal String[] AbbrevEraNames(int calendarId)
346 Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
348 return this.GetCalendar(calendarId).saAbbrevEraNames;
351 internal String[] AbbreviatedEnglishEraNames(int calendarId)
353 Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
355 return this.GetCalendar(calendarId).saAbbrevEnglishEraNames;
358 // (user can override default only) short date format
359 internal String[] ShortDates(int calendarId)
361 return GetCalendar(calendarId).saShortDates;
364 // (user can override default only) long date format
365 internal String[] LongDates(int calendarId)
367 return GetCalendar(calendarId).saLongDates;
370 // (user can override) date year/month format.
371 internal String[] YearMonths(int calendarId)
373 return GetCalendar(calendarId).saYearMonths;
377 internal string[] DayNames(int calendarId)
379 return GetCalendar(calendarId).saDayNames;
382 // abbreviated day names
383 internal string[] AbbreviatedDayNames(int calendarId)
385 // Get abbreviated day names for this calendar from the OS if necessary
386 return GetCalendar(calendarId).saAbbrevDayNames;
389 // The super short day names
390 internal string[] SuperShortDayNames(int calendarId)
392 return GetCalendar(calendarId).saSuperShortDayNames;
396 internal string[] MonthNames(int calendarId)
398 return GetCalendar(calendarId).saMonthNames;
401 // Genitive month names
402 internal string[] GenitiveMonthNames(int calendarId)
404 return GetCalendar(calendarId).saMonthGenitiveNames;
408 internal string[] AbbreviatedMonthNames(int calendarId)
410 return GetCalendar(calendarId).saAbbrevMonthNames;
413 // Genitive month names
414 internal string[] AbbreviatedGenitiveMonthNames(int calendarId)
416 return GetCalendar(calendarId).saAbbrevMonthGenitiveNames;
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)
424 return GetCalendar(calendarId).saLeapYearMonthNames;
427 // month/day format (single string, no override)
428 internal String MonthDay(int calendarId)
430 return GetCalendar(calendarId).sMonthDay;
433 // Date separator (derived from short date format)
434 internal String DateSeparator(int calendarId)
436 return GetDateSeparator(ShortDates(calendarId)[0]);
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)
443 // Date format separator (ie: / in 9/1/03)
445 // We calculate this from the provided short date
449 // Find the date separator so that we can pretend we know SDATE.
451 return GetSeparator(format, "dyM");
454 private static string GetSeparator(string format, string timeParts)
456 int index = IndexOfTimePart(format, 0, timeParts);
460 // Found a time part, find out when it changes
461 char cTimePart = format[index];
466 } while (index < format.Length && format[index] == cTimePart);
468 int separatorStart = index;
470 // Now we need to find the end of the separator
471 if (separatorStart < format.Length)
473 int separatorEnd = IndexOfTimePart(format, separatorStart, timeParts);
474 if (separatorEnd != -1)
476 // From [separatorStart, count) is our string, except we need to unescape
477 return UnescapeNlsString(format, separatorStart, separatorEnd - 1);
485 private static int IndexOfTimePart(string format, int startIndex, string timeParts)
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)
492 // See if we have a time Part
493 if (!inQuote && timeParts.IndexOf(format[i]) != -1)
500 if (i + 1 < format.Length)
509 --i; //backup since we will move over this next
523 ////////////////////////////////////////////////////////////////////////////
525 // Unescape a NLS style quote string
527 // This removes single quotes:
533 // This removes the first \ of escaped characters:
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 \.
541 ////////////////////////////////////////////////////////////////////////////
542 static private String UnescapeNlsString(String str, int start, int end)
544 Contract.Requires(str != null);
545 Contract.Requires(start >= 0);
546 Contract.Requires(end >= 0);
547 StringBuilder result = null;
549 for (int i = start; i < str.Length && i <= end; i++)
556 result = new StringBuilder(str, start, i - start, str.Length);
562 result = new StringBuilder(str, start, i - start, str.Length);
567 result.Append(str[i]);
573 result.Append(str[i]);
580 return (str.Substring(start, end - start + 1));
582 return (result.ToString());
588 static internal String[] ReescapeWin32Strings(String[] array)
593 static internal String ReescapeWin32String(String str)
598 internal static bool IsCustomCultureId(int cultureId)
603 internal void GetNFIValues (NumberFormatInfo nfi)
605 if (this.IsInvariantCulture)
607 // Same as default values
612 // We don't have information for the following four. All cultures use
613 // the same value of the number formatting values.
615 // PercentDecimalDigits
616 // PercentDecimalSeparator
618 // PercentGroupSeparator
620 fill_number_data (nfi, numberIndex);
624 // We don't have percent values, so use the number values
626 nfi.percentDecimalDigits = nfi.numberDecimalDigits;
627 nfi.percentDecimalSeparator = nfi.numberDecimalSeparator;
628 nfi.percentGroupSizes = nfi.numberGroupSizes;
629 nfi.percentGroupSeparator = nfi.numberGroupSeparator;
632 [MethodImplAttribute (MethodImplOptions.InternalCall)]
633 extern static void fill_number_data (NumberFormatInfo nfi, int numberIndex);