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 		int numberIndex;
69 
70 		int iDefaultAnsiCodePage;
71 		int iDefaultOemCodePage;
72 		int iDefaultMacCodePage;
73 		int iDefaultEbcdicCodePage;
74 		bool isRightToLeft;
75 		string sListSeparator;
76 
CultureData(string name)77 		private CultureData (string name)
78 		{
79 			this.sRealName = name;
80 		}
81 
82 		static CultureData s_Invariant;
83 
84 		public static CultureData Invariant {
85 			get {
86 				if (s_Invariant == null) {
87 					var invariant = new CultureData ("");
88 
89 					// Language
90 					invariant.sISO639Language = "iv";                   // ISO 639 Language Name
91 
92 					// Time
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
98 
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
103 
104 					// Store for specific data about each calendar
105 		    		invariant.calendars = new CalendarData[CalendarData.MAX_CALENDARS];
106 		    		invariant.calendars[0] = CalendarData.Invariant;
107 
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
112 
113 					invariant.sListSeparator = ",";
114 
115 					Interlocked.CompareExchange (ref s_Invariant, invariant, null);
116 				}
117 
118 				return s_Invariant;
119 			}
120 		}
121 
GetCultureData(string cultureName, bool useUserOverride)122 		public static CultureData GetCultureData (string cultureName, bool useUserOverride)
123 		{
124 			try {
125 				var ci = new CultureInfo (cultureName, useUserOverride);
126 				return ci.m_cultureData;
127 			} catch {
128 				return null;
129 			}
130 		}
131 
GetCultureData(string cultureName, bool useUserOverride, int datetimeIndex, int calendarId, int numberIndex, string iso2lang, int ansiCodePage, int oemCodePage, int macCodePage, int ebcdicCodePage, bool rightToLeft, string listSeparator)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)
134 		{
135 			if (string.IsNullOrEmpty (cultureName))
136 				return Invariant;
137 
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;
150 			return cd;
151 		}
152 
GetCultureData(int culture, bool bUseUserOverride)153 		internal static CultureData GetCultureData (int culture, bool bUseUserOverride)
154 		{
155 			// Legacy path which we should never hit
156 			return null;
157 		}
158 
159 		[MethodImplAttribute (MethodImplOptions.InternalCall)]
fill_culture_data(int datetimeIndex)160 		extern void fill_culture_data (int datetimeIndex);
161 
GetCalendar(int calendarId)162 		public CalendarData GetCalendar (int calendarId)
163 		{
164 			// arrays are 0 based, calendarIds are 1 based
165 			int calendarIndex = calendarId - 1;
166 
167 			// Have to have calendars
168 			if (calendars == null)
169 			{
170 				calendars = new CalendarData[CalendarData.MAX_CALENDARS];
171 			}
172 
173 			var calendarData = calendars[calendarIndex];
174 			if (calendarData == null) {
175 				calendarData = new CalendarData (sRealName, calendarId, bUseOverrides);
176 				calendars [calendarIndex] = calendarData;
177 			}
178 
179 			return calendarData;
180 		}
181 
182 		internal String[] LongTimes {
183 			get {
184 				return saLongTimes;
185 			}
186 		}
187 
188 		internal String[] ShortTimes {
189 			get {
190 				return saShortTimes;
191 			}
192 		}
193 
194 		internal String SISO639LANGNAME {
195 			get {
196 				return sISO639Language;
197 			}
198 		}
199 
200 		internal int IFIRSTDAYOFWEEK {
201 			get {
202 				return iFirstDayOfWeek;
203 			}
204 		}
205 
206 		internal int IFIRSTWEEKOFYEAR {
207 			get {
208 				return iFirstWeekOfYear;
209 			}
210 		}
211 
212 		internal String SAM1159 {
213 			get {
214 				return sAM1159;
215 			}
216 		}
217 
218 		internal String SPM2359 {
219 			get {
220 				return sPM2359;
221 			}
222 		}
223 
224 		internal String TimeSeparator {
225 			get {
226 				return sTimeSeparator;
227 			}
228 		}
229 
230 		internal int[] CalendarIds {
231 			get {
232 				if (this.waCalendars == null) {
233 					// Need this specialization because GetJapaneseCalendarDTFI/GetTaiwanCalendarDTFI depend on
234 					// optional calendars
235 					switch (sISO639Language) {
236 					case "ja":
237 						waCalendars = new int[] { calendarId, Calendar.CAL_JAPAN };
238 						break;
239 					case "zh":
240 						waCalendars = new int[] { calendarId, Calendar.CAL_TAIWAN };
241 						break;
242 					default:
243 						waCalendars = new int [] { calendarId };
244 						break;
245 					}
246 				}
247 
248 				return waCalendars;
249 			}
250 		}
251 
252 		internal bool IsInvariantCulture {
253 			get {
254 				return string.IsNullOrEmpty (sRealName);
255 			}
256 		}
257 
258 		internal String CultureName {
259 			get {
260 				return sRealName;
261 			}
262 		}
263 
264 		internal String SCOMPAREINFO {
265 			get {
266 				return "";
267 			}
268 		}
269 
270 		internal String STEXTINFO {
271 			get {
272 				return sRealName;
273 			}
274 		}
275 
276 		internal int ILANGUAGE {
277 			get {
278 				return 0;
279 			}
280 		}
281 
282 		internal int IDEFAULTANSICODEPAGE {
283 			get {
284 				return iDefaultAnsiCodePage;
285 			}
286 		}
287 
288 		internal int IDEFAULTOEMCODEPAGE {
289 			get {
290 				return iDefaultOemCodePage;
291 			}
292 		}
293 
294 		internal int IDEFAULTMACCODEPAGE {
295 			get {
296 				return iDefaultMacCodePage;
297 			}
298 		}
299 
300 		internal int IDEFAULTEBCDICCODEPAGE {
301 			get {
302 				return iDefaultEbcdicCodePage;
303 			}
304 		}
305 
306 		internal bool IsRightToLeft {
307 			get {
308 				return isRightToLeft;
309 			}
310 		}
311 
312 		internal String SLIST {
313 			get {
314 				return sListSeparator;
315 			}
316 		}
317 
318 #region from reference sources
319 
320         // Are overrides enabled?
321         internal bool UseUserOverride
322         {
323             get
324             {
325                 return this.bUseOverrides;
326             }
327         }
328 
329 		// Native calendar names.  index of optional calendar - 1, empty if no optional calendar at that number
CalendarName(int calendarId)330 		internal String CalendarName(int calendarId)
331 		{
332 			// Get the calendar
333 			return GetCalendar(calendarId).sNativeName;
334 		}
335 
336 		// All of our era names
EraNames(int calendarId)337 		internal String[] EraNames(int calendarId)
338 		{
339 			Contract.Assert(calendarId > 0, "[CultureData.saEraNames] Expected Calendar.ID > 0");
340 
341 			return this.GetCalendar(calendarId).saEraNames;
342 		}
343 
AbbrevEraNames(int calendarId)344 		internal String[] AbbrevEraNames(int calendarId)
345 		{
346 			Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
347 
348 			return this.GetCalendar(calendarId).saAbbrevEraNames;
349 		}
350 
AbbreviatedEnglishEraNames(int calendarId)351 		internal String[] AbbreviatedEnglishEraNames(int calendarId)
352 		{
353 			Contract.Assert(calendarId > 0, "[CultureData.saAbbrevEraNames] Expected Calendar.ID > 0");
354 
355 			return this.GetCalendar(calendarId).saAbbrevEnglishEraNames;
356 		}
357 
358 		// (user can override default only) short date format
ShortDates(int calendarId)359 		internal String[] ShortDates(int calendarId)
360 		{
361 			return GetCalendar(calendarId).saShortDates;
362 		}
363 
364 		// (user can override default only) long date format
LongDates(int calendarId)365 		internal String[] LongDates(int calendarId)
366 		{
367 			return GetCalendar(calendarId).saLongDates;
368 		}
369 
370 		// (user can override) date year/month format.
YearMonths(int calendarId)371 		internal String[] YearMonths(int calendarId)
372 		{
373 			return GetCalendar(calendarId).saYearMonths;
374 		}
375 
376 		// day names
DayNames(int calendarId)377 		internal string[] DayNames(int calendarId)
378 		{
379 			return GetCalendar(calendarId).saDayNames;
380 		}
381 
382 		// abbreviated day names
AbbreviatedDayNames(int calendarId)383 		internal string[] AbbreviatedDayNames(int calendarId)
384 		{
385 			// Get abbreviated day names for this calendar from the OS if necessary
386 			return GetCalendar(calendarId).saAbbrevDayNames;
387 		}
388 
389 		// The super short day names
SuperShortDayNames(int calendarId)390 		internal string[] SuperShortDayNames(int calendarId)
391 		{
392 			return GetCalendar(calendarId).saSuperShortDayNames;
393 		}
394 
395 		// month names
MonthNames(int calendarId)396 		internal string[] MonthNames(int calendarId)
397 		{
398 			return GetCalendar(calendarId).saMonthNames;
399 		}
400 
401 		// Genitive month names
GenitiveMonthNames(int calendarId)402 		internal string[] GenitiveMonthNames(int calendarId)
403 		{
404 			return GetCalendar(calendarId).saMonthGenitiveNames;
405 		}
406 
407 		// month names
AbbreviatedMonthNames(int calendarId)408 		internal string[] AbbreviatedMonthNames(int calendarId)
409 		{
410 			return GetCalendar(calendarId).saAbbrevMonthNames;
411 		}
412 
413 		// Genitive month names
AbbreviatedGenitiveMonthNames(int calendarId)414 		internal string[] AbbreviatedGenitiveMonthNames(int calendarId)
415 		{
416 			return GetCalendar(calendarId).saAbbrevMonthGenitiveNames;
417 		}
418 
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
LeapYearMonthNames(int calendarId)422 		internal string[] LeapYearMonthNames(int calendarId)
423 		{
424 			return GetCalendar(calendarId).saLeapYearMonthNames;
425 		}
426 
427 		// month/day format (single string, no override)
MonthDay(int calendarId)428 		internal String MonthDay(int calendarId)
429 		{
430 			return GetCalendar(calendarId).sMonthDay;
431 		}
432 
433 		// Date separator (derived from short date format)
DateSeparator(int calendarId)434 		internal String DateSeparator(int calendarId)
435 		{
436 			return GetDateSeparator(ShortDates(calendarId)[0]);
437 		}
438 
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.
GetDateSeparator(String format)441 		static private String GetDateSeparator(String format)
442 		{
443 			// Date format separator (ie: / in 9/1/03)
444 			//
445 			// We calculate this from the provided short date
446 			//
447 
448 			//
449 			//  Find the date separator so that we can pretend we know SDATE.
450 			//
451 			return GetSeparator(format, "dyM");
452 		}
453 
GetSeparator(string format, string timeParts)454 		private static string GetSeparator(string format, string timeParts)
455 		{
456 			int index = IndexOfTimePart(format, 0, timeParts);
457 
458 			if (index != -1)
459 			{
460 				// Found a time part, find out when it changes
461 				char cTimePart = format[index];
462 
463 				do
464 				{
465 					index++;
466 				} while (index < format.Length && format[index] == cTimePart);
467 
468 				int separatorStart = index;
469 
470 				// Now we need to find the end of the separator
471 				if (separatorStart < format.Length)
472 				{
473 					int separatorEnd = IndexOfTimePart(format, separatorStart, timeParts);
474 					if (separatorEnd != -1)
475 					{
476 						// From [separatorStart, count) is our string, except we need to unescape
477 						return UnescapeNlsString(format, separatorStart, separatorEnd - 1);
478 					}
479 				}
480 			}
481 
482 			return String.Empty;
483 		}
484 
IndexOfTimePart(string format, int startIndex, string timeParts)485 		private static int IndexOfTimePart(string format, int startIndex, string timeParts)
486 		{
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)
491 			{
492 				// See if we have a time Part
493 				if (!inQuote && timeParts.IndexOf(format[i]) != -1)
494 				{
495 					return i;
496 				}
497 				switch (format[i])
498 				{
499 					case '\\':
500 						if (i + 1 < format.Length)
501 						{
502 							++i;
503 							switch (format[i])
504 							{
505 								case '\'':
506 								case '\\':
507 									break;
508 								default:
509 									--i; //backup since we will move over this next
510 									break;
511 							}
512 						}
513 						break;
514 					case '\'':
515 						inQuote = !inQuote;
516 						break;
517 				}
518 			}
519 
520 			return -1;
521 		}
522 
523 		////////////////////////////////////////////////////////////////////////////
524 		//
525 		// Unescape a NLS style quote string
526 		//
527 		// This removes single quotes:
528 		//      'fred' -> fred
529 		//      'fred -> fred
530 		//      fred' -> fred
531 		//      fred's -> freds
532 		//
533 		// This removes the first \ of escaped characters:
534 		//      fred\'s -> fred's
535 		//      a\\b -> a\b
536 		//      a\b -> ab
537 		//
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 \.
540 		//
541 		////////////////////////////////////////////////////////////////////////////
UnescapeNlsString(String str, int start, int end)542 		static private String UnescapeNlsString(String str, int start, int end)
543 		{
544 			Contract.Requires(str != null);
545 			Contract.Requires(start >= 0);
546 			Contract.Requires(end >= 0);
547 			StringBuilder result = null;
548 
549 			for (int i = start; i < str.Length && i <= end; i++)
550 			{
551 				switch (str[i])
552 				{
553 					case '\'':
554 						if (result == null)
555 						{
556 							result = new StringBuilder(str, start, i - start, str.Length);
557 						}
558 						break;
559 					case '\\':
560 						if (result == null)
561 						{
562 							result = new StringBuilder(str, start, i - start, str.Length);
563 						}
564 						++i;
565 						if (i < str.Length)
566 						{
567 							result.Append(str[i]);
568 						}
569 						break;
570 					default:
571 						if (result != null)
572 						{
573 							result.Append(str[i]);
574 						}
575 						break;
576 				}
577 			}
578 
579 			if (result == null)
580 				return (str.Substring(start, end - start + 1));
581 
582 			return (result.ToString());
583 		}
584 
585 #endregion
586 
587 
ReescapeWin32Strings(String[] array)588 		static internal String[] ReescapeWin32Strings(String[] array)
589 		{
590 			return array;
591 		}
592 
ReescapeWin32String(String str)593 		static internal String ReescapeWin32String(String str)
594 		{
595 			return str;
596 		}
597 
IsCustomCultureId(int cultureId)598 		internal static bool IsCustomCultureId(int cultureId)
599 		{
600 			return false;
601 		}
602 
GetNFIValues(NumberFormatInfo nfi)603 		internal void GetNFIValues (NumberFormatInfo nfi)
604 		{
605 			if (this.IsInvariantCulture)
606 			{
607 				// Same as default values
608 			}
609 			else
610 			{
611 				//
612 				// We don't have information for the following four.  All cultures use
613 				// the same value of the number formatting values.
614 				//
615 				// PercentDecimalDigits
616 				// PercentDecimalSeparator
617 				// PercentGroupSize
618 				// PercentGroupSeparator
619 				//
620 				fill_number_data (nfi, numberIndex);
621 			}
622 
623 			//
624 			// We don't have percent values, so use the number values
625 			//
626 			nfi.percentDecimalDigits = nfi.numberDecimalDigits;
627 			nfi.percentDecimalSeparator = nfi.numberDecimalSeparator;
628 			nfi.percentGroupSizes = nfi.numberGroupSizes;
629 			nfi.percentGroupSeparator = nfi.numberGroupSeparator;
630 		}
631 
632 		[MethodImplAttribute (MethodImplOptions.InternalCall)]
fill_number_data(NumberFormatInfo nfi, int numberIndex)633 		extern static void fill_number_data (NumberFormatInfo nfi, int numberIndex);
634 	}
635 }
636