diff options
Diffstat (limited to 'src/mscorlib/src/System/Globalization/CalendarData.Windows.cs')
-rw-r--r-- | src/mscorlib/src/System/Globalization/CalendarData.Windows.cs | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Globalization/CalendarData.Windows.cs b/src/mscorlib/src/System/Globalization/CalendarData.Windows.cs new file mode 100644 index 0000000000..51a2727c7d --- /dev/null +++ b/src/mscorlib/src/System/Globalization/CalendarData.Windows.cs @@ -0,0 +1,502 @@ +// 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; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Diagnostics.Contracts; +using System.Collections.Generic; + +namespace System.Globalization +{ +#if CORECLR + using IntList = List<int>; + using StringList = List<string>; +#else + using IntList = LowLevelList<int>; + using StringList = LowLevelList<string>; +#endif + + internal partial class CalendarData + { + private bool LoadCalendarDataFromSystem(String localeName, CalendarId calendarId) + { + Debug.Assert(!GlobalizationMode.Invariant); + + bool ret = true; + + uint useOverrides = this.bUseUserOverrides ? 0 : CAL_NOUSEROVERRIDE; + + // + // Windows doesn't support some calendars right now, so remap those. + // + switch (calendarId) + { + case CalendarId.JAPANESELUNISOLAR: // Data looks like Japanese + calendarId = CalendarId.JAPAN; + break; + case CalendarId.JULIAN: // Data looks like gregorian US + case CalendarId.CHINESELUNISOLAR: // Algorithmic, so actual data is irrelevent + case CalendarId.SAKA: // reserved to match Office but not implemented in our code, so data is irrelevent + case CalendarId.LUNAR_ETO_CHN: // reserved to match Office but not implemented in our code, so data is irrelevent + case CalendarId.LUNAR_ETO_KOR: // reserved to match Office but not implemented in our code, so data is irrelevent + case CalendarId.LUNAR_ETO_ROKUYOU: // reserved to match Office but not implemented in our code, so data is irrelevent + case CalendarId.KOREANLUNISOLAR: // Algorithmic, so actual data is irrelevent + case CalendarId.TAIWANLUNISOLAR: // Algorithmic, so actual data is irrelevent + calendarId = CalendarId.GREGORIAN_US; + break; + } + + // + // Special handling for some special calendar due to OS limitation. + // This includes calendar like Taiwan calendar, UmAlQura calendar, etc. + // + CheckSpecialCalendar(ref calendarId, ref localeName); + + // Numbers + ret &= CallGetCalendarInfoEx(localeName, calendarId, CAL_ITWODIGITYEARMAX | useOverrides, out this.iTwoDigitYearMax); + + // Strings + ret &= CallGetCalendarInfoEx(localeName, calendarId, CAL_SCALNAME, out this.sNativeName); + ret &= CallGetCalendarInfoEx(localeName, calendarId, CAL_SMONTHDAY | useOverrides, out this.sMonthDay); + + // String Arrays + // Formats + ret &= CallEnumCalendarInfo(localeName, calendarId, CAL_SSHORTDATE, LOCALE_SSHORTDATE | useOverrides, out this.saShortDates); + ret &= CallEnumCalendarInfo(localeName, calendarId, CAL_SLONGDATE, LOCALE_SLONGDATE | useOverrides, out this.saLongDates); + + // Get the YearMonth pattern. + ret &= CallEnumCalendarInfo(localeName, calendarId, CAL_SYEARMONTH, LOCALE_SYEARMONTH, out this.saYearMonths); + + // Day & Month Names + // These are all single calType entries, 1 per day, so we have to make 7 or 13 calls to collect all the names + + // Day + // Note that we're off-by-one since managed starts on sunday and windows starts on monday + ret &= GetCalendarDayInfo(localeName, calendarId, CAL_SDAYNAME7, out this.saDayNames); + ret &= GetCalendarDayInfo(localeName, calendarId, CAL_SABBREVDAYNAME7, out this.saAbbrevDayNames); + + // Month names + ret &= GetCalendarMonthInfo(localeName, calendarId, CAL_SMONTHNAME1, out this.saMonthNames); + ret &= GetCalendarMonthInfo(localeName, calendarId, CAL_SABBREVMONTHNAME1, out this.saAbbrevMonthNames); + + // + // The following LCTYPE are not supported in some platforms. If the call fails, + // don't return a failure. + // + GetCalendarDayInfo(localeName, calendarId, CAL_SSHORTESTDAYNAME7, out this.saSuperShortDayNames); + + // Gregorian may have genitive month names + if (calendarId == CalendarId.GREGORIAN) + { + GetCalendarMonthInfo(localeName, calendarId, CAL_SMONTHNAME1 | CAL_RETURN_GENITIVE_NAMES, out this.saMonthGenitiveNames); + GetCalendarMonthInfo(localeName, calendarId, CAL_SABBREVMONTHNAME1 | CAL_RETURN_GENITIVE_NAMES, out this.saAbbrevMonthGenitiveNames); + } + + // Calendar Parts Names + // This doesn't get always get localized names for gregorian (not available in windows < 7) + // so: eg: coreclr on win < 7 won't get these + CallEnumCalendarInfo(localeName, calendarId, CAL_SERASTRING, 0, out this.saEraNames); + CallEnumCalendarInfo(localeName, calendarId, CAL_SABBREVERASTRING, 0, out this.saAbbrevEraNames); + + // + // Calendar Era Info + // Note that calendar era data (offsets, etc) is hard coded for each calendar since this + // data is implementation specific and not dynamic (except perhaps Japanese) + // + + // Clean up the escaping of the formats + this.saShortDates = CultureData.ReescapeWin32Strings(this.saShortDates); + this.saLongDates = CultureData.ReescapeWin32Strings(this.saLongDates); + this.saYearMonths = CultureData.ReescapeWin32Strings(this.saYearMonths); + this.sMonthDay = CultureData.ReescapeWin32String(this.sMonthDay); + + return ret; + } + + // Get native two digit year max + internal static int GetTwoDigitYearMax(CalendarId calendarId) + { + if (GlobalizationMode.Invariant) + { + return Invariant.iTwoDigitYearMax; + } + + int twoDigitYearMax = -1; + + if (!CallGetCalendarInfoEx(null, calendarId, (uint)CAL_ITWODIGITYEARMAX, out twoDigitYearMax)) + { + twoDigitYearMax = -1; + } + + return twoDigitYearMax; + } + + // Call native side to figure out which calendars are allowed + internal static int GetCalendars(String localeName, bool useUserOverride, CalendarId[] calendars) + { + Debug.Assert(!GlobalizationMode.Invariant); + + EnumCalendarsData data = new EnumCalendarsData(); + data.userOverride = 0; + data.calendars = new IntList(); + + // First call GetLocaleInfo if necessary + if (useUserOverride) + { + // They want user overrides, see if the user calendar matches the input calendar + int userCalendar = CultureData.GetLocaleInfoExInt(localeName, LOCALE_ICALENDARTYPE); + + // If we got a default, then use it as the first calendar + if (userCalendar != 0) + { + data.userOverride = userCalendar; + data.calendars.Add(userCalendar); + } + } + + GCHandle contextHandle = GCHandle.Alloc(data); + try + { + // Now call the enumeration API. Work is done by our callback function +#if CORECLR + Interop.Kernel32.EnumCalendarInfoExEx(EnumCalendarsCallback, localeName, ENUM_ALL_CALENDARS, null, CAL_ICALINTVALUE, (IntPtr)contextHandle); +#else + IntPtr callback = AddrofIntrinsics.AddrOf<Func<IntPtr, uint, IntPtr, IntPtr, Interop.BOOL>>(EnumCalendarsCallback); + Interop.Kernel32.EnumCalendarInfoExEx(callback, localeName, ENUM_ALL_CALENDARS, null, CAL_ICALINTVALUE, (IntPtr)contextHandle); +#endif + } + finally + { + contextHandle.Free(); + } + + // Copy to the output array + for (int i = 0; i < Math.Min(calendars.Length, data.calendars.Count); i++) + calendars[i] = (CalendarId)data.calendars[i]; + + // Now we have a list of data, return the count + return data.calendars.Count; + } + + private static bool SystemSupportsTaiwaneseCalendar() + { + Debug.Assert(!GlobalizationMode.Invariant); + + string data; + // Taiwanese calendar get listed as one of the optional zh-TW calendars only when having zh-TW UI + return CallGetCalendarInfoEx("zh-TW", CalendarId.TAIWAN, CAL_SCALNAME, out data); + } + + // PAL Layer ends here + + private const uint CAL_RETURN_NUMBER = 0x20000000; + private const uint CAL_RETURN_GENITIVE_NAMES = 0x10000000; + private const uint CAL_NOUSEROVERRIDE = 0x80000000; + private const uint CAL_SCALNAME = 0x00000002; + private const uint CAL_SMONTHDAY = 0x00000038; + private const uint CAL_SSHORTDATE = 0x00000005; + private const uint CAL_SLONGDATE = 0x00000006; + private const uint CAL_SYEARMONTH = 0x0000002f; + private const uint CAL_SDAYNAME7 = 0x0000000d; + private const uint CAL_SABBREVDAYNAME7 = 0x00000014; + private const uint CAL_SMONTHNAME1 = 0x00000015; + private const uint CAL_SABBREVMONTHNAME1 = 0x00000022; + private const uint CAL_SSHORTESTDAYNAME7 = 0x00000037; + private const uint CAL_SERASTRING = 0x00000004; + private const uint CAL_SABBREVERASTRING = 0x00000039; + private const uint CAL_ICALINTVALUE = 0x00000001; + private const uint CAL_ITWODIGITYEARMAX = 0x00000030; + + private const uint ENUM_ALL_CALENDARS = 0xffffffff; + + private const uint LOCALE_SSHORTDATE = 0x0000001F; + private const uint LOCALE_SLONGDATE = 0x00000020; + private const uint LOCALE_SYEARMONTH = 0x00001006; + private const uint LOCALE_ICALENDARTYPE = 0x00001009; + + //////////////////////////////////////////////////////////////////////// + // + // For calendars like Gregorain US/Taiwan/UmAlQura, they are not available + // in all OS or all localized versions of OS. + // If OS does not support these calendars, we will fallback by using the + // appropriate fallback calendar and locale combination to retrieve data from OS. + // + // Parameters: + // __deref_inout pCalendarInt: + // Pointer to the calendar ID. This will be updated to new fallback calendar ID if needed. + // __in_out pLocaleNameStackBuffer + // Pointer to the StackSString object which holds the locale name to be checked. + // This will be updated to new fallback locale name if needed. + // + //////////////////////////////////////////////////////////////////////// + private static void CheckSpecialCalendar(ref CalendarId calendar, ref string localeName) + { + string data; + + // Gregorian-US isn't always available in the OS, however it is the same for all locales + switch (calendar) + { + case CalendarId.GREGORIAN_US: + // See if this works + if (!CallGetCalendarInfoEx(localeName, calendar, CAL_SCALNAME, out data)) + { + // Failed, set it to a locale (fa-IR) that's alway has Gregorian US available in the OS + localeName = "fa-IR"; + } + // See if that works + if (!CallGetCalendarInfoEx(localeName, calendar, CAL_SCALNAME, out data)) + { + // Failed again, just use en-US with the gregorian calendar + localeName = "en-US"; + calendar = CalendarId.GREGORIAN; + } + break; + case CalendarId.TAIWAN: + // Taiwan calendar data is not always in all language version of OS due to Geopolical reasons. + // It is only available in zh-TW localized versions of Windows. + // Let's check if OS supports it. If not, fallback to Greogrian localized for Taiwan calendar. + if (!SystemSupportsTaiwaneseCalendar()) + { + calendar = CalendarId.GREGORIAN; + } + break; + } + } + + private static bool CallGetCalendarInfoEx(string localeName, CalendarId calendar, uint calType, out int data) + { + return (Interop.Kernel32.GetCalendarInfoEx(localeName, (uint)calendar, IntPtr.Zero, calType | CAL_RETURN_NUMBER, IntPtr.Zero, 0, out data) != 0); + } + + private static unsafe bool CallGetCalendarInfoEx(string localeName, CalendarId calendar, uint calType, out string data) + { + const int BUFFER_LENGTH = 80; + + // The maximum size for values returned from GetCalendarInfoEx is 80 characters. + char* buffer = stackalloc char[BUFFER_LENGTH]; + + int ret = Interop.Kernel32.GetCalendarInfoEx(localeName, (uint)calendar, IntPtr.Zero, calType, (IntPtr)buffer, BUFFER_LENGTH, IntPtr.Zero); + if (ret > 0) + { + if (buffer[ret - 1] == '\0') + { + ret--; // don't include the null termination in the string + } + data = new string(buffer, 0, ret); + return true; + } + data = ""; + return false; + } + + // Context for EnumCalendarInfoExEx callback. + private class EnumData + { + public string userOverride; + public StringList strings; + } + + // EnumCalendarInfoExEx callback itself. +#if !CORECLR + [NativeCallable(CallingConvention = CallingConvention.StdCall)] +#endif + private static unsafe Interop.BOOL EnumCalendarInfoCallback(IntPtr lpCalendarInfoString, uint calendar, IntPtr pReserved, IntPtr lParam) + { + EnumData context = (EnumData)((GCHandle)lParam).Target; + try + { + string calendarInfo = new string((char*)lpCalendarInfoString); + + // If we had a user override, check to make sure this differs + if (context.userOverride != calendarInfo) + context.strings.Add(calendarInfo); + + return Interop.BOOL.TRUE; + } + catch (Exception) + { + return Interop.BOOL.FALSE; + } + } + + private static unsafe bool CallEnumCalendarInfo(string localeName, CalendarId calendar, uint calType, uint lcType, out string[] data) + { + EnumData context = new EnumData(); + context.userOverride = null; + context.strings = new StringList(); + // First call GetLocaleInfo if necessary + if (((lcType != 0) && ((lcType & CAL_NOUSEROVERRIDE) == 0)) && + // Get user locale, see if it matches localeName. + // Note that they should match exactly, including letter case + GetUserDefaultLocaleName() == localeName) + { + // They want user overrides, see if the user calendar matches the input calendar + CalendarId userCalendar = (CalendarId)CultureData.GetLocaleInfoExInt(localeName, LOCALE_ICALENDARTYPE); + + // If the calendars were the same, see if the locales were the same + if (userCalendar == calendar) + { + // They matched, get the user override since locale & calendar match + string res = CultureData.GetLocaleInfoEx(localeName, lcType); + + // if it succeeded remember the override for the later callers + if (res != "") + { + // Remember this was the override (so we can look for duplicates later in the enum function) + context.userOverride = res; + + // Add to the result strings. + context.strings.Add(res); + } + } + } + + GCHandle contextHandle = GCHandle.Alloc(context); + try + { +#if CORECLR + Interop.Kernel32.EnumCalendarInfoExEx(EnumCalendarInfoCallback, localeName, (uint)calendar, null, calType, (IntPtr)contextHandle); +#else + // Now call the enumeration API. Work is done by our callback function + IntPtr callback = AddrofIntrinsics.AddrOf<Func<IntPtr, uint, IntPtr, IntPtr, Interop.BOOL>>(EnumCalendarInfoCallback); + Interop.Kernel32.EnumCalendarInfoExEx(callback, localeName, (uint)calendar, null, calType, (IntPtr)contextHandle); +#endif // CORECLR + } + finally + { + contextHandle.Free(); + } + + // Now we have a list of data, fail if we didn't find anything. + if (context.strings.Count == 0) + { + data = null; + return false; + } + + string[] output = context.strings.ToArray(); + + if (calType == CAL_SABBREVERASTRING || calType == CAL_SERASTRING) + { + // Eras are enumerated backwards. (oldest era name first, but + // Japanese calendar has newest era first in array, and is only + // calendar with multiple eras) + Array.Reverse(output, 0, output.Length); + } + + data = output; + + return true; + } + + //////////////////////////////////////////////////////////////////////// + // + // Get the native day names + // + // NOTE: There's a disparity between .Net & windows day orders, the input day should + // start with Sunday + // + // Parameters: + // OUT pOutputStrings The output string[] value. + // + //////////////////////////////////////////////////////////////////////// + private static bool GetCalendarDayInfo(string localeName, CalendarId calendar, uint calType, out string[] outputStrings) + { + bool result = true; + + // + // We'll need a new array of 7 items + // + string[] results = new string[7]; + + // Get each one of them + for (int i = 0; i < 7; i++, calType++) + { + result &= CallGetCalendarInfoEx(localeName, calendar, calType, out results[i]); + + // On the first iteration we need to go from CAL_SDAYNAME7 to CAL_SDAYNAME1, so subtract 7 before the ++ happens + // This is because the framework starts on sunday and windows starts on monday when counting days + if (i == 0) + calType -= 7; + } + + outputStrings = results; + + return result; + } + + //////////////////////////////////////////////////////////////////////// + // + // Get the native month names + // + // Parameters: + // OUT pOutputStrings The output string[] value. + // + //////////////////////////////////////////////////////////////////////// + private static bool GetCalendarMonthInfo(string localeName, CalendarId calendar, uint calType, out string[] outputStrings) + { + // + // We'll need a new array of 13 items + // + string[] results = new string[13]; + + // Get each one of them + for (int i = 0; i < 13; i++, calType++) + { + if (!CallGetCalendarInfoEx(localeName, calendar, calType, out results[i])) + results[i] = ""; + } + + outputStrings = results; + + return true; + } + + // + // struct to help our calendar data enumaration callback + // + private class EnumCalendarsData + { + public int userOverride; // user override value (if found) + public IntList calendars; // list of calendars found so far + } + +#if !CORECLR + [NativeCallable(CallingConvention = CallingConvention.StdCall)] +#endif + private static Interop.BOOL EnumCalendarsCallback(IntPtr lpCalendarInfoString, uint calendar, IntPtr reserved, IntPtr lParam) + { + EnumCalendarsData context = (EnumCalendarsData)((GCHandle)lParam).Target; + try + { + // If we had a user override, check to make sure this differs + if (context.userOverride != calendar) + context.calendars.Add((int)calendar); + + return Interop.BOOL.TRUE; + } + catch (Exception) + { + return Interop.BOOL.FALSE; + } + } + + private static unsafe String GetUserDefaultLocaleName() + { + Debug.Assert(!GlobalizationMode.Invariant); + + const int LOCALE_NAME_MAX_LENGTH = 85; + const uint LOCALE_SNAME = 0x0000005c; + const string LOCALE_NAME_USER_DEFAULT = null; + + int result; + char* localeName = stackalloc char[LOCALE_NAME_MAX_LENGTH]; + result = CultureData.GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, LOCALE_SNAME, localeName, LOCALE_NAME_MAX_LENGTH); + + return result <= 0 ? "" : new String(localeName, 0, result - 1); // exclude the null termination + } + } +} |