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