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
68 private CultureData (string name)
70 this.sRealName = name;
73 static CultureData s_Invariant;
75 public static CultureData Invariant {
77 if (s_Invariant == null) {
78 var invariant = new CultureData ("");
81 invariant.sISO639Language = "iv"; // ISO 639 Language Name
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
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
95 // Store for specific data about each calendar
96 invariant.calendars = new CalendarData[CalendarData.MAX_CALENDARS];
97 invariant.calendars[0] = CalendarData.Invariant;
99 Interlocked.CompareExchange (ref s_Invariant, invariant, null);
106 public static CultureData GetCultureData (string cultureName, bool useUserOverride)
109 var ci = new CultureInfo (cultureName, useUserOverride);
110 return ci.m_cultureData;
116 public static CultureData GetCultureData (string cultureName, bool useUserOverride, int datetimeIndex, int calendarId, string iso2lang)
118 if (string.IsNullOrEmpty (cultureName))
121 var cd = new CultureData (cultureName);
122 cd.fill_culture_data (datetimeIndex);
123 cd.bUseOverrides = useUserOverride;
124 cd.calendarId = calendarId;
125 cd.sISO639Language = iso2lang;
129 internal static CultureData GetCultureData (int culture, bool bUseUserOverride)
131 // Legacy path which we should never hit
135 [MethodImplAttribute (MethodImplOptions.InternalCall)]
136 extern void fill_culture_data (int datetimeIndex);
138 public CalendarData GetCalendar (int calendarId)
140 // arrays are 0 based, calendarIds are 1 based
141 int calendarIndex = calendarId - 1;
143 // Have to have calendars
144 if (calendars == null)
146 calendars = new CalendarData[CalendarData.MAX_CALENDARS];
149 var calendarData = calendars[calendarIndex];
150 if (calendarData == null) {
151 calendarData = new CalendarData (sRealName, calendarId, bUseOverrides);
152 calendars [calendarIndex] = calendarData;
158 internal String[] LongTimes {
164 internal String[] ShortTimes {
170 internal String SISO639LANGNAME {
172 return sISO639Language;
176 internal int IFIRSTDAYOFWEEK {
178 return iFirstDayOfWeek;
182 internal int IFIRSTWEEKOFYEAR {
184 return iFirstWeekOfYear;
188 internal String SAM1159 {
194 internal String SPM2359 {
200 internal String TimeSeparator {
202 return sTimeSeparator;
206 internal int[] CalendarIds {
208 if (this.waCalendars == null) {
209 // Need this specialization because GetJapaneseCalendarDTFI/GetTaiwanCalendarDTFI depend on
210 // optional calendars
211 switch (sISO639Language) {
213 waCalendars = new int[] { calendarId, Calendar.CAL_JAPAN };
216 waCalendars = new int[] { calendarId, Calendar.CAL_TAIWAN };
219 waCalendars = new int [] { calendarId };
228 internal String CultureName {
234 internal String SCOMPAREINFO {
240 internal int ILANGUAGE {
246 #region from reference sources
248 // Are overrides enabled?
249 internal bool UseUserOverride
253 return this.bUseOverrides;
257 // Native calendar names. index of optional calendar - 1, empty if no optional calendar at that number
258 internal String CalendarName(int calendarId)
261 return GetCalendar(calendarId).sNativeName;
264 // All of our era names
265 internal String[] EraNames(int calendarId)
267 Contract.Assert(calendarId > 0, "[CultureData.saEraNames] Expected Calendar.ID > 0");
269 return this.GetCalendar(calendarId).saEraNames;
272 internal String[] AbbrevEraNames(int calendarId)
274 Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
276 return this.GetCalendar(calendarId).saAbbrevEraNames;
279 internal String[] AbbreviatedEnglishEraNames(int calendarId)
281 Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
283 return this.GetCalendar(calendarId).saAbbrevEnglishEraNames;
286 // (user can override default only) short date format
287 internal String[] ShortDates(int calendarId)
289 return GetCalendar(calendarId).saShortDates;
292 // (user can override default only) long date format
293 internal String[] LongDates(int calendarId)
295 return GetCalendar(calendarId).saLongDates;
298 // (user can override) date year/month format.
299 internal String[] YearMonths(int calendarId)
301 return GetCalendar(calendarId).saYearMonths;
305 internal string[] DayNames(int calendarId)
307 return GetCalendar(calendarId).saDayNames;
310 // abbreviated day names
311 internal string[] AbbreviatedDayNames(int calendarId)
313 // Get abbreviated day names for this calendar from the OS if necessary
314 return GetCalendar(calendarId).saAbbrevDayNames;
317 // The super short day names
318 internal string[] SuperShortDayNames(int calendarId)
320 return GetCalendar(calendarId).saSuperShortDayNames;
324 internal string[] MonthNames(int calendarId)
326 return GetCalendar(calendarId).saMonthNames;
329 // Genitive month names
330 internal string[] GenitiveMonthNames(int calendarId)
332 return GetCalendar(calendarId).saMonthGenitiveNames;
336 internal string[] AbbreviatedMonthNames(int calendarId)
338 return GetCalendar(calendarId).saAbbrevMonthNames;
341 // Genitive month names
342 internal string[] AbbreviatedGenitiveMonthNames(int calendarId)
344 return GetCalendar(calendarId).saAbbrevMonthGenitiveNames;
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)
352 return GetCalendar(calendarId).saLeapYearMonthNames;
355 // month/day format (single string, no override)
356 internal String MonthDay(int calendarId)
358 return GetCalendar(calendarId).sMonthDay;
361 // Date separator (derived from short date format)
362 internal String DateSeparator(int calendarId)
364 return GetDateSeparator(ShortDates(calendarId)[0]);
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)
371 // Date format separator (ie: / in 9/1/03)
373 // We calculate this from the provided short date
377 // Find the date separator so that we can pretend we know SDATE.
379 return GetSeparator(format, "dyM");
382 private static string GetSeparator(string format, string timeParts)
384 int index = IndexOfTimePart(format, 0, timeParts);
388 // Found a time part, find out when it changes
389 char cTimePart = format[index];
394 } while (index < format.Length && format[index] == cTimePart);
396 int separatorStart = index;
398 // Now we need to find the end of the separator
399 if (separatorStart < format.Length)
401 int separatorEnd = IndexOfTimePart(format, separatorStart, timeParts);
402 if (separatorEnd != -1)
404 // From [separatorStart, count) is our string, except we need to unescape
405 return UnescapeNlsString(format, separatorStart, separatorEnd - 1);
413 private static int IndexOfTimePart(string format, int startIndex, string timeParts)
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)
420 // See if we have a time Part
421 if (!inQuote && timeParts.IndexOf(format[i]) != -1)
428 if (i + 1 < format.Length)
437 --i; //backup since we will move over this next
451 ////////////////////////////////////////////////////////////////////////////
453 // Unescape a NLS style quote string
455 // This removes single quotes:
461 // This removes the first \ of escaped characters:
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 \.
469 ////////////////////////////////////////////////////////////////////////////
470 static private String UnescapeNlsString(String str, int start, int end)
472 Contract.Requires(str != null);
473 Contract.Requires(start >= 0);
474 Contract.Requires(end >= 0);
475 StringBuilder result = null;
477 for (int i = start; i < str.Length && i <= end; i++)
484 result = new StringBuilder(str, start, i - start, str.Length);
490 result = new StringBuilder(str, start, i - start, str.Length);
495 result.Append(str[i]);
501 result.Append(str[i]);
508 return (str.Substring(start, end - start + 1));
510 return (result.ToString());
516 static internal String[] ReescapeWin32Strings(String[] array)
521 static internal String ReescapeWin32String(String str)