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 [MethodImplAttribute (MethodImplOptions.InternalCall)]
130 extern void fill_culture_data (int datetimeIndex);
132 public CalendarData GetCalendar (int calendarId)
134 // arrays are 0 based, calendarIds are 1 based
135 int calendarIndex = calendarId - 1;
137 // Have to have calendars
138 if (calendars == null)
140 calendars = new CalendarData[CalendarData.MAX_CALENDARS];
143 var calendarData = calendars[calendarIndex];
144 if (calendarData == null) {
145 calendarData = new CalendarData (sRealName, calendarId, bUseOverrides);
146 calendars [calendarIndex] = calendarData;
152 internal String[] LongTimes {
158 internal String[] ShortTimes {
164 internal String SISO639LANGNAME {
166 return sISO639Language;
170 internal int IFIRSTDAYOFWEEK {
172 return iFirstDayOfWeek;
176 internal int IFIRSTWEEKOFYEAR {
178 return iFirstWeekOfYear;
182 internal String SAM1159 {
188 internal String SPM2359 {
194 internal String TimeSeparator {
196 return sTimeSeparator;
200 internal int[] CalendarIds {
202 if (this.waCalendars == null) {
203 // Need this specialization because GetJapaneseCalendarDTFI/GetTaiwanCalendarDTFI depend on
204 // optional calendars
205 switch (sISO639Language) {
207 waCalendars = new int[] { calendarId, Calendar.CAL_JAPAN };
210 waCalendars = new int[] { calendarId, Calendar.CAL_TAIWAN };
213 waCalendars = new int [] { calendarId };
222 internal String CultureName {
228 internal String SCOMPAREINFO {
234 #region from reference sources
236 // Are overrides enabled?
237 internal bool UseUserOverride
241 return this.bUseOverrides;
245 // Native calendar names. index of optional calendar - 1, empty if no optional calendar at that number
246 internal String CalendarName(int calendarId)
249 return GetCalendar(calendarId).sNativeName;
252 // All of our era names
253 internal String[] EraNames(int calendarId)
255 Contract.Assert(calendarId > 0, "[CultureData.saEraNames] Expected Calendar.ID > 0");
257 return this.GetCalendar(calendarId).saEraNames;
260 internal String[] AbbrevEraNames(int calendarId)
262 Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
264 return this.GetCalendar(calendarId).saAbbrevEraNames;
267 internal String[] AbbreviatedEnglishEraNames(int calendarId)
269 Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
271 return this.GetCalendar(calendarId).saAbbrevEnglishEraNames;
274 // (user can override default only) short date format
275 internal String[] ShortDates(int calendarId)
277 return GetCalendar(calendarId).saShortDates;
280 // (user can override default only) long date format
281 internal String[] LongDates(int calendarId)
283 return GetCalendar(calendarId).saLongDates;
286 // (user can override) date year/month format.
287 internal String[] YearMonths(int calendarId)
289 return GetCalendar(calendarId).saYearMonths;
293 internal string[] DayNames(int calendarId)
295 return GetCalendar(calendarId).saDayNames;
298 // abbreviated day names
299 internal string[] AbbreviatedDayNames(int calendarId)
301 // Get abbreviated day names for this calendar from the OS if necessary
302 return GetCalendar(calendarId).saAbbrevDayNames;
305 // The super short day names
306 internal string[] SuperShortDayNames(int calendarId)
308 return GetCalendar(calendarId).saSuperShortDayNames;
312 internal string[] MonthNames(int calendarId)
314 return GetCalendar(calendarId).saMonthNames;
317 // Genitive month names
318 internal string[] GenitiveMonthNames(int calendarId)
320 return GetCalendar(calendarId).saMonthGenitiveNames;
324 internal string[] AbbreviatedMonthNames(int calendarId)
326 return GetCalendar(calendarId).saAbbrevMonthNames;
329 // Genitive month names
330 internal string[] AbbreviatedGenitiveMonthNames(int calendarId)
332 return GetCalendar(calendarId).saAbbrevMonthGenitiveNames;
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)
340 return GetCalendar(calendarId).saLeapYearMonthNames;
343 // month/day format (single string, no override)
344 internal String MonthDay(int calendarId)
346 return GetCalendar(calendarId).sMonthDay;
349 // Date separator (derived from short date format)
350 internal String DateSeparator(int calendarId)
352 return GetDateSeparator(ShortDates(calendarId)[0]);
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)
359 // Date format separator (ie: / in 9/1/03)
361 // We calculate this from the provided short date
365 // Find the date separator so that we can pretend we know SDATE.
367 return GetSeparator(format, "dyM");
370 private static string GetSeparator(string format, string timeParts)
372 int index = IndexOfTimePart(format, 0, timeParts);
376 // Found a time part, find out when it changes
377 char cTimePart = format[index];
382 } while (index < format.Length && format[index] == cTimePart);
384 int separatorStart = index;
386 // Now we need to find the end of the separator
387 if (separatorStart < format.Length)
389 int separatorEnd = IndexOfTimePart(format, separatorStart, timeParts);
390 if (separatorEnd != -1)
392 // From [separatorStart, count) is our string, except we need to unescape
393 return UnescapeNlsString(format, separatorStart, separatorEnd - 1);
401 private static int IndexOfTimePart(string format, int startIndex, string timeParts)
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)
408 // See if we have a time Part
409 if (!inQuote && timeParts.IndexOf(format[i]) != -1)
416 if (i + 1 < format.Length)
425 --i; //backup since we will move over this next
439 ////////////////////////////////////////////////////////////////////////////
441 // Unescape a NLS style quote string
443 // This removes single quotes:
449 // This removes the first \ of escaped characters:
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 \.
457 ////////////////////////////////////////////////////////////////////////////
458 static private String UnescapeNlsString(String str, int start, int end)
460 Contract.Requires(str != null);
461 Contract.Requires(start >= 0);
462 Contract.Requires(end >= 0);
463 StringBuilder result = null;
465 for (int i = start; i < str.Length && i <= end; i++)
472 result = new StringBuilder(str, start, i - start, str.Length);
478 result = new StringBuilder(str, start, i - start, str.Length);
483 result.Append(str[i]);
489 result.Append(str[i]);
496 return (str.Substring(start, end - start + 1));
498 return (result.ToString());
504 static internal String[] ReescapeWin32Strings(String[] array)
509 static internal String ReescapeWin32String(String str)