From 61d6a817e39d3bae0f47dbc09838d51db22a5d30 Mon Sep 17 00:00:00 2001 From: Jiyoung Yun Date: Tue, 13 Jun 2017 18:47:36 +0900 Subject: Imported Upstream version 2.0.0.11992 --- .../System/Globalization/CalendarData.Unix.cs | 348 ++++++++ .../System/Globalization/CalendarWeekRule.cs | 1 - .../Globalization/ChineseLunisolarCalendar.cs | 1 - .../System/Globalization/CultureData.Unix.cs | 431 ++++++++++ .../Globalization/CultureNotFoundException.cs | 6 +- .../System/Globalization/DateTimeFormatInfo.cs | 1 - .../shared/System/Globalization/DaylightTime.cs | 1 - .../Globalization/EastAsianLunisolarCalendar.cs | 1 - .../System/Globalization/GregorianCalendarTypes.cs | 1 - .../shared/System/Globalization/HebrewCalendar.cs | 1 - .../System/Globalization/HijriCalendar.Unix.cs | 15 + .../System/Globalization/HijriCalendar.Win32.cs | 96 +++ .../System/Globalization/HijriCalendar.WinRT.cs | 16 + .../shared/System/Globalization/HijriCalendar.cs | 1 - .../shared/System/Globalization/IdnMapping.Unix.cs | 142 ++++ .../System/Globalization/JapaneseCalendar.Unix.cs | 96 +++ .../System/Globalization/JapaneseCalendar.Win32.cs | 209 +++++ .../System/Globalization/JapaneseCalendar.WinRT.cs | 62 ++ .../System/Globalization/JapaneseCalendar.cs | 1 - .../Globalization/JapaneseLunisolarCalendar.cs | 1 - .../shared/System/Globalization/JulianCalendar.cs | 1 - .../shared/System/Globalization/KoreanCalendar.cs | 1 - .../Globalization/KoreanLunisolarCalendar.cs | 1 - .../System/Globalization/NumberFormatInfo.cs | 899 +++++++++++++++++++++ .../shared/System/Globalization/PersianCalendar.cs | 1 - .../shared/System/Globalization/SortKey.cs | 202 +++++ .../shared/System/Globalization/SortVersion.cs | 20 +- .../shared/System/Globalization/StringInfo.cs | 373 +++++++++ .../shared/System/Globalization/TaiwanCalendar.cs | 1 - .../Globalization/TaiwanLunisolarCalendar.cs | 1 - .../System/Globalization/ThaiBuddhistCalendar.cs | 1 - .../System/Globalization/UmAlQuraCalendar.cs | 1 - 32 files changed, 2900 insertions(+), 33 deletions(-) create mode 100644 src/mscorlib/shared/System/Globalization/CalendarData.Unix.cs create mode 100644 src/mscorlib/shared/System/Globalization/CultureData.Unix.cs create mode 100644 src/mscorlib/shared/System/Globalization/HijriCalendar.Unix.cs create mode 100644 src/mscorlib/shared/System/Globalization/HijriCalendar.Win32.cs create mode 100644 src/mscorlib/shared/System/Globalization/HijriCalendar.WinRT.cs create mode 100644 src/mscorlib/shared/System/Globalization/IdnMapping.Unix.cs create mode 100644 src/mscorlib/shared/System/Globalization/JapaneseCalendar.Unix.cs create mode 100644 src/mscorlib/shared/System/Globalization/JapaneseCalendar.Win32.cs create mode 100644 src/mscorlib/shared/System/Globalization/JapaneseCalendar.WinRT.cs create mode 100644 src/mscorlib/shared/System/Globalization/NumberFormatInfo.cs create mode 100644 src/mscorlib/shared/System/Globalization/SortKey.cs create mode 100644 src/mscorlib/shared/System/Globalization/StringInfo.cs (limited to 'src/mscorlib/shared/System/Globalization') diff --git a/src/mscorlib/shared/System/Globalization/CalendarData.Unix.cs b/src/mscorlib/shared/System/Globalization/CalendarData.Unix.cs new file mode 100644 index 0000000000..a2ceeb1e67 --- /dev/null +++ b/src/mscorlib/shared/System/Globalization/CalendarData.Unix.cs @@ -0,0 +1,348 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; + +namespace System.Globalization +{ + // needs to be kept in sync with CalendarDataType in System.Globalization.Native + internal enum CalendarDataType + { + Uninitialized = 0, + NativeName = 1, + MonthDay = 2, + ShortDates = 3, + LongDates = 4, + YearMonths = 5, + DayNames = 6, + AbbrevDayNames = 7, + MonthNames = 8, + AbbrevMonthNames = 9, + SuperShortDayNames = 10, + MonthGenitiveNames = 11, + AbbrevMonthGenitiveNames = 12, + EraNames = 13, + AbbrevEraNames = 14, + } + + internal partial class CalendarData + { + private bool LoadCalendarDataFromSystem(String localeName, CalendarId calendarId) + { + bool result = true; + result &= GetCalendarInfo(localeName, calendarId, CalendarDataType.NativeName, out this.sNativeName); + result &= GetCalendarInfo(localeName, calendarId, CalendarDataType.MonthDay, out this.sMonthDay); + this.sMonthDay = NormalizeDatePattern(this.sMonthDay); + + result &= EnumDatePatterns(localeName, calendarId, CalendarDataType.ShortDates, out this.saShortDates); + result &= EnumDatePatterns(localeName, calendarId, CalendarDataType.LongDates, out this.saLongDates); + result &= EnumDatePatterns(localeName, calendarId, CalendarDataType.YearMonths, out this.saYearMonths); + result &= EnumCalendarInfo(localeName, calendarId, CalendarDataType.DayNames, out this.saDayNames); + result &= EnumCalendarInfo(localeName, calendarId, CalendarDataType.AbbrevDayNames, out this.saAbbrevDayNames); + result &= EnumCalendarInfo(localeName, calendarId, CalendarDataType.SuperShortDayNames, out this.saSuperShortDayNames); + result &= EnumMonthNames(localeName, calendarId, CalendarDataType.MonthNames, out this.saMonthNames); + result &= EnumMonthNames(localeName, calendarId, CalendarDataType.AbbrevMonthNames, out this.saAbbrevMonthNames); + result &= EnumMonthNames(localeName, calendarId, CalendarDataType.MonthGenitiveNames, out this.saMonthGenitiveNames); + result &= EnumMonthNames(localeName, calendarId, CalendarDataType.AbbrevMonthGenitiveNames, out this.saAbbrevMonthGenitiveNames); + result &= EnumEraNames(localeName, calendarId, CalendarDataType.EraNames, out this.saEraNames); + result &= EnumEraNames(localeName, calendarId, CalendarDataType.AbbrevEraNames, out this.saAbbrevEraNames); + + return result; + } + + internal static int GetTwoDigitYearMax(CalendarId calendarId) + { + // There is no user override for this value on Linux or in ICU. + // So just return -1 to use the hard-coded defaults. + return -1; + } + + // Call native side to figure out which calendars are allowed + internal static int GetCalendars(string localeName, bool useUserOverride, CalendarId[] calendars) + { + Debug.Assert(!GlobalizationMode.Invariant); + + // NOTE: there are no 'user overrides' on Linux + int count = Interop.GlobalizationInterop.GetCalendars(localeName, calendars, calendars.Length); + + // ensure there is at least 1 calendar returned + if (count == 0 && calendars.Length > 0) + { + calendars[0] = CalendarId.GREGORIAN; + count = 1; + } + + return count; + } + + private static bool SystemSupportsTaiwaneseCalendar() + { + return true; + } + + // PAL Layer ends here + + private static bool GetCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, out string calendarString) + { + Debug.Assert(!GlobalizationMode.Invariant); + + return Interop.CallStringMethod( + (locale, calId, type, stringBuilder) => + Interop.GlobalizationInterop.GetCalendarInfo( + locale, + calId, + type, + stringBuilder, + stringBuilder.Capacity), + localeName, + calendarId, + dataType, + out calendarString); + } + + private static bool EnumDatePatterns(string localeName, CalendarId calendarId, CalendarDataType dataType, out string[] datePatterns) + { + datePatterns = null; + + CallbackContext callbackContext = new CallbackContext(); + callbackContext.DisallowDuplicates = true; + bool result = EnumCalendarInfo(localeName, calendarId, dataType, callbackContext); + if (result) + { + List datePatternsList = callbackContext.Results; + + datePatterns = new string[datePatternsList.Count]; + for (int i = 0; i < datePatternsList.Count; i++) + { + datePatterns[i] = NormalizeDatePattern(datePatternsList[i]); + } + } + + return result; + } + + /// + /// The ICU date format characters are not exactly the same as the .NET date format characters. + /// NormalizeDatePattern will take in an ICU date pattern and return the equivalent .NET date pattern. + /// + /// + /// see Date Field Symbol Table in http://userguide.icu-project.org/formatparse/datetime + /// and https://msdn.microsoft.com/en-us/library/8kb3ddd4(v=vs.110).aspx + /// + private static string NormalizeDatePattern(string input) + { + StringBuilder destination = StringBuilderCache.Acquire(input.Length); + + int index = 0; + while (index < input.Length) + { + switch (input[index]) + { + case '\'': + // single quotes escape characters, like 'de' in es-SP + // so read verbatim until the next single quote + destination.Append(input[index++]); + while (index < input.Length) + { + char current = input[index++]; + destination.Append(current); + if (current == '\'') + { + break; + } + } + break; + case 'E': + case 'e': + case 'c': + // 'E' in ICU is the day of the week, which maps to 3 or 4 'd's in .NET + // 'e' in ICU is the local day of the week, which has no representation in .NET, but + // maps closest to 3 or 4 'd's in .NET + // 'c' in ICU is the stand-alone day of the week, which has no representation in .NET, but + // maps closest to 3 or 4 'd's in .NET + NormalizeDayOfWeek(input, destination, ref index); + break; + case 'L': + case 'M': + // 'L' in ICU is the stand-alone name of the month, + // which maps closest to 'M' in .NET since it doesn't support stand-alone month names in patterns + // 'M' in both ICU and .NET is the month, + // but ICU supports 5 'M's, which is the super short month name + int occurrences = CountOccurrences(input, input[index], ref index); + if (occurrences > 4) + { + // 5 'L's or 'M's in ICU is the super short name, which maps closest to MMM in .NET + occurrences = 3; + } + destination.Append('M', occurrences); + break; + case 'G': + // 'G' in ICU is the era, which maps to 'g' in .NET + occurrences = CountOccurrences(input, 'G', ref index); + + // it doesn't matter how many 'G's, since .NET only supports 'g' or 'gg', and they + // have the same meaning + destination.Append('g'); + break; + case 'y': + // a single 'y' in ICU is the year with no padding or trimming. + // a single 'y' in .NET is the year with 1 or 2 digits + // so convert any single 'y' to 'yyyy' + occurrences = CountOccurrences(input, 'y', ref index); + if (occurrences == 1) + { + occurrences = 4; + } + destination.Append('y', occurrences); + break; + default: + const string unsupportedDateFieldSymbols = "YuUrQqwWDFg"; + Debug.Assert(unsupportedDateFieldSymbols.IndexOf(input[index]) == -1, + string.Format(CultureInfo.InvariantCulture, + "Encountered an unexpected date field symbol '{0}' from ICU which has no known corresponding .NET equivalent.", + input[index])); + + destination.Append(input[index++]); + break; + } + } + + return StringBuilderCache.GetStringAndRelease(destination); + } + + private static void NormalizeDayOfWeek(string input, StringBuilder destination, ref int index) + { + char dayChar = input[index]; + int occurrences = CountOccurrences(input, dayChar, ref index); + occurrences = Math.Max(occurrences, 3); + if (occurrences > 4) + { + // 5 and 6 E/e/c characters in ICU is the super short names, which maps closest to ddd in .NET + occurrences = 3; + } + + destination.Append('d', occurrences); + } + + private static int CountOccurrences(string input, char value, ref int index) + { + int startIndex = index; + while (index < input.Length && input[index] == value) + { + index++; + } + + return index - startIndex; + } + + private static bool EnumMonthNames(string localeName, CalendarId calendarId, CalendarDataType dataType, out string[] monthNames) + { + monthNames = null; + + CallbackContext callbackContext = new CallbackContext(); + bool result = EnumCalendarInfo(localeName, calendarId, dataType, callbackContext); + if (result) + { + // the month-name arrays are expected to have 13 elements. If ICU only returns 12, add an + // extra empty string to fill the array. + if (callbackContext.Results.Count == 12) + { + callbackContext.Results.Add(string.Empty); + } + + monthNames = callbackContext.Results.ToArray(); + } + + return result; + } + + private static bool EnumEraNames(string localeName, CalendarId calendarId, CalendarDataType dataType, out string[] eraNames) + { + bool result = EnumCalendarInfo(localeName, calendarId, dataType, out eraNames); + + // .NET expects that only the Japanese calendars have more than 1 era. + // So for other calendars, only return the latest era. + if (calendarId != CalendarId.JAPAN && calendarId != CalendarId.JAPANESELUNISOLAR && eraNames.Length > 0) + { + string[] latestEraName = new string[] { eraNames[eraNames.Length - 1] }; + eraNames = latestEraName; + } + + return result; + } + + internal static bool EnumCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, out string[] calendarData) + { + calendarData = null; + + CallbackContext callbackContext = new CallbackContext(); + bool result = EnumCalendarInfo(localeName, calendarId, dataType, callbackContext); + if (result) + { + calendarData = callbackContext.Results.ToArray(); + } + + return result; + } + + private static bool EnumCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, CallbackContext callbackContext) + { + GCHandle context = GCHandle.Alloc(callbackContext); + try + { + return Interop.GlobalizationInterop.EnumCalendarInfo(EnumCalendarInfoCallback, localeName, calendarId, dataType, (IntPtr)context); + } + finally + { + context.Free(); + } + } + + private static void EnumCalendarInfoCallback(string calendarString, IntPtr context) + { + try + { + CallbackContext callbackContext = (CallbackContext)((GCHandle)context).Target; + + if (callbackContext.DisallowDuplicates) + { + foreach (string existingResult in callbackContext.Results) + { + if (string.Equals(calendarString, existingResult, StringComparison.Ordinal)) + { + // the value is already in the results, so don't add it again + return; + } + } + } + + callbackContext.Results.Add(calendarString); + } + catch (Exception e) + { + Debug.Assert(false, e.ToString()); + // we ignore the managed exceptions here because EnumCalendarInfoCallback will get called from the native code. + // If we don't ignore the exception here that can cause the runtime to fail fast. + } + } + + private class CallbackContext + { + private List _results = new List(); + + public CallbackContext() + { + } + + public List Results { get { return _results; } } + + public bool DisallowDuplicates { get; set; } + } + } +} diff --git a/src/mscorlib/shared/System/Globalization/CalendarWeekRule.cs b/src/mscorlib/shared/System/Globalization/CalendarWeekRule.cs index b381b5c544..f683ed05e7 100644 --- a/src/mscorlib/shared/System/Globalization/CalendarWeekRule.cs +++ b/src/mscorlib/shared/System/Globalization/CalendarWeekRule.cs @@ -4,7 +4,6 @@ namespace System.Globalization { - [Serializable] public enum CalendarWeekRule { FirstDay = 0, // Week 1 begins on the first day of the year diff --git a/src/mscorlib/shared/System/Globalization/ChineseLunisolarCalendar.cs b/src/mscorlib/shared/System/Globalization/ChineseLunisolarCalendar.cs index 404add0936..e09011a9d8 100644 --- a/src/mscorlib/shared/System/Globalization/ChineseLunisolarCalendar.cs +++ b/src/mscorlib/shared/System/Globalization/ChineseLunisolarCalendar.cs @@ -14,7 +14,6 @@ namespace System.Globalization ** ChineseLunisolar 1901/01/01 2100/12/29 */ - [Serializable] public class ChineseLunisolarCalendar : EastAsianLunisolarCalendar { // diff --git a/src/mscorlib/shared/System/Globalization/CultureData.Unix.cs b/src/mscorlib/shared/System/Globalization/CultureData.Unix.cs new file mode 100644 index 0000000000..4f685de580 --- /dev/null +++ b/src/mscorlib/shared/System/Globalization/CultureData.Unix.cs @@ -0,0 +1,431 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; + +namespace System.Globalization +{ + internal partial class CultureData + { + // ICU constants + const int ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY = 100; // max size of keyword or value + const int ICU_ULOC_FULLNAME_CAPACITY = 157; // max size of locale name + const string ICU_COLLATION_KEYWORD = "@collation="; + + + /// + /// This method uses the sRealName field (which is initialized by the constructor before this is called) to + /// initialize the rest of the state of CultureData based on the underlying OS globalization library. + /// + private unsafe bool InitCultureData() + { + Debug.Assert(_sRealName != null); + + Debug.Assert(!GlobalizationMode.Invariant); + + string alternateSortName = string.Empty; + string realNameBuffer = _sRealName; + + // Basic validation + if (realNameBuffer.Contains("@")) + { + return false; // don't allow ICU variants to come in directly + } + + // Replace _ (alternate sort) with @collation= for ICU + int index = realNameBuffer.IndexOf('_'); + if (index > 0) + { + if (index >= (realNameBuffer.Length - 1) // must have characters after _ + || realNameBuffer.Substring(index + 1).Contains("_")) // only one _ allowed + { + return false; // fail + } + alternateSortName = realNameBuffer.Substring(index + 1); + realNameBuffer = realNameBuffer.Substring(0, index) + ICU_COLLATION_KEYWORD + alternateSortName; + } + + // Get the locale name from ICU + if (!GetLocaleName(realNameBuffer, out _sWindowsName)) + { + return false; // fail + } + + // Replace the ICU collation keyword with an _ + index = _sWindowsName.IndexOf(ICU_COLLATION_KEYWORD, StringComparison.Ordinal); + if (index >= 0) + { + _sName = _sWindowsName.Substring(0, index) + "_" + alternateSortName; + } + else + { + _sName = _sWindowsName; + } + _sRealName = _sName; + + _iLanguage = this.ILANGUAGE; + if (_iLanguage == 0) + { + _iLanguage = CultureInfo.LOCALE_CUSTOM_UNSPECIFIED; + } + + _bNeutral = (this.SISO3166CTRYNAME.Length == 0); + + _sSpecificCulture = _bNeutral ? LocaleData.GetSpecificCultureName(_sRealName) : _sRealName; + + // Remove the sort from sName unless custom culture + if (index>0 && !_bNeutral && !IsCustomCultureId(_iLanguage)) + { + _sName = _sWindowsName.Substring(0, index); + } + return true; + } + + internal static bool GetLocaleName(string localeName, out string windowsName) + { + // Get the locale name from ICU + StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_FULLNAME_CAPACITY); + if (!Interop.GlobalizationInterop.GetLocaleName(localeName, sb, sb.Capacity)) + { + StringBuilderCache.Release(sb); + windowsName = null; + return false; // fail + } + + // Success - use the locale name returned which may be different than realNameBuffer (casing) + windowsName = StringBuilderCache.GetStringAndRelease(sb); // the name passed to subsequent ICU calls + return true; + } + + internal static bool GetDefaultLocaleName(out string windowsName) + { + // Get the default (system) locale name from ICU + StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_FULLNAME_CAPACITY); + if (!Interop.GlobalizationInterop.GetDefaultLocaleName(sb, sb.Capacity)) + { + StringBuilderCache.Release(sb); + windowsName = null; + return false; // fail + } + + // Success - use the locale name returned which may be different than realNameBuffer (casing) + windowsName = StringBuilderCache.GetStringAndRelease(sb); // the name passed to subsequent ICU calls + return true; + } + + private string GetLocaleInfo(LocaleStringData type) + { + Debug.Assert(!GlobalizationMode.Invariant); + + Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo] Expected _sWindowsName to be populated already"); + return GetLocaleInfo(_sWindowsName, type); + } + + // For LOCALE_SPARENT we need the option of using the "real" name (forcing neutral names) instead of the + // "windows" name, which can be specific for downlevel (< windows 7) os's. + private string GetLocaleInfo(string localeName, LocaleStringData type) + { + Debug.Assert(localeName != null, "[CultureData.GetLocaleInfo] Expected localeName to be not be null"); + + switch (type) + { + case LocaleStringData.NegativeInfinitySymbol: + // not an equivalent in ICU; prefix the PositiveInfinitySymbol with NegativeSign + return GetLocaleInfo(localeName, LocaleStringData.NegativeSign) + + GetLocaleInfo(localeName, LocaleStringData.PositiveInfinitySymbol); + } + + StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY); + + bool result = Interop.GlobalizationInterop.GetLocaleInfoString(localeName, (uint)type, sb, sb.Capacity); + if (!result) + { + // Failed, just use empty string + StringBuilderCache.Release(sb); + Debug.Assert(false, "[CultureData.GetLocaleInfo(LocaleStringData)] Failed"); + return String.Empty; + } + return StringBuilderCache.GetStringAndRelease(sb); + } + + private int GetLocaleInfo(LocaleNumberData type) + { + Debug.Assert(!GlobalizationMode.Invariant); + + Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo(LocaleNumberData)] Expected _sWindowsName to be populated already"); + + switch (type) + { + case LocaleNumberData.CalendarType: + // returning 0 will cause the first supported calendar to be returned, which is the preferred calendar + return 0; + } + + + int value = 0; + bool result = Interop.GlobalizationInterop.GetLocaleInfoInt(_sWindowsName, (uint)type, ref value); + if (!result) + { + // Failed, just use 0 + Debug.Assert(false, "[CultureData.GetLocaleInfo(LocaleNumberData)] failed"); + } + + return value; + } + + private int[] GetLocaleInfo(LocaleGroupingData type) + { + Debug.Assert(_sWindowsName != null, "[CultureData.GetLocaleInfo(LocaleGroupingData)] Expected _sWindowsName to be populated already"); + + int primaryGroupingSize = 0; + int secondaryGroupingSize = 0; + bool result = Interop.GlobalizationInterop.GetLocaleInfoGroupingSizes(_sWindowsName, (uint)type, ref primaryGroupingSize, ref secondaryGroupingSize); + if (!result) + { + Debug.Assert(false, "[CultureData.GetLocaleInfo(LocaleGroupingData type)] failed"); + } + + if (secondaryGroupingSize == 0) + { + return new int[] { primaryGroupingSize }; + } + + return new int[] { primaryGroupingSize, secondaryGroupingSize }; + } + + private string GetTimeFormatString() + { + return GetTimeFormatString(false); + } + + private string GetTimeFormatString(bool shortFormat) + { + Debug.Assert(_sWindowsName != null, "[CultureData.GetTimeFormatString(bool shortFormat)] Expected _sWindowsName to be populated already"); + + StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_KEYWORD_AND_VALUES_CAPACITY); + + bool result = Interop.GlobalizationInterop.GetLocaleTimeFormat(_sWindowsName, shortFormat, sb, sb.Capacity); + if (!result) + { + // Failed, just use empty string + StringBuilderCache.Release(sb); + Debug.Assert(false, "[CultureData.GetTimeFormatString(bool shortFormat)] Failed"); + return String.Empty; + } + + return ConvertIcuTimeFormatString(StringBuilderCache.GetStringAndRelease(sb)); + } + + private int GetFirstDayOfWeek() + { + return this.GetLocaleInfo(LocaleNumberData.FirstDayOfWeek); + } + + private String[] GetTimeFormats() + { + string format = GetTimeFormatString(false); + return new string[] { format }; + } + + private String[] GetShortTimeFormats() + { + string format = GetTimeFormatString(true); + return new string[] { format }; + } + + private static CultureData GetCultureDataFromRegionName(String regionName) + { + // no support to lookup by region name, other than the hard-coded list in CultureData + return null; + } + + private static string GetLanguageDisplayName(string cultureName) + { + return new CultureInfo(cultureName)._cultureData.GetLocaleInfo(cultureName, LocaleStringData.LocalizedDisplayName); + } + + private static string GetRegionDisplayName(string isoCountryCode) + { + // use the fallback which is to return NativeName + return null; + } + + private static CultureInfo GetUserDefaultCulture() + { + return CultureInfo.GetUserDefaultCulture(); + } + + private static string ConvertIcuTimeFormatString(string icuFormatString) + { + StringBuilder sb = StringBuilderCache.Acquire(ICU_ULOC_FULLNAME_CAPACITY); + bool amPmAdded = false; + + for (int i = 0; i < icuFormatString.Length; i++) + { + switch(icuFormatString[i]) + { + case ':': + case '.': + case 'H': + case 'h': + case 'm': + case 's': + sb.Append(icuFormatString[i]); + break; + + case ' ': + case '\u00A0': + // Convert nonbreaking spaces into regular spaces + sb.Append(' '); + break; + + case 'a': // AM/PM + if (!amPmAdded) + { + amPmAdded = true; + sb.Append("tt"); + } + break; + + } + } + + return StringBuilderCache.GetStringAndRelease(sb); + } + + private static string LCIDToLocaleName(int culture) + { + Debug.Assert(!GlobalizationMode.Invariant); + + return LocaleData.LCIDToLocaleName(culture); + } + + private static int LocaleNameToLCID(string cultureName) + { + Debug.Assert(!GlobalizationMode.Invariant); + + int lcid = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.Lcid); + return lcid == -1 ? CultureInfo.LOCALE_CUSTOM_UNSPECIFIED : lcid; + } + + private static int GetAnsiCodePage(string cultureName) + { + int ansiCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.AnsiCodePage); + return ansiCodePage == -1 ? CultureData.Invariant.IDEFAULTANSICODEPAGE : ansiCodePage; + } + + private static int GetOemCodePage(string cultureName) + { + int oemCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.OemCodePage); + return oemCodePage == -1 ? CultureData.Invariant.IDEFAULTOEMCODEPAGE : oemCodePage; + } + + private static int GetMacCodePage(string cultureName) + { + int macCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.MacCodePage); + return macCodePage == -1 ? CultureData.Invariant.IDEFAULTMACCODEPAGE : macCodePage; + } + + private static int GetEbcdicCodePage(string cultureName) + { + int ebcdicCodePage = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.EbcdicCodePage); + return ebcdicCodePage == -1 ? CultureData.Invariant.IDEFAULTEBCDICCODEPAGE : ebcdicCodePage; + } + + private static int GetGeoId(string cultureName) + { + int geoId = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.GeoId); + return geoId == -1 ? CultureData.Invariant.IGEOID : geoId; + } + + private static int GetDigitSubstitution(string cultureName) + { + int digitSubstitution = LocaleData.GetLocaleDataNumericPart(cultureName, LocaleDataParts.DigitSubstitution); + return digitSubstitution == -1 ? (int) DigitShapes.None : digitSubstitution; + } + + private static string GetThreeLetterWindowsLanguageName(string cultureName) + { + string langName = LocaleData.GetThreeLetterWindowsLangageName(cultureName); + return langName == null ? "ZZZ" /* default lang name */ : langName; + } + + private static CultureInfo[] EnumCultures(CultureTypes types) + { + Debug.Assert(!GlobalizationMode.Invariant); + + if ((types & (CultureTypes.NeutralCultures | CultureTypes.SpecificCultures)) == 0) + { + return Array.Empty(); + } + + int bufferLength = Interop.GlobalizationInterop.GetLocales(null, 0); + if (bufferLength <= 0) + { + return Array.Empty(); + } + + Char [] chars = new Char[bufferLength]; + + bufferLength = Interop.GlobalizationInterop.GetLocales(chars, bufferLength); + if (bufferLength <= 0) + { + return Array.Empty(); + } + + bool enumNeutrals = (types & CultureTypes.NeutralCultures) != 0; + bool enumSpecificss = (types & CultureTypes.SpecificCultures) != 0; + + List list = new List(); + if (enumNeutrals) + { + list.Add(CultureInfo.InvariantCulture); + } + + int index = 0; + while (index < bufferLength) + { + int length = (int) chars[index++]; + if (index + length <= bufferLength) + { + CultureInfo ci = CultureInfo.GetCultureInfo(new String(chars, index, length)); + if ((enumNeutrals && ci.IsNeutralCulture) || (enumSpecificss && !ci.IsNeutralCulture)) + { + list.Add(ci); + } + } + + index += length; + } + + return list.ToArray(); + } + + private static string GetConsoleFallbackName(string cultureName) + { + return LocaleData.GetConsoleUICulture(cultureName); + } + + internal bool IsFramework // not applicable on Linux based systems + { + get { return false; } + } + + internal bool IsWin32Installed // not applicable on Linux based systems + { + get { return false; } + } + + internal bool IsReplacementCulture // not applicable on Linux based systems + { + get { return false; } + } + } +} diff --git a/src/mscorlib/shared/System/Globalization/CultureNotFoundException.cs b/src/mscorlib/shared/System/Globalization/CultureNotFoundException.cs index 929f4bb000..dde1a8b2ba 100644 --- a/src/mscorlib/shared/System/Globalization/CultureNotFoundException.cs +++ b/src/mscorlib/shared/System/Globalization/CultureNotFoundException.cs @@ -6,7 +6,6 @@ using System.Runtime.Serialization; namespace System.Globalization { - [Serializable] public class CultureNotFoundException : ArgumentException { private string _invalidCultureName; // unrecognized culture name @@ -59,15 +58,12 @@ namespace System.Globalization protected CultureNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { - _invalidCultureId = (int?)info.GetValue("InvalidCultureId", typeof(int?)); - _invalidCultureName = (string)info.GetValue("InvalidCultureName", typeof(string)); + throw new PlatformNotSupportedException(); } public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); - info.AddValue("InvalidCultureId", _invalidCultureId, typeof(int?)); - info.AddValue("InvalidCultureName", _invalidCultureName, typeof(string)); } public virtual Nullable InvalidCultureId diff --git a/src/mscorlib/shared/System/Globalization/DateTimeFormatInfo.cs b/src/mscorlib/shared/System/Globalization/DateTimeFormatInfo.cs index d3f1ea9a45..5d3239ebfc 100644 --- a/src/mscorlib/shared/System/Globalization/DateTimeFormatInfo.cs +++ b/src/mscorlib/shared/System/Globalization/DateTimeFormatInfo.cs @@ -49,7 +49,6 @@ namespace System.Globalization } - [Serializable] public sealed class DateTimeFormatInfo : IFormatProvider, ICloneable { // cache for the invariant culture. diff --git a/src/mscorlib/shared/System/Globalization/DaylightTime.cs b/src/mscorlib/shared/System/Globalization/DaylightTime.cs index b3c70e1d10..10f074dc24 100644 --- a/src/mscorlib/shared/System/Globalization/DaylightTime.cs +++ b/src/mscorlib/shared/System/Globalization/DaylightTime.cs @@ -5,7 +5,6 @@ namespace System.Globalization { // This class represents a starting/ending time for a period of daylight saving time. - [Serializable] public class DaylightTime { private readonly DateTime _start; diff --git a/src/mscorlib/shared/System/Globalization/EastAsianLunisolarCalendar.cs b/src/mscorlib/shared/System/Globalization/EastAsianLunisolarCalendar.cs index d06b13cd7d..0697b602db 100644 --- a/src/mscorlib/shared/System/Globalization/EastAsianLunisolarCalendar.cs +++ b/src/mscorlib/shared/System/Globalization/EastAsianLunisolarCalendar.cs @@ -6,7 +6,6 @@ using System.Diagnostics.Contracts; namespace System.Globalization { - [Serializable] public abstract class EastAsianLunisolarCalendar : Calendar { internal const int LeapMonth = 0; diff --git a/src/mscorlib/shared/System/Globalization/GregorianCalendarTypes.cs b/src/mscorlib/shared/System/Globalization/GregorianCalendarTypes.cs index 1b61e5256e..46f13b00e0 100644 --- a/src/mscorlib/shared/System/Globalization/GregorianCalendarTypes.cs +++ b/src/mscorlib/shared/System/Globalization/GregorianCalendarTypes.cs @@ -6,7 +6,6 @@ namespace System.Globalization { // Note: The values of the members of this enum must match the coresponding values // in the CalendarId enum (since we cast between GregorianCalendarTypes and CalendarId). - [Serializable] public enum GregorianCalendarTypes { Localized = CalendarId.GREGORIAN, diff --git a/src/mscorlib/shared/System/Globalization/HebrewCalendar.cs b/src/mscorlib/shared/System/Globalization/HebrewCalendar.cs index b4f54f8fbb..6ba4f082f1 100644 --- a/src/mscorlib/shared/System/Globalization/HebrewCalendar.cs +++ b/src/mscorlib/shared/System/Globalization/HebrewCalendar.cs @@ -62,7 +62,6 @@ namespace System.Globalization // Gregorian to Hebrew Lunar from 1583 to 2239. - [Serializable] public class HebrewCalendar : Calendar { public static readonly int HebrewEra = 1; diff --git a/src/mscorlib/shared/System/Globalization/HijriCalendar.Unix.cs b/src/mscorlib/shared/System/Globalization/HijriCalendar.Unix.cs new file mode 100644 index 0000000000..a6e8f73d3e --- /dev/null +++ b/src/mscorlib/shared/System/Globalization/HijriCalendar.Unix.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Globalization +{ + public partial class HijriCalendar : Calendar + { + private static int GetHijriDateAdjustment() + { + // this setting is not supported on Unix, so always return 0 + return 0; + } + } +} diff --git a/src/mscorlib/shared/System/Globalization/HijriCalendar.Win32.cs b/src/mscorlib/shared/System/Globalization/HijriCalendar.Win32.cs new file mode 100644 index 0000000000..869b809bff --- /dev/null +++ b/src/mscorlib/shared/System/Globalization/HijriCalendar.Win32.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32; + +namespace System.Globalization +{ + public partial class HijriCalendar : Calendar + { + private int GetHijriDateAdjustment() + { + if (_hijriAdvance == Int32.MinValue) + { + // Never been set before. Use the system value from registry. + _hijriAdvance = GetAdvanceHijriDate(); + } + return (_hijriAdvance); + } + + private const String InternationalRegKey = "Control Panel\\International"; + private const String HijriAdvanceRegKeyEntry = "AddHijriDate"; + + /*=================================GetAdvanceHijriDate========================== + **Action: Gets the AddHijriDate value from the registry. + **Returns: + **Arguments: None. + **Exceptions: + **Note: + ** The HijriCalendar has a user-overidable calculation. That is, use can set a value from the control + ** panel, so that the calculation of the Hijri Calendar can move ahead or backwards from -2 to +2 days. + ** + ** The valid string values in the registry are: + ** "AddHijriDate-2" => Add -2 days to the current calculated Hijri date. + ** "AddHijriDate" => Add -1 day to the current calculated Hijri date. + ** "" => Add 0 day to the current calculated Hijri date. + ** "AddHijriDate+1" => Add +1 days to the current calculated Hijri date. + ** "AddHijriDate+2" => Add +2 days to the current calculated Hijri date. + ============================================================================*/ + private static int GetAdvanceHijriDate() + { + int hijriAdvance = 0; + Microsoft.Win32.RegistryKey key = null; + + try + { + // Open in read-only mode. + // Use InternalOpenSubKey so that we avoid the security check. + key = RegistryKey.GetBaseKey(RegistryKey.HKEY_CURRENT_USER).OpenSubKey(InternationalRegKey, false); + } + //If this fails for any reason, we'll just return 0. + catch (ObjectDisposedException) { return 0; } + catch (ArgumentException) { return 0; } + + if (key != null) + { + try + { + Object value = key.InternalGetValue(HijriAdvanceRegKeyEntry, null, false, false); + if (value == null) + { + return (0); + } + String str = value.ToString(); + if (String.Compare(str, 0, HijriAdvanceRegKeyEntry, 0, HijriAdvanceRegKeyEntry.Length, StringComparison.OrdinalIgnoreCase) == 0) + { + if (str.Length == HijriAdvanceRegKeyEntry.Length) + hijriAdvance = -1; + else + { + str = str.Substring(HijriAdvanceRegKeyEntry.Length); + try + { + int advance = Int32.Parse(str.ToString(), CultureInfo.InvariantCulture); + if ((advance >= MinAdvancedHijri) && (advance <= MaxAdvancedHijri)) + { + hijriAdvance = advance; + } + } + // If we got garbage from registry just ignore it. + // hijriAdvance = 0 because of declaraction assignment up above. + catch (ArgumentException) { } + catch (FormatException) { } + catch (OverflowException) { } + } + } + } + finally + { + key.Close(); + } + } + return (hijriAdvance); + } + } +} diff --git a/src/mscorlib/shared/System/Globalization/HijriCalendar.WinRT.cs b/src/mscorlib/shared/System/Globalization/HijriCalendar.WinRT.cs new file mode 100644 index 0000000000..fb91c27ef6 --- /dev/null +++ b/src/mscorlib/shared/System/Globalization/HijriCalendar.WinRT.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Internal.Runtime.Augments; + +namespace System.Globalization +{ + public partial class HijriCalendar : Calendar + { + private static int GetHijriDateAdjustment() + { + return WinRTInterop.Callbacks.GetHijriDateAdjustment(); + } + } +} diff --git a/src/mscorlib/shared/System/Globalization/HijriCalendar.cs b/src/mscorlib/shared/System/Globalization/HijriCalendar.cs index cafde5fbb8..125248a685 100644 --- a/src/mscorlib/shared/System/Globalization/HijriCalendar.cs +++ b/src/mscorlib/shared/System/Globalization/HijriCalendar.cs @@ -42,7 +42,6 @@ namespace System.Globalization ** Hijri 0001/01/01 9666/04/03 */ - [Serializable] public partial class HijriCalendar : Calendar { public static readonly int HijriEra = 1; diff --git a/src/mscorlib/shared/System/Globalization/IdnMapping.Unix.cs b/src/mscorlib/shared/System/Globalization/IdnMapping.Unix.cs new file mode 100644 index 0000000000..2bbda0d3a7 --- /dev/null +++ b/src/mscorlib/shared/System/Globalization/IdnMapping.Unix.cs @@ -0,0 +1,142 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace System.Globalization +{ + sealed partial class IdnMapping + { + private unsafe string GetAsciiCore(char* unicode, int count) + { + Debug.Assert(!GlobalizationMode.Invariant); + + uint flags = Flags; + CheckInvalidIdnCharacters(unicode, count, flags, nameof(unicode)); + + const int StackallocThreshold = 512; + // Each unicode character is represented by up to 3 ASCII chars + // and the whole string is prefixed by "xn--" (length 4) + int estimatedLength = (int)Math.Min(count * 3L + 4, StackallocThreshold); + int actualLength; + if (estimatedLength < StackallocThreshold) + { + char* outputStack = stackalloc char[estimatedLength]; + actualLength = Interop.GlobalizationInterop.ToAscii(flags, unicode, count, outputStack, estimatedLength); + if (actualLength > 0 && actualLength <= estimatedLength) + { + return new string(outputStack, 0, actualLength); + } + } + else + { + actualLength = Interop.GlobalizationInterop.ToAscii(flags, unicode, count, null, 0); + } + if (actualLength == 0) + { + throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(unicode)); + } + + char[] outputHeap = new char[actualLength]; + fixed (char* pOutputHeap = &outputHeap[0]) + { + actualLength = Interop.GlobalizationInterop.ToAscii(flags, unicode, count, pOutputHeap, actualLength); + if (actualLength == 0 || actualLength > outputHeap.Length) + { + throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(unicode)); + } + return new string(pOutputHeap, 0, actualLength); + } + } + + private unsafe string GetUnicodeCore(char* ascii, int count) + { + Debug.Assert(!GlobalizationMode.Invariant); + + uint flags = Flags; + CheckInvalidIdnCharacters(ascii, count, flags, nameof(ascii)); + + const int StackAllocThreshold = 512; + if (count < StackAllocThreshold) + { + char* output = stackalloc char[count]; + return GetUnicodeCore(ascii, count, flags, output, count, reattempt: true); + } + else + { + char[] output = new char[count]; + fixed (char* pOutput = &output[0]) + { + return GetUnicodeCore(ascii, count, flags, pOutput, count, reattempt: true); + } + } + } + + private unsafe string GetUnicodeCore(char* ascii, int count, uint flags, char* output, int outputLength, bool reattempt) + { + Debug.Assert(!GlobalizationMode.Invariant); + + int realLen = Interop.GlobalizationInterop.ToUnicode(flags, ascii, count, output, outputLength); + + if (realLen == 0) + { + throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(ascii)); + } + else if (realLen <= outputLength) + { + return new string(output, 0, realLen); + } + else if (reattempt) + { + char[] newOutput = new char[realLen]; + fixed (char* pNewOutput = newOutput) + { + return GetUnicodeCore(ascii, count, flags, pNewOutput, realLen, reattempt: false); + } + } + + throw new ArgumentException(SR.Argument_IdnIllegalName, nameof(ascii)); + } + + // ----------------------------- + // ---- PAL layer ends here ---- + // ----------------------------- + + private uint Flags + { + get + { + int flags = + (AllowUnassigned ? Interop.GlobalizationInterop.AllowUnassigned : 0) | + (UseStd3AsciiRules ? Interop.GlobalizationInterop.UseStd3AsciiRules : 0); + return (uint)flags; + } + } + + /// + /// ICU doesn't check for invalid characters unless the STD3 rules option + /// is enabled. + /// + /// To match Windows behavior, we walk the string ourselves looking for these + /// bad characters so we can continue to throw ArgumentException in these cases. + /// + private static unsafe void CheckInvalidIdnCharacters(char* s, int count, uint flags, string paramName) + { + if ((flags & Interop.GlobalizationInterop.UseStd3AsciiRules) == 0) + { + for (int i = 0; i < count; i++) + { + char c = s[i]; + + // These characters are prohibited regardless of the UseStd3AsciiRules property. + // See https://msdn.microsoft.com/en-us/library/system.globalization.idnmapping.usestd3asciirules(v=vs.110).aspx + if (c <= 0x1F || c == 0x7F) + { + throw new ArgumentException(SR.Argument_IdnIllegalName, paramName); + } + } + } + } + } +} diff --git a/src/mscorlib/shared/System/Globalization/JapaneseCalendar.Unix.cs b/src/mscorlib/shared/System/Globalization/JapaneseCalendar.Unix.cs new file mode 100644 index 0000000000..51ff8095a3 --- /dev/null +++ b/src/mscorlib/shared/System/Globalization/JapaneseCalendar.Unix.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Globalization +{ + public partial class JapaneseCalendar : Calendar + { + private static EraInfo[] GetJapaneseEras() + { + if (GlobalizationMode.Invariant) + { + return null; + } + + string[] eraNames; + if (!CalendarData.EnumCalendarInfo("ja-JP", CalendarId.JAPAN, CalendarDataType.EraNames, out eraNames)) + { + return null; + } + + string[] abbrevEnglishEraNames; + if (!CalendarData.EnumCalendarInfo("en", CalendarId.JAPAN, CalendarDataType.AbbrevEraNames, out abbrevEnglishEraNames)) + { + return null; + } + + List eras = new List(); + int lastMaxYear = GregorianCalendar.MaxYear; + + int latestEra = Interop.GlobalizationInterop.GetLatestJapaneseEra(); + for (int i = latestEra; i >= 0; i--) + { + DateTime dt; + if (!GetJapaneseEraStartDate(i, out dt)) + { + return null; + } + + if (dt < JapaneseCalendar.calendarMinValue) + { + // only populate the Eras that are valid JapaneseCalendar date times + break; + } + + eras.Add(new EraInfo(i, dt.Year, dt.Month, dt.Day, dt.Year - 1, 1, lastMaxYear - dt.Year + 1, + eraNames[i], GetAbbreviatedEraName(eraNames, i), abbrevEnglishEraNames[i])); + + lastMaxYear = dt.Year; + } + + // remap the Era numbers, now that we know how many there will be + for (int i = 0; i < eras.Count; i++) + { + eras[i].era = eras.Count - i; + } + + return eras.ToArray(); + } + + // PAL Layer ends here + + private static string GetAbbreviatedEraName(string[] eraNames, int eraIndex) + { + // This matches the behavior on Win32 - only returning the first character of the era name. + // See Calendar.EraAsString(Int32) - https://msdn.microsoft.com/en-us/library/windows/apps/br206751.aspx + return eraNames[eraIndex].Substring(0, 1); + } + + private static bool GetJapaneseEraStartDate(int era, out DateTime dateTime) + { + Debug.Assert(!GlobalizationMode.Invariant); + + dateTime = default(DateTime); + + int startYear; + int startMonth; + int startDay; + bool result = Interop.GlobalizationInterop.GetJapaneseEraStartDate( + era, + out startYear, + out startMonth, + out startDay); + + if (result) + { + dateTime = new DateTime(startYear, startMonth, startDay); + } + + return result; + } + } +} diff --git a/src/mscorlib/shared/System/Globalization/JapaneseCalendar.Win32.cs b/src/mscorlib/shared/System/Globalization/JapaneseCalendar.Win32.cs new file mode 100644 index 0000000000..a83c4fad9e --- /dev/null +++ b/src/mscorlib/shared/System/Globalization/JapaneseCalendar.Win32.cs @@ -0,0 +1,209 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +using Microsoft.Win32; + +namespace System.Globalization +{ + public partial class JapaneseCalendar : Calendar + { + private const string c_japaneseErasHive = @"System\CurrentControlSet\Control\Nls\Calendars\Japanese\Eras"; + private const string c_japaneseErasHivePermissionList = @"HKEY_LOCAL_MACHINE\" + c_japaneseErasHive; + + // We know about 4 built-in eras, however users may add additional era(s) from the + // registry, by adding values to HKLM\SYSTEM\CurrentControlSet\Control\Nls\Calendars\Japanese\Eras + // + // Registry values look like: + // yyyy.mm.dd=era_abbrev_english_englishabbrev + // + // Where yyyy.mm.dd is the registry value name, and also the date of the era start. + // yyyy, mm, and dd are the year, month & day the era begins (4, 2 & 2 digits long) + // era is the Japanese Era name + // abbrev is the Abbreviated Japanese Era Name + // english is the English name for the Era (unused) + // englishabbrev is the Abbreviated English name for the era. + // . is a delimiter, but the value of . doesn't matter. + // '_' marks the space between the japanese era name, japanese abbreviated era name + // english name, and abbreviated english names. + private static EraInfo[] GetJapaneseEras() + { + // Look in the registry key and see if we can find any ranges + int iFoundEras = 0; + EraInfo[] registryEraRanges = null; + + try + { + // Need to access registry + RegistryKey key = RegistryKey.GetBaseKey(RegistryKey.HKEY_LOCAL_MACHINE).OpenSubKey(c_japaneseErasHive, false); + + // Abort if we didn't find anything + if (key == null) return null; + + // Look up the values in our reg key + String[] valueNames = key.GetValueNames(); + if (valueNames != null && valueNames.Length > 0) + { + registryEraRanges = new EraInfo[valueNames.Length]; + + // Loop through the registry and read in all the values + for (int i = 0; i < valueNames.Length; i++) + { + // See if the era is a valid date + EraInfo era = GetEraFromValue(valueNames[i], key.GetValue(valueNames[i]).ToString()); + + // continue if not valid + if (era == null) continue; + + // Remember we found one. + registryEraRanges[iFoundEras] = era; + iFoundEras++; + } + } + } + catch (System.Security.SecurityException) + { + // If we weren't allowed to read, then just ignore the error + return null; + } + catch (System.IO.IOException) + { + // If key is being deleted just ignore the error + return null; + } + catch (System.UnauthorizedAccessException) + { + // Registry access rights permissions, just ignore the error + return null; + } + + // + // If we didn't have valid eras, then fail + // should have at least 4 eras + // + if (iFoundEras < 4) return null; + + // + // Now we have eras, clean them up. + // + // Clean up array length + Array.Resize(ref registryEraRanges, iFoundEras); + + // Sort them + Array.Sort(registryEraRanges, CompareEraRanges); + + // Clean up era information + for (int i = 0; i < registryEraRanges.Length; i++) + { + // eras count backwards from length to 1 (and are 1 based indexes into string arrays) + registryEraRanges[i].era = registryEraRanges.Length - i; + + // update max era year + if (i == 0) + { + // First range is 'til the end of the calendar + registryEraRanges[0].maxEraYear = GregorianCalendar.MaxYear - registryEraRanges[0].yearOffset; + } + else + { + // Rest are until the next era (remember most recent era is first in array) + registryEraRanges[i].maxEraYear = registryEraRanges[i - 1].yearOffset + 1 - registryEraRanges[i].yearOffset; + } + } + + // Return our ranges + return registryEraRanges; + } + + // + // Compare two era ranges, eg just the ticks + // Remember the era array is supposed to be in reverse chronological order + // + private static int CompareEraRanges(EraInfo a, EraInfo b) + { + return b.ticks.CompareTo(a.ticks); + } + + // + // GetEraFromValue + // + // Parse the registry value name/data pair into an era + // + // Registry values look like: + // yyyy.mm.dd=era_abbrev_english_englishabbrev + // + // Where yyyy.mm.dd is the registry value name, and also the date of the era start. + // yyyy, mm, and dd are the year, month & day the era begins (4, 2 & 2 digits long) + // era is the Japanese Era name + // abbrev is the Abbreviated Japanese Era Name + // english is the English name for the Era (unused) + // englishabbrev is the Abbreviated English name for the era. + // . is a delimiter, but the value of . doesn't matter. + // '_' marks the space between the japanese era name, japanese abbreviated era name + // english name, and abbreviated english names. + private static EraInfo GetEraFromValue(String value, String data) + { + // Need inputs + if (value == null || data == null) return null; + + // + // Get Date + // + // Need exactly 10 characters in name for date + // yyyy.mm.dd although the . can be any character + if (value.Length != 10) return null; + + int year; + int month; + int day; + + if (!Int32.TryParse(value.Substring(0, 4), NumberStyles.None, NumberFormatInfo.InvariantInfo, out year) || + !Int32.TryParse(value.Substring(5, 2), NumberStyles.None, NumberFormatInfo.InvariantInfo, out month) || + !Int32.TryParse(value.Substring(8, 2), NumberStyles.None, NumberFormatInfo.InvariantInfo, out day)) + { + // Couldn't convert integer, fail + return null; + } + + // + // Get Strings + // + // Needs to be a certain length e_a_E_A at least (7 chars, exactly 4 groups) + String[] names = data.Split('_'); + + // Should have exactly 4 parts + // 0 - Era Name + // 1 - Abbreviated Era Name + // 2 - English Era Name + // 3 - Abbreviated English Era Name + if (names.Length != 4) return null; + + // Each part should have data in it + if (names[0].Length == 0 || + names[1].Length == 0 || + names[2].Length == 0 || + names[3].Length == 0) + return null; + + // + // Now we have an era we can build + // Note that the era # and max era year need cleaned up after sorting + // Don't use the full English Era Name (names[2]) + // + return new EraInfo(0, year, month, day, year - 1, 1, 0, + names[0], names[1], names[3]); + } + + // PAL Layer ends here + + private static string[] s_japaneseErasEnglishNames = new String[] { "M", "T", "S", "H" }; + + private static string GetJapaneseEnglishEraName(int era) + { + Debug.Assert(era > 0); + return era <= s_japaneseErasEnglishNames.Length ? s_japaneseErasEnglishNames[era - 1] : " "; + } + } +} diff --git a/src/mscorlib/shared/System/Globalization/JapaneseCalendar.WinRT.cs b/src/mscorlib/shared/System/Globalization/JapaneseCalendar.WinRT.cs new file mode 100644 index 0000000000..6a9df97200 --- /dev/null +++ b/src/mscorlib/shared/System/Globalization/JapaneseCalendar.WinRT.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +using Internal.Runtime.Augments; + +namespace System.Globalization +{ + public partial class JapaneseCalendar : Calendar + { + private static EraInfo[] GetJapaneseEras() + { + int erasCount = WinRTInterop.Callbacks.GetJapaneseEraCount(); + if (erasCount < 4) + { + return null; + } + + EraInfo[] eras = new EraInfo[erasCount]; + int lastMaxYear = GregorianCalendar.MaxYear; + + for (int i = erasCount; i > 0; i--) + { + DateTimeOffset dateOffset; + + string eraName; + string abbreviatedEraName; + + if (!GetJapaneseEraInfo(i, out dateOffset, out eraName, out abbreviatedEraName)) + { + return null; + } + + DateTime dt = new DateTime(dateOffset.Ticks); + + eras[erasCount - i] = new EraInfo(i, dt.Year, dt.Month, dt.Day, dt.Year - 1, 1, lastMaxYear - dt.Year + 1, + eraName, abbreviatedEraName, GetJapaneseEnglishEraName(i)); // era #4 start year/month/day, yearOffset, minEraYear + + lastMaxYear = dt.Year; + } + + return eras; + } + + // PAL Layer ends here + + private static string[] JapaneseErasEnglishNames = new String[] { "M", "T", "S", "H" }; + + private static string GetJapaneseEnglishEraName(int era) + { + Debug.Assert(era > 0); + return era <= JapaneseErasEnglishNames.Length ? JapaneseErasEnglishNames[era - 1] : " "; + } + + private static bool GetJapaneseEraInfo(int era, out DateTimeOffset dateOffset, out string eraName, out string abbreviatedEraName) + { + return WinRTInterop.Callbacks.GetJapaneseEraInfo(era, out dateOffset, out eraName, out abbreviatedEraName); + } + } +} diff --git a/src/mscorlib/shared/System/Globalization/JapaneseCalendar.cs b/src/mscorlib/shared/System/Globalization/JapaneseCalendar.cs index 4655e08a4e..0db1e6517a 100644 --- a/src/mscorlib/shared/System/Globalization/JapaneseCalendar.cs +++ b/src/mscorlib/shared/System/Globalization/JapaneseCalendar.cs @@ -36,7 +36,6 @@ namespace System.Globalization ============================================================================*/ - [Serializable] public partial class JapaneseCalendar : Calendar { internal static readonly DateTime calendarMinValue = new DateTime(1868, 9, 8); diff --git a/src/mscorlib/shared/System/Globalization/JapaneseLunisolarCalendar.cs b/src/mscorlib/shared/System/Globalization/JapaneseLunisolarCalendar.cs index 95e87f85d7..a90c4e8f21 100644 --- a/src/mscorlib/shared/System/Globalization/JapaneseLunisolarCalendar.cs +++ b/src/mscorlib/shared/System/Globalization/JapaneseLunisolarCalendar.cs @@ -14,7 +14,6 @@ namespace System.Globalization ** JapaneseLunisolar 1960/01/01 2049/12/29 */ - [Serializable] public class JapaneseLunisolarCalendar : EastAsianLunisolarCalendar { // diff --git a/src/mscorlib/shared/System/Globalization/JulianCalendar.cs b/src/mscorlib/shared/System/Globalization/JulianCalendar.cs index f4678c1a85..8d94290547 100644 --- a/src/mscorlib/shared/System/Globalization/JulianCalendar.cs +++ b/src/mscorlib/shared/System/Globalization/JulianCalendar.cs @@ -17,7 +17,6 @@ namespace System.Globalization //* Gregorian 0001/01/01 9999/12/31 //* Julia 0001/01/03 9999/10/19 - [Serializable] public class JulianCalendar : Calendar { public static readonly int JulianEra = 1; diff --git a/src/mscorlib/shared/System/Globalization/KoreanCalendar.cs b/src/mscorlib/shared/System/Globalization/KoreanCalendar.cs index b962b1c427..ef7495f07d 100644 --- a/src/mscorlib/shared/System/Globalization/KoreanCalendar.cs +++ b/src/mscorlib/shared/System/Globalization/KoreanCalendar.cs @@ -23,7 +23,6 @@ namespace System.Globalization ============================================================================*/ - [Serializable] public class KoreanCalendar : Calendar { // diff --git a/src/mscorlib/shared/System/Globalization/KoreanLunisolarCalendar.cs b/src/mscorlib/shared/System/Globalization/KoreanLunisolarCalendar.cs index d4c71632aa..8364532c9e 100644 --- a/src/mscorlib/shared/System/Globalization/KoreanLunisolarCalendar.cs +++ b/src/mscorlib/shared/System/Globalization/KoreanLunisolarCalendar.cs @@ -14,7 +14,6 @@ namespace System.Globalization ** KoreanLunisolar 918/01/01 2050/13/29 */ - [Serializable] public class KoreanLunisolarCalendar : EastAsianLunisolarCalendar { // diff --git a/src/mscorlib/shared/System/Globalization/NumberFormatInfo.cs b/src/mscorlib/shared/System/Globalization/NumberFormatInfo.cs new file mode 100644 index 0000000000..9fea694cca --- /dev/null +++ b/src/mscorlib/shared/System/Globalization/NumberFormatInfo.cs @@ -0,0 +1,899 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.Contracts; +using System.Runtime.Serialization; +using System.Text; + +namespace System.Globalization +{ + // + // Property Default Description + // PositiveSign '+' Character used to indicate positive values. + // NegativeSign '-' Character used to indicate negative values. + // NumberDecimalSeparator '.' The character used as the decimal separator. + // NumberGroupSeparator ',' The character used to separate groups of + // digits to the left of the decimal point. + // NumberDecimalDigits 2 The default number of decimal places. + // NumberGroupSizes 3 The number of digits in each group to the + // left of the decimal point. + // NaNSymbol "NaN" The string used to represent NaN values. + // PositiveInfinitySymbol"Infinity" The string used to represent positive + // infinities. + // NegativeInfinitySymbol"-Infinity" The string used to represent negative + // infinities. + // + // + // + // Property Default Description + // CurrencyDecimalSeparator '.' The character used as the decimal + // separator. + // CurrencyGroupSeparator ',' The character used to separate groups + // of digits to the left of the decimal + // point. + // CurrencyDecimalDigits 2 The default number of decimal places. + // CurrencyGroupSizes 3 The number of digits in each group to + // the left of the decimal point. + // CurrencyPositivePattern 0 The format of positive values. + // CurrencyNegativePattern 0 The format of negative values. + // CurrencySymbol "$" String used as local monetary symbol. + // + + sealed public class NumberFormatInfo : IFormatProvider, ICloneable + { + // invariantInfo is constant irrespective of your current culture. + private static volatile NumberFormatInfo s_invariantInfo; + + // READTHIS READTHIS READTHIS + // This class has an exact mapping onto a native structure defined in COMNumber.cpp + // DO NOT UPDATE THIS WITHOUT UPDATING THAT STRUCTURE. IF YOU ADD BOOL, ADD THEM AT THE END. + // ALSO MAKE SURE TO UPDATE mscorlib.h in the VM directory to check field offsets. + // READTHIS READTHIS READTHIS + internal int[] numberGroupSizes = new int[] { 3 }; + internal int[] currencyGroupSizes = new int[] { 3 }; + internal int[] percentGroupSizes = new int[] { 3 }; + internal String positiveSign = "+"; + internal String negativeSign = "-"; + internal String numberDecimalSeparator = "."; + internal String numberGroupSeparator = ","; + internal String currencyGroupSeparator = ","; + internal String currencyDecimalSeparator = "."; + internal String currencySymbol = "\x00a4"; // U+00a4 is the symbol for International Monetary Fund. + internal String nanSymbol = "NaN"; + internal String positiveInfinitySymbol = "Infinity"; + internal String negativeInfinitySymbol = "-Infinity"; + internal String percentDecimalSeparator = "."; + internal String percentGroupSeparator = ","; + internal String percentSymbol = "%"; + internal String perMilleSymbol = "\u2030"; + + + [OptionalField(VersionAdded = 2)] + internal String[] nativeDigits = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + + internal int numberDecimalDigits = 2; + internal int currencyDecimalDigits = 2; + internal int currencyPositivePattern = 0; + internal int currencyNegativePattern = 0; + internal int numberNegativePattern = 1; + internal int percentPositivePattern = 0; + internal int percentNegativePattern = 0; + internal int percentDecimalDigits = 2; + + [OptionalField(VersionAdded = 2)] + internal int digitSubstitution = (int)DigitShapes.None; + + internal bool isReadOnly = false; + + // Is this NumberFormatInfo for invariant culture? + + [OptionalField(VersionAdded = 2)] + internal bool m_isInvariant = false; + + public NumberFormatInfo() : this(null) + { + } + + [OnSerializing] + private void OnSerializing(StreamingContext ctx) { } + + [OnDeserializing] + private void OnDeserializing(StreamingContext ctx) { } + + [OnDeserialized] + private void OnDeserialized(StreamingContext ctx) { } + + private static void VerifyDecimalSeparator(String decSep, String propertyName) + { + if (decSep == null) + { + throw new ArgumentNullException(propertyName, + SR.ArgumentNull_String); + } + + if (decSep.Length == 0) + { + throw new ArgumentException(SR.Argument_EmptyDecString); + } + Contract.EndContractBlock(); + } + + private static void VerifyGroupSeparator(String groupSep, String propertyName) + { + if (groupSep == null) + { + throw new ArgumentNullException(propertyName, + SR.ArgumentNull_String); + } + Contract.EndContractBlock(); + } + + private static void VerifyNativeDigits(string[] nativeDig, string propertyName) + { + if (nativeDig == null) + { + throw new ArgumentNullException(propertyName, SR.ArgumentNull_Array); + } + + if (nativeDig.Length != 10) + { + throw new ArgumentException(SR.Argument_InvalidNativeDigitCount, propertyName); + } + Contract.EndContractBlock(); + + for (int i = 0; i < nativeDig.Length; i++) + { + if (nativeDig[i] == null) + { + throw new ArgumentNullException(propertyName, SR.ArgumentNull_ArrayValue); + } + + if (nativeDig[i].Length != 1) + { + if (nativeDig[i].Length != 2) + { + // Not 1 or 2 UTF-16 code points + throw new ArgumentException(SR.Argument_InvalidNativeDigitValue, propertyName); + } + else if (!char.IsSurrogatePair(nativeDig[i][0], nativeDig[i][1])) + { + // 2 UTF-6 code points, but not a surrogate pair + throw new ArgumentException(SR.Argument_InvalidNativeDigitValue, propertyName); + } + } + + if (CharUnicodeInfo.GetDecimalDigitValue(nativeDig[i], 0) != i && + CharUnicodeInfo.GetUnicodeCategory(nativeDig[i], 0) != UnicodeCategory.PrivateUse) + { + // Not the appropriate digit according to the Unicode data properties + // (Digit 0 must be a 0, etc.). + throw new ArgumentException(SR.Argument_InvalidNativeDigitValue, propertyName); + } + } + } + + private static void VerifyDigitSubstitution(DigitShapes digitSub, string propertyName) + { + switch (digitSub) + { + case DigitShapes.Context: + case DigitShapes.None: + case DigitShapes.NativeNational: + // Success. + break; + + default: + throw new ArgumentException(SR.Argument_InvalidDigitSubstitution, propertyName); + } + } + + internal NumberFormatInfo(CultureData cultureData) + { + if (cultureData != null) + { + // We directly use fields here since these data is coming from data table or Win32, so we + // don't need to verify their values (except for invalid parsing situations). + cultureData.GetNFIValues(this); + + if (cultureData.IsInvariantCulture) + { + // For invariant culture + this.m_isInvariant = true; + } + } + } + + [Pure] + private void VerifyWritable() + { + if (isReadOnly) + { + throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); + } + Contract.EndContractBlock(); + } + + // Returns a default NumberFormatInfo that will be universally + // supported and constant irrespective of the current culture. + // Used by FromString methods. + // + + public static NumberFormatInfo InvariantInfo + { + get + { + if (s_invariantInfo == null) + { + // Lazy create the invariant info. This cannot be done in a .cctor because exceptions can + // be thrown out of a .cctor stack that will need this. + NumberFormatInfo nfi = new NumberFormatInfo(); + nfi.m_isInvariant = true; + s_invariantInfo = ReadOnly(nfi); + } + return s_invariantInfo; + } + } + + public static NumberFormatInfo GetInstance(IFormatProvider formatProvider) + { + // Fast case for a regular CultureInfo + NumberFormatInfo info; + CultureInfo cultureProvider = formatProvider as CultureInfo; + if (cultureProvider != null && !cultureProvider._isInherited) + { + info = cultureProvider.numInfo; + if (info != null) + { + return info; + } + else + { + return cultureProvider.NumberFormat; + } + } + // Fast case for an NFI; + info = formatProvider as NumberFormatInfo; + if (info != null) + { + return info; + } + if (formatProvider != null) + { + info = formatProvider.GetFormat(typeof(NumberFormatInfo)) as NumberFormatInfo; + if (info != null) + { + return info; + } + } + return CurrentInfo; + } + + + + public Object Clone() + { + NumberFormatInfo n = (NumberFormatInfo)MemberwiseClone(); + n.isReadOnly = false; + return n; + } + + + public int CurrencyDecimalDigits + { + get { return currencyDecimalDigits; } + set + { + if (value < 0 || value > 99) + { + throw new ArgumentOutOfRangeException( + nameof(CurrencyDecimalDigits), + String.Format( + CultureInfo.CurrentCulture, + SR.ArgumentOutOfRange_Range, + 0, + 99)); + } + Contract.EndContractBlock(); + VerifyWritable(); + currencyDecimalDigits = value; + } + } + + + public String CurrencyDecimalSeparator + { + get { return currencyDecimalSeparator; } + set + { + VerifyWritable(); + VerifyDecimalSeparator(value, nameof(CurrencyDecimalSeparator)); + currencyDecimalSeparator = value; + } + } + + + public bool IsReadOnly + { + get + { + return isReadOnly; + } + } + + // + // Check the values of the groupSize array. + // + // Every element in the groupSize array should be between 1 and 9 + // excpet the last element could be zero. + // + internal static void CheckGroupSize(String propName, int[] groupSize) + { + for (int i = 0; i < groupSize.Length; i++) + { + if (groupSize[i] < 1) + { + if (i == groupSize.Length - 1 && groupSize[i] == 0) + return; + throw new ArgumentException(SR.Argument_InvalidGroupSize, propName); + } + else if (groupSize[i] > 9) + { + throw new ArgumentException(SR.Argument_InvalidGroupSize, propName); + } + } + } + + + public int[] CurrencyGroupSizes + { + get + { + return ((int[])currencyGroupSizes.Clone()); + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(CurrencyGroupSizes), + SR.ArgumentNull_Obj); + } + Contract.EndContractBlock(); + VerifyWritable(); + + Int32[] inputSizes = (Int32[])value.Clone(); + CheckGroupSize(nameof(CurrencyGroupSizes), inputSizes); + currencyGroupSizes = inputSizes; + } + } + + + + public int[] NumberGroupSizes + { + get + { + return ((int[])numberGroupSizes.Clone()); + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(NumberGroupSizes), + SR.ArgumentNull_Obj); + } + Contract.EndContractBlock(); + VerifyWritable(); + + Int32[] inputSizes = (Int32[])value.Clone(); + CheckGroupSize(nameof(NumberGroupSizes), inputSizes); + numberGroupSizes = inputSizes; + } + } + + + public int[] PercentGroupSizes + { + get + { + return ((int[])percentGroupSizes.Clone()); + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(PercentGroupSizes), + SR.ArgumentNull_Obj); + } + Contract.EndContractBlock(); + VerifyWritable(); + Int32[] inputSizes = (Int32[])value.Clone(); + CheckGroupSize(nameof(PercentGroupSizes), inputSizes); + percentGroupSizes = inputSizes; + } + } + + + public String CurrencyGroupSeparator + { + get { return currencyGroupSeparator; } + set + { + VerifyWritable(); + VerifyGroupSeparator(value, nameof(CurrencyGroupSeparator)); + currencyGroupSeparator = value; + } + } + + + public String CurrencySymbol + { + get { return currencySymbol; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(CurrencySymbol), + SR.ArgumentNull_String); + } + Contract.EndContractBlock(); + VerifyWritable(); + currencySymbol = value; + } + } + + // Returns the current culture's NumberFormatInfo. Used by Parse methods. + // + + public static NumberFormatInfo CurrentInfo + { + get + { + System.Globalization.CultureInfo culture = CultureInfo.CurrentCulture; + if (!culture._isInherited) + { + NumberFormatInfo info = culture.numInfo; + if (info != null) + { + return info; + } + } + return ((NumberFormatInfo)culture.GetFormat(typeof(NumberFormatInfo))); + } + } + + + public String NaNSymbol + { + get + { + return nanSymbol; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(NaNSymbol), + SR.ArgumentNull_String); + } + Contract.EndContractBlock(); + VerifyWritable(); + nanSymbol = value; + } + } + + + + public int CurrencyNegativePattern + { + get { return currencyNegativePattern; } + set + { + if (value < 0 || value > 15) + { + throw new ArgumentOutOfRangeException( + nameof(CurrencyNegativePattern), + String.Format( + CultureInfo.CurrentCulture, + SR.ArgumentOutOfRange_Range, + 0, + 15)); + } + Contract.EndContractBlock(); + VerifyWritable(); + currencyNegativePattern = value; + } + } + + + public int NumberNegativePattern + { + get { return numberNegativePattern; } + set + { + // + // NOTENOTE: the range of value should correspond to negNumberFormats[] in vm\COMNumber.cpp. + // + if (value < 0 || value > 4) + { + throw new ArgumentOutOfRangeException( + nameof(NumberNegativePattern), + String.Format( + CultureInfo.CurrentCulture, + SR.ArgumentOutOfRange_Range, + 0, + 4)); + } + Contract.EndContractBlock(); + VerifyWritable(); + numberNegativePattern = value; + } + } + + + public int PercentPositivePattern + { + get { return percentPositivePattern; } + set + { + // + // NOTENOTE: the range of value should correspond to posPercentFormats[] in vm\COMNumber.cpp. + // + if (value < 0 || value > 3) + { + throw new ArgumentOutOfRangeException( + nameof(PercentPositivePattern), + String.Format( + CultureInfo.CurrentCulture, + SR.ArgumentOutOfRange_Range, + 0, + 3)); + } + Contract.EndContractBlock(); + VerifyWritable(); + percentPositivePattern = value; + } + } + + + public int PercentNegativePattern + { + get { return percentNegativePattern; } + set + { + // + // NOTENOTE: the range of value should correspond to posPercentFormats[] in vm\COMNumber.cpp. + // + if (value < 0 || value > 11) + { + throw new ArgumentOutOfRangeException( + nameof(PercentNegativePattern), + String.Format( + CultureInfo.CurrentCulture, + SR.ArgumentOutOfRange_Range, + 0, + 11)); + } + Contract.EndContractBlock(); + VerifyWritable(); + percentNegativePattern = value; + } + } + + + public String NegativeInfinitySymbol + { + get + { + return negativeInfinitySymbol; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(NegativeInfinitySymbol), + SR.ArgumentNull_String); + } + Contract.EndContractBlock(); + VerifyWritable(); + negativeInfinitySymbol = value; + } + } + + + public String NegativeSign + { + get { return negativeSign; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(NegativeSign), + SR.ArgumentNull_String); + } + Contract.EndContractBlock(); + VerifyWritable(); + negativeSign = value; + } + } + + + public int NumberDecimalDigits + { + get { return numberDecimalDigits; } + set + { + if (value < 0 || value > 99) + { + throw new ArgumentOutOfRangeException( + nameof(NumberDecimalDigits), + String.Format( + CultureInfo.CurrentCulture, + SR.ArgumentOutOfRange_Range, + 0, + 99)); + } + Contract.EndContractBlock(); + VerifyWritable(); + numberDecimalDigits = value; + } + } + + + public String NumberDecimalSeparator + { + get { return numberDecimalSeparator; } + set + { + VerifyWritable(); + VerifyDecimalSeparator(value, nameof(NumberDecimalSeparator)); + numberDecimalSeparator = value; + } + } + + + public String NumberGroupSeparator + { + get { return numberGroupSeparator; } + set + { + VerifyWritable(); + VerifyGroupSeparator(value, nameof(NumberGroupSeparator)); + numberGroupSeparator = value; + } + } + + + public int CurrencyPositivePattern + { + get { return currencyPositivePattern; } + set + { + if (value < 0 || value > 3) + { + throw new ArgumentOutOfRangeException( + nameof(CurrencyPositivePattern), + String.Format( + CultureInfo.CurrentCulture, + SR.ArgumentOutOfRange_Range, + 0, + 3)); + } + Contract.EndContractBlock(); + VerifyWritable(); + currencyPositivePattern = value; + } + } + + + public String PositiveInfinitySymbol + { + get + { + return positiveInfinitySymbol; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(PositiveInfinitySymbol), + SR.ArgumentNull_String); + } + Contract.EndContractBlock(); + VerifyWritable(); + positiveInfinitySymbol = value; + } + } + + + public String PositiveSign + { + get { return positiveSign; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(PositiveSign), + SR.ArgumentNull_String); + } + Contract.EndContractBlock(); + VerifyWritable(); + positiveSign = value; + } + } + + + public int PercentDecimalDigits + { + get { return percentDecimalDigits; } + set + { + if (value < 0 || value > 99) + { + throw new ArgumentOutOfRangeException( + nameof(PercentDecimalDigits), + String.Format( + CultureInfo.CurrentCulture, + SR.ArgumentOutOfRange_Range, + 0, + 99)); + } + Contract.EndContractBlock(); + VerifyWritable(); + percentDecimalDigits = value; + } + } + + + public String PercentDecimalSeparator + { + get { return percentDecimalSeparator; } + set + { + VerifyWritable(); + VerifyDecimalSeparator(value, nameof(PercentDecimalSeparator)); + percentDecimalSeparator = value; + } + } + + + public String PercentGroupSeparator + { + get { return percentGroupSeparator; } + set + { + VerifyWritable(); + VerifyGroupSeparator(value, nameof(PercentGroupSeparator)); + percentGroupSeparator = value; + } + } + + + public String PercentSymbol + { + get + { + return percentSymbol; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(PercentSymbol), + SR.ArgumentNull_String); + } + Contract.EndContractBlock(); + VerifyWritable(); + percentSymbol = value; + } + } + + + public String PerMilleSymbol + { + get { return perMilleSymbol; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(PerMilleSymbol), + SR.ArgumentNull_String); + } + Contract.EndContractBlock(); + VerifyWritable(); + perMilleSymbol = value; + } + } + + public string[] NativeDigits + { + get { return (String[])nativeDigits.Clone(); } + set + { + VerifyWritable(); + VerifyNativeDigits(value, nameof(NativeDigits)); + nativeDigits = value; + } + } + + public DigitShapes DigitSubstitution + { + get { return (DigitShapes)digitSubstitution; } + set + { + VerifyWritable(); + VerifyDigitSubstitution(value, nameof(DigitSubstitution)); + digitSubstitution = (int)value; + } + } + + public Object GetFormat(Type formatType) + { + return formatType == typeof(NumberFormatInfo) ? this : null; + } + + public static NumberFormatInfo ReadOnly(NumberFormatInfo nfi) + { + if (nfi == null) + { + throw new ArgumentNullException(nameof(nfi)); + } + Contract.EndContractBlock(); + if (nfi.IsReadOnly) + { + return (nfi); + } + NumberFormatInfo info = (NumberFormatInfo)(nfi.MemberwiseClone()); + info.isReadOnly = true; + return info; + } + + // private const NumberStyles InvalidNumberStyles = unchecked((NumberStyles) 0xFFFFFC00); + private const NumberStyles InvalidNumberStyles = ~(NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite + | NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingSign + | NumberStyles.AllowParentheses | NumberStyles.AllowDecimalPoint + | NumberStyles.AllowThousands | NumberStyles.AllowExponent + | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier); + + internal static void ValidateParseStyleInteger(NumberStyles style) + { + // Check for undefined flags + if ((style & InvalidNumberStyles) != 0) + { + throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style)); + } + Contract.EndContractBlock(); + if ((style & NumberStyles.AllowHexSpecifier) != 0) + { // Check for hex number + if ((style & ~NumberStyles.HexNumber) != 0) + { + throw new ArgumentException(SR.Arg_InvalidHexStyle); + } + } + } + + internal static void ValidateParseStyleFloatingPoint(NumberStyles style) + { + // Check for undefined flags + if ((style & InvalidNumberStyles) != 0) + { + throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style)); + } + Contract.EndContractBlock(); + if ((style & NumberStyles.AllowHexSpecifier) != 0) + { // Check for hex number + throw new ArgumentException(SR.Arg_HexStyleNotSupported); + } + } + } // NumberFormatInfo +} + + + + + + + + + diff --git a/src/mscorlib/shared/System/Globalization/PersianCalendar.cs b/src/mscorlib/shared/System/Globalization/PersianCalendar.cs index 445bbd6d0c..78a081e1b9 100644 --- a/src/mscorlib/shared/System/Globalization/PersianCalendar.cs +++ b/src/mscorlib/shared/System/Globalization/PersianCalendar.cs @@ -19,7 +19,6 @@ namespace System.Globalization ** Persian 0001/01/01 9378/10/13 */ - [Serializable] public class PersianCalendar : Calendar { public static readonly int PersianEra = 1; diff --git a/src/mscorlib/shared/System/Globalization/SortKey.cs b/src/mscorlib/shared/System/Globalization/SortKey.cs new file mode 100644 index 0000000000..d65e097cb4 --- /dev/null +++ b/src/mscorlib/shared/System/Globalization/SortKey.cs @@ -0,0 +1,202 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +//////////////////////////////////////////////////////////////////////////// +// +// +// Purpose: This class implements a set of methods for retrieving +// sort key information. +// +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Diagnostics; +using System.Diagnostics.Contracts; + +namespace System.Globalization +{ + public partial class SortKey + { + //--------------------------------------------------------------------// + // Internal Information // + //--------------------------------------------------------------------// + + [OptionalField(VersionAdded = 3)] + internal string _localeName; // locale identifier + + [OptionalField(VersionAdded = 1)] // LCID field so serialization is Whidbey compatible though we don't officially support it + internal int _win32LCID; + + internal CompareOptions _options; // options + internal string _string; // original string + internal byte[] _keyData; // sortkey data + + // + // The following constructor is designed to be called from CompareInfo to get the + // the sort key of specific string for synthetic culture + // + internal SortKey(String localeName, String str, CompareOptions options, byte[] keyData) + { + _keyData = keyData; + _localeName = localeName; + _options = options; + _string = str; + } + + [OnSerializing] + private void OnSerializing(StreamingContext context) + { + //set LCID to proper value for Whidbey serialization (no other use) + if (_win32LCID == 0) + { + _win32LCID = CultureInfo.GetCultureInfo(_localeName).LCID; + } + } + + [OnDeserialized] + private void OnDeserialized(StreamingContext context) + { + //set locale name to proper value after Whidbey deserialization + if (String.IsNullOrEmpty(_localeName) && _win32LCID != 0) + { + _localeName = CultureInfo.GetCultureInfo(_win32LCID).Name; + } + } + + //////////////////////////////////////////////////////////////////////// + // + // GetOriginalString + // + // Returns the original string used to create the current instance + // of SortKey. + // + //////////////////////////////////////////////////////////////////////// + public virtual String OriginalString + { + get + { + return (_string); + } + } + + //////////////////////////////////////////////////////////////////////// + // + // GetKeyData + // + // Returns a byte array representing the current instance of the + // sort key. + // + //////////////////////////////////////////////////////////////////////// + public virtual byte[] KeyData + { + get + { + return (byte[])(_keyData.Clone()); + } + } + + //////////////////////////////////////////////////////////////////////// + // + // Compare + // + // Compares the two sort keys. Returns 0 if the two sort keys are + // equal, a number less than 0 if sortkey1 is less than sortkey2, + // and a number greater than 0 if sortkey1 is greater than sortkey2. + // + //////////////////////////////////////////////////////////////////////// + public static int Compare(SortKey sortkey1, SortKey sortkey2) + { + if (sortkey1 == null || sortkey2 == null) + { + throw new ArgumentNullException((sortkey1 == null ? nameof(sortkey1) : nameof(sortkey2))); + } + Contract.EndContractBlock(); + + byte[] key1Data = sortkey1._keyData; + byte[] key2Data = sortkey2._keyData; + + Debug.Assert(key1Data != null, "key1Data != null"); + Debug.Assert(key2Data != null, "key2Data != null"); + + if (key1Data.Length == 0) + { + if (key2Data.Length == 0) + { + return (0); + } + return (-1); + } + if (key2Data.Length == 0) + { + return (1); + } + + int compLen = (key1Data.Length < key2Data.Length) ? key1Data.Length : key2Data.Length; + + for (int i = 0; i < compLen; i++) + { + if (key1Data[i] > key2Data[i]) + { + return (1); + } + if (key1Data[i] < key2Data[i]) + { + return (-1); + } + } + + return 0; + } + + //////////////////////////////////////////////////////////////////////// + // + // Equals + // + // Implements Object.Equals(). Returns a boolean indicating whether + // or not object refers to the same SortKey as the current instance. + // + //////////////////////////////////////////////////////////////////////// + public override bool Equals(Object value) + { + SortKey that = value as SortKey; + + if (that != null) + { + return Compare(this, that) == 0; + } + + return (false); + } + + //////////////////////////////////////////////////////////////////////// + // + // GetHashCode + // + // Implements Object.GetHashCode(). Returns the hash code for the + // SortKey. The hash code is guaranteed to be the same for + // SortKey A and B where A.Equals(B) is true. + // + //////////////////////////////////////////////////////////////////////// + public override int GetHashCode() + { + return (CompareInfo.GetCompareInfo(_localeName).GetHashCodeOfString(_string, _options)); + } + + //////////////////////////////////////////////////////////////////////// + // + // ToString + // + // Implements Object.ToString(). Returns a string describing the + // SortKey. + // + //////////////////////////////////////////////////////////////////////// + public override String ToString() + { + return ("SortKey - " + _localeName + ", " + _options + ", " + _string); + } + } +} diff --git a/src/mscorlib/shared/System/Globalization/SortVersion.cs b/src/mscorlib/shared/System/Globalization/SortVersion.cs index a7aef6d84b..94c04d7063 100644 --- a/src/mscorlib/shared/System/Globalization/SortVersion.cs +++ b/src/mscorlib/shared/System/Globalization/SortVersion.cs @@ -7,14 +7,14 @@ namespace System.Globalization [Serializable] public sealed class SortVersion : IEquatable { - private int _nlsVersion; - private Guid _sortId; + private int m_NlsVersion; // Do not rename (binary serialization) + private Guid m_SortId; // Do not rename (binary serialization) public int FullVersion { get { - return _nlsVersion; + return m_NlsVersion; } } @@ -22,19 +22,19 @@ namespace System.Globalization { get { - return _sortId; + return m_SortId; } } public SortVersion(int fullVersion, Guid sortId) { - _sortId = sortId; - _nlsVersion = fullVersion; + m_SortId = sortId; + m_NlsVersion = fullVersion; } internal SortVersion(int nlsVersion, int effectiveId, Guid customVersion) { - _nlsVersion = nlsVersion; + m_NlsVersion = nlsVersion; if (customVersion == Guid.Empty) { @@ -45,7 +45,7 @@ namespace System.Globalization customVersion = new Guid(0, 0, 0, 0, 0, 0, 0, b1, b2, b3, b4); } - _sortId = customVersion; + m_SortId = customVersion; } public override bool Equals(object obj) @@ -66,12 +66,12 @@ namespace System.Globalization return false; } - return _nlsVersion == other._nlsVersion && _sortId == other._sortId; + return m_NlsVersion == other.m_NlsVersion && m_SortId == other.m_SortId; } public override int GetHashCode() { - return _nlsVersion * 7 | _sortId.GetHashCode(); + return m_NlsVersion * 7 | m_SortId.GetHashCode(); } public static bool operator ==(SortVersion left, SortVersion right) diff --git a/src/mscorlib/shared/System/Globalization/StringInfo.cs b/src/mscorlib/shared/System/Globalization/StringInfo.cs new file mode 100644 index 0000000000..87d1b9f684 --- /dev/null +++ b/src/mscorlib/shared/System/Globalization/StringInfo.cs @@ -0,0 +1,373 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +//////////////////////////////////////////////////////////////////////////// +// +// +// Purpose: This class defines behaviors specific to a writing system. +// A writing system is the collection of scripts and +// orthographic rules required to represent a language as text. +// +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Runtime.Serialization; + +namespace System.Globalization +{ + public class StringInfo + { + [OptionalField(VersionAdded = 2)] + private string _str; + + [NonSerialized] + private int[] _indexes; + + // Legacy constructor + public StringInfo() : this("") { } + + // Primary, useful constructor + public StringInfo(string value) + { + this.String = value; + } + + [OnDeserializing] + private void OnDeserializing(StreamingContext ctx) + { + _str = String.Empty; + } + + [OnDeserialized] + private void OnDeserialized(StreamingContext ctx) + { + if (_str.Length == 0) + { + _indexes = null; + } + } + + public override bool Equals(Object value) + { + StringInfo that = value as StringInfo; + if (that != null) + { + return (_str.Equals(that._str)); + } + return (false); + } + + public override int GetHashCode() + { + return _str.GetHashCode(); + } + + + // Our zero-based array of index values into the string. Initialize if + // our private array is not yet, in fact, initialized. + private int[] Indexes + { + get + { + if ((null == _indexes) && (0 < this.String.Length)) + { + _indexes = StringInfo.ParseCombiningCharacters(this.String); + } + + return (_indexes); + } + } + + public string String + { + get + { + return (_str); + } + set + { + if (null == value) + { + throw new ArgumentNullException(nameof(String), + SR.ArgumentNull_String); + } + Contract.EndContractBlock(); + + _str = value; + _indexes = null; + } + } + + public int LengthInTextElements + { + get + { + if (null == this.Indexes) + { + // Indexes not initialized, so assume length zero + return (0); + } + + return (this.Indexes.Length); + } + } + + public string SubstringByTextElements(int startingTextElement) + { + // If the string is empty, no sense going further. + if (null == this.Indexes) + { + // Just decide which error to give depending on the param they gave us.... + if (startingTextElement < 0) + { + throw new ArgumentOutOfRangeException(nameof(startingTextElement), SR.ArgumentOutOfRange_NeedPosNum); + } + else + { + throw new ArgumentOutOfRangeException(nameof(startingTextElement), SR.Arg_ArgumentOutOfRangeException); + } + } + return (SubstringByTextElements(startingTextElement, Indexes.Length - startingTextElement)); + } + + public string SubstringByTextElements(int startingTextElement, int lengthInTextElements) + { + if (startingTextElement < 0) + { + throw new ArgumentOutOfRangeException(nameof(startingTextElement), SR.ArgumentOutOfRange_NeedPosNum); + } + + if (this.String.Length == 0 || startingTextElement >= Indexes.Length) + { + throw new ArgumentOutOfRangeException(nameof(startingTextElement), SR.Arg_ArgumentOutOfRangeException); + } + + if (lengthInTextElements < 0) + { + throw new ArgumentOutOfRangeException(nameof(lengthInTextElements), SR.ArgumentOutOfRange_NeedPosNum); + } + + if (startingTextElement > Indexes.Length - lengthInTextElements) + { + throw new ArgumentOutOfRangeException(nameof(lengthInTextElements), SR.Arg_ArgumentOutOfRangeException); + } + + int start = Indexes[startingTextElement]; + + if (startingTextElement + lengthInTextElements == Indexes.Length) + { + // We are at the last text element in the string and because of that + // must handle the call differently. + return (this.String.Substring(start)); + } + else + { + return (this.String.Substring(start, (Indexes[lengthInTextElements + startingTextElement] - start))); + } + } + + public static string GetNextTextElement(string str) + { + return (GetNextTextElement(str, 0)); + } + + + //////////////////////////////////////////////////////////////////////// + // + // Get the code point count of the current text element. + // + // A combining class is defined as: + // A character/surrogate that has the following Unicode category: + // * NonSpacingMark (e.g. U+0300 COMBINING GRAVE ACCENT) + // * SpacingCombiningMark (e.g. U+ 0903 DEVANGARI SIGN VISARGA) + // * EnclosingMark (e.g. U+20DD COMBINING ENCLOSING CIRCLE) + // + // In the context of GetNextTextElement() and ParseCombiningCharacters(), a text element is defined as: + // + // 1. If a character/surrogate is in the following category, it is a text element. + // It can NOT further combine with characters in the combinging class to form a text element. + // * one of the Unicode category in the combinging class + // * UnicodeCategory.Format + // * UnicodeCateogry.Control + // * UnicodeCategory.OtherNotAssigned + // 2. Otherwise, the character/surrogate can be combined with characters in the combinging class to form a text element. + // + // Return: + // The length of the current text element + // + // Parameters: + // String str + // index The starting index + // len The total length of str (to define the upper boundary) + // ucCurrent The Unicode category pointed by Index. It will be updated to the uc of next character if this is not the last text element. + // currentCharCount The char count of an abstract char pointed by Index. It will be updated to the char count of next abstract character if this is not the last text element. + // + //////////////////////////////////////////////////////////////////////// + + internal static int GetCurrentTextElementLen(string str, int index, int len, ref UnicodeCategory ucCurrent, ref int currentCharCount) + { + Debug.Assert(index >= 0 && len >= 0, "StringInfo.GetCurrentTextElementLen() : index = " + index + ", len = " + len); + Debug.Assert(index < len, "StringInfo.GetCurrentTextElementLen() : index = " + index + ", len = " + len); + if (index + currentCharCount == len) + { + // This is the last character/surrogate in the string. + return (currentCharCount); + } + + // Call an internal GetUnicodeCategory, which will tell us both the unicode category, and also tell us if it is a surrogate pair or not. + int nextCharCount; + UnicodeCategory ucNext = CharUnicodeInfo.InternalGetUnicodeCategory(str, index + currentCharCount, out nextCharCount); + if (CharUnicodeInfo.IsCombiningCategory(ucNext)) + { + // The next element is a combining class. + // Check if the current text element to see if it is a valid base category (i.e. it should not be a combining category, + // not a format character, and not a control character). + + if (CharUnicodeInfo.IsCombiningCategory(ucCurrent) + || (ucCurrent == UnicodeCategory.Format) + || (ucCurrent == UnicodeCategory.Control) + || (ucCurrent == UnicodeCategory.OtherNotAssigned) + || (ucCurrent == UnicodeCategory.Surrogate)) // An unpair high surrogate or low surrogate + { + // Will fall thru and return the currentCharCount + } + else + { + int startIndex = index; // Remember the current index. + + // We have a valid base characters, and we have a character (or surrogate) that is combining. + // Check if there are more combining characters to follow. + // Check if the next character is a nonspacing character. + index += currentCharCount + nextCharCount; + + while (index < len) + { + ucNext = CharUnicodeInfo.InternalGetUnicodeCategory(str, index, out nextCharCount); + if (!CharUnicodeInfo.IsCombiningCategory(ucNext)) + { + ucCurrent = ucNext; + currentCharCount = nextCharCount; + break; + } + index += nextCharCount; + } + return (index - startIndex); + } + } + // The return value will be the currentCharCount. + int ret = currentCharCount; + ucCurrent = ucNext; + // Update currentCharCount. + currentCharCount = nextCharCount; + return (ret); + } + + // Returns the str containing the next text element in str starting at + // index index. If index is not supplied, then it will start at the beginning + // of str. It recognizes a base character plus one or more combining + // characters or a properly formed surrogate pair as a text element. See also + // the ParseCombiningCharacters() and the ParseSurrogates() methods. + public static string GetNextTextElement(string str, int index) + { + // + // Validate parameters. + // + if (str == null) + { + throw new ArgumentNullException(nameof(str)); + } + Contract.EndContractBlock(); + + int len = str.Length; + if (index < 0 || index >= len) + { + if (index == len) + { + return (String.Empty); + } + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index); + } + + int charLen; + UnicodeCategory uc = CharUnicodeInfo.InternalGetUnicodeCategory(str, index, out charLen); + return (str.Substring(index, GetCurrentTextElementLen(str, index, len, ref uc, ref charLen))); + } + + public static TextElementEnumerator GetTextElementEnumerator(string str) + { + return (GetTextElementEnumerator(str, 0)); + } + + public static TextElementEnumerator GetTextElementEnumerator(string str, int index) + { + // + // Validate parameters. + // + if (str == null) + { + throw new ArgumentNullException(nameof(str)); + } + Contract.EndContractBlock(); + + int len = str.Length; + if (index < 0 || (index > len)) + { + throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_Index); + } + + return (new TextElementEnumerator(str, index, len)); + } + + /* + * Returns the indices of each base character or properly formed surrogate pair + * within the str. It recognizes a base character plus one or more combining + * characters or a properly formed surrogate pair as a text element and returns + * the index of the base character or high surrogate. Each index is the + * beginning of a text element within a str. The length of each element is + * easily computed as the difference between successive indices. The length of + * the array will always be less than or equal to the length of the str. For + * example, given the str \u4f00\u302a\ud800\udc00\u4f01, this method would + * return the indices: 0, 2, 4. + */ + + public static int[] ParseCombiningCharacters(string str) + { + if (str == null) + { + throw new ArgumentNullException(nameof(str)); + } + Contract.EndContractBlock(); + + int len = str.Length; + int[] result = new int[len]; + if (len == 0) + { + return (result); + } + + int resultCount = 0; + + int i = 0; + int currentCharLen; + UnicodeCategory currentCategory = CharUnicodeInfo.InternalGetUnicodeCategory(str, 0, out currentCharLen); + + while (i < len) + { + result[resultCount++] = i; + i += GetCurrentTextElementLen(str, i, len, ref currentCategory, ref currentCharLen); + } + + if (resultCount < len) + { + int[] returnArray = new int[resultCount]; + Array.Copy(result, returnArray, resultCount); + return (returnArray); + } + return (result); + } + } +} diff --git a/src/mscorlib/shared/System/Globalization/TaiwanCalendar.cs b/src/mscorlib/shared/System/Globalization/TaiwanCalendar.cs index 2e735e0cb9..ec4188161a 100644 --- a/src/mscorlib/shared/System/Globalization/TaiwanCalendar.cs +++ b/src/mscorlib/shared/System/Globalization/TaiwanCalendar.cs @@ -21,7 +21,6 @@ namespace System.Globalization ** Taiwan 01/01/01 8088/12/31 ============================================================================*/ - [Serializable] public class TaiwanCalendar : Calendar { // diff --git a/src/mscorlib/shared/System/Globalization/TaiwanLunisolarCalendar.cs b/src/mscorlib/shared/System/Globalization/TaiwanLunisolarCalendar.cs index 8ba1f278e7..1e2ec62a71 100644 --- a/src/mscorlib/shared/System/Globalization/TaiwanLunisolarCalendar.cs +++ b/src/mscorlib/shared/System/Globalization/TaiwanLunisolarCalendar.cs @@ -15,7 +15,6 @@ namespace System.Globalization ** TaiwanLunisolar 1912/01/01 2050/13/29 */ - [Serializable] public class TaiwanLunisolarCalendar : EastAsianLunisolarCalendar { // Since diff --git a/src/mscorlib/shared/System/Globalization/ThaiBuddhistCalendar.cs b/src/mscorlib/shared/System/Globalization/ThaiBuddhistCalendar.cs index 9e6e30406c..e1646bfa8e 100644 --- a/src/mscorlib/shared/System/Globalization/ThaiBuddhistCalendar.cs +++ b/src/mscorlib/shared/System/Globalization/ThaiBuddhistCalendar.cs @@ -20,7 +20,6 @@ namespace System.Globalization ** Thai 0544/01/01 10542/12/31 ============================================================================*/ - [Serializable] public class ThaiBuddhistCalendar : Calendar { // Initialize our era info. diff --git a/src/mscorlib/shared/System/Globalization/UmAlQuraCalendar.cs b/src/mscorlib/shared/System/Globalization/UmAlQuraCalendar.cs index b7ba6d0112..c03ac23d95 100644 --- a/src/mscorlib/shared/System/Globalization/UmAlQuraCalendar.cs +++ b/src/mscorlib/shared/System/Globalization/UmAlQuraCalendar.cs @@ -15,7 +15,6 @@ namespace System.Globalization ** UmAlQura 1318/01/01 1500/12/30 */ - [Serializable] public partial class UmAlQuraCalendar : Calendar { internal const int MinCalendarYear = 1318; -- cgit v1.2.3