| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- // 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.Security;
- using System.Text;
- using Internal.Runtime.CompilerServices;
- 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);
- string leapHebrewMonthName = null;
- result &= EnumMonthNames(localeName, calendarId, CalendarDataType.MonthNames, out this.saMonthNames, ref leapHebrewMonthName);
- if (leapHebrewMonthName != null)
- {
- // In Hebrew calendar, get the leap month name Adar II and override the non-leap month 7
- Debug.Assert(calendarId == CalendarId.HEBREW && saMonthNames.Length == 13);
- saLeapYearMonthNames = (string[]) saMonthNames.Clone();
- saLeapYearMonthNames[6] = leapHebrewMonthName;
- // The returned data from ICU has 6th month name as 'Adar I' and 7th month name as 'Adar'
- // We need to adjust that in the list used with non-leap year to have 6th month as 'Adar' and 7th month as 'Adar II'
- // note that when formatting non-leap year dates, 7th month shouldn't get used at all.
- saMonthNames[5] = saMonthNames[6];
- saMonthNames[6] = leapHebrewMonthName;
- }
- result &= EnumMonthNames(localeName, calendarId, CalendarDataType.AbbrevMonthNames, out this.saAbbrevMonthNames, ref leapHebrewMonthName);
- result &= EnumMonthNames(localeName, calendarId, CalendarDataType.MonthGenitiveNames, out this.saMonthGenitiveNames, ref leapHebrewMonthName);
- result &= EnumMonthNames(localeName, calendarId, CalendarDataType.AbbrevMonthGenitiveNames, out this.saAbbrevMonthGenitiveNames, ref leapHebrewMonthName);
- 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.Globalization.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 unsafe bool GetCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, out string calendarString)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- return Interop.CallStringMethod(
- (buffer, locale, id, type) =>
- {
- fixed (char* bufferPtr = buffer)
- {
- return Interop.Globalization.GetCalendarInfo(locale, id, type, bufferPtr, buffer.Length);
- }
- },
- localeName,
- calendarId,
- dataType,
- out calendarString);
- }
- private static bool EnumDatePatterns(string localeName, CalendarId calendarId, CalendarDataType dataType, out string[] datePatterns)
- {
- datePatterns = null;
- EnumCalendarsData callbackContext = new EnumCalendarsData();
- callbackContext.Results = new List<string>();
- callbackContext.DisallowDuplicates = true;
- bool result = EnumCalendarInfo(localeName, calendarId, dataType, ref callbackContext);
- if (result)
- {
- List<string> datePatternsList = callbackContext.Results;
- for (int i = 0; i < datePatternsList.Count; i++)
- {
- datePatternsList[i] = NormalizeDatePattern(datePatternsList[i]);
- }
- if (dataType == CalendarDataType.ShortDates)
- FixDefaultShortDatePattern(datePatternsList);
- datePatterns = datePatternsList.ToArray();
- }
- return result;
- }
- // FixDefaultShortDatePattern will convert the default short date pattern from using 'yy' to using 'yyyy'
- // And will ensure the original pattern still exist in the list.
- // doing that will have the short date pattern format the year as 4-digit number and not just 2-digit number.
- // Example: June 5, 2018 will be formatted to something like 6/5/2018 instead of 6/5/18 fro en-US culture.
- private static void FixDefaultShortDatePattern(List<string> shortDatePatterns)
- {
- if (shortDatePatterns.Count == 0)
- return;
- string s = shortDatePatterns[0];
- // We are not expecting any pattern have length more than 100.
- // We have to do this check to prevent stack overflow as we allocate the buffer on the stack.
- if (s.Length > 100)
- return;
- Span<char> modifiedPattern = stackalloc char[s.Length + 2];
- int index = 0;
- while (index < s.Length)
- {
- if (s[index] == '\'')
- {
- do
- {
- modifiedPattern[index] = s[index];
- index++;
- } while (index < s.Length && s[index] != '\'');
- if (index >= s.Length)
- return;
- }
- else if (s[index] == 'y')
- {
- modifiedPattern[index] = 'y';
- break;
- }
- modifiedPattern[index] = s[index];
- index++;
- }
- if (index >= s.Length - 1 || s[index + 1] != 'y')
- {
- // not a 'yy' pattern
- return;
- }
- if (index + 2 < s.Length && s[index + 2] == 'y')
- {
- // we have 'yyy' then nothing to do
- return;
- }
- // we are sure now we have 'yy' pattern
- Debug.Assert(index + 3 < modifiedPattern.Length);
- modifiedPattern[index + 1] = 'y'; // second y
- modifiedPattern[index + 2] = 'y'; // third y
- modifiedPattern[index + 3] = 'y'; // fourth y
- index += 2;
- // Now, copy the rest of the pattern to the destination buffer
- while (index < s.Length)
- {
- modifiedPattern[index + 2] = s[index];
- index++;
- }
- shortDatePatterns[0] = modifiedPattern.ToString();
- for (int i = 1; i < shortDatePatterns.Count; i++)
- {
- if (shortDatePatterns[i] == shortDatePatterns[0])
- {
- // Found match in the list to the new constructed pattern, then replace it with the original modified pattern
- shortDatePatterns[i] = s;
- return;
- }
- }
- // if we come here means the newly constructed pattern not found on the list, then add the original pattern
- shortDatePatterns.Add(s);
- }
- /// <summary>
- /// 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.
- /// </summary>
- /// <remarks>
- /// 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
- /// </remarks>
- 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.Contains(input[index]),
- 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, ref string leapHebrewMonthName)
- {
- monthNames = null;
- EnumCalendarsData callbackContext = new EnumCalendarsData();
- callbackContext.Results = new List<string>();
- bool result = EnumCalendarInfo(localeName, calendarId, dataType, ref 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);
- }
- if (callbackContext.Results.Count > 13)
- {
- Debug.Assert(calendarId == CalendarId.HEBREW && callbackContext.Results.Count == 14);
-
- if (calendarId == CalendarId.HEBREW)
- {
- leapHebrewMonthName = callbackContext.Results[13];
- }
- callbackContext.Results.RemoveRange(13, callbackContext.Results.Count - 13);
- }
- 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;
- EnumCalendarsData callbackContext = new EnumCalendarsData();
- callbackContext.Results = new List<string>();
- bool result = EnumCalendarInfo(localeName, calendarId, dataType, ref callbackContext);
- if (result)
- {
- calendarData = callbackContext.Results.ToArray();
- }
- return result;
- }
- private static unsafe bool EnumCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, ref EnumCalendarsData callbackContext)
- {
- return Interop.Globalization.EnumCalendarInfo(EnumCalendarInfoCallback, localeName, calendarId, dataType, (IntPtr)Unsafe.AsPointer(ref callbackContext));
- }
- private static unsafe void EnumCalendarInfoCallback(string calendarString, IntPtr context)
- {
- try
- {
- ref EnumCalendarsData callbackContext = ref Unsafe.As<byte, EnumCalendarsData>(ref *(byte*)context);
- 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.Fail(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 struct EnumCalendarsData
- {
- public List<string> Results;
- public bool DisallowDuplicates;
- }
- }
- }
|