diff options
-rw-r--r-- | src/corefx/System.Globalization.Native/calendarData.cpp | 42 | ||||
-rw-r--r-- | src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs | 197 |
2 files changed, 215 insertions, 24 deletions
diff --git a/src/corefx/System.Globalization.Native/calendarData.cpp b/src/corefx/System.Globalization.Native/calendarData.cpp index 061fca9172..a9c356dbaf 100644 --- a/src/corefx/System.Globalization.Native/calendarData.cpp +++ b/src/corefx/System.Globalization.Native/calendarData.cpp @@ -9,6 +9,7 @@ #include "locale.hpp" #include <unicode/dtfmtsym.h> +#include <unicode/smpdtfmt.h> #include <unicode/dtptngen.h> #include <unicode/locdspnm.h> @@ -245,7 +246,7 @@ CalendarDataResult GetMonthDayPattern(Locale& locale, UChar* sMonthDay, int32_t if (U_FAILURE(err)) return GetCalendarDataResult(err); - UnicodeString monthDayPattern = generator->getBestPattern(UnicodeString("MMMMd"), err); + UnicodeString monthDayPattern = generator->getBestPattern(UnicodeString(UDAT_MONTH_DAY), err); if (U_FAILURE(err)) return GetCalendarDataResult(err); @@ -299,6 +300,30 @@ extern "C" CalendarDataResult GetCalendarInfo(const UChar* localeName, CalendarI /* Function: +InvokeCallbackForDatePattern + +Gets the ICU date pattern for the specified locale and EStyle and invokes the callback with the result. +*/ +bool InvokeCallbackForDatePattern(Locale& locale, DateFormat::EStyle style, EnumCalendarInfoCallback callback, const void* context) +{ + LocalPointer<DateFormat> dateFormat(DateFormat::createDateInstance(style, locale)); + if (dateFormat.isNull()) + return false; + + // cast to SimpleDateFormat so we can call toPattern() + SimpleDateFormat* sdf = dynamic_cast<SimpleDateFormat*>(dateFormat.getAlias()); + if (sdf == NULL) + return false; + + UnicodeString pattern; + sdf->toPattern(pattern); + + callback(pattern.getTerminatedBuffer(), context); + return true; +} + +/* +Function: InvokeCallbackForDateTimePattern Gets the DateTime pattern for the specified skeleton and invokes the callback with the retrieved value. @@ -451,14 +476,17 @@ extern "C" int32_t EnumCalendarInfo( switch (dataType) { case ShortDates: - return InvokeCallbackForDateTimePattern(locale, "Mdyyyy", callback, context); + // ShortDates to map kShort and kMedium in ICU, but also adding the "yMd" skeleton as well, as this + // closely matches what is used on Windows + return InvokeCallbackForDateTimePattern(locale, UDAT_YEAR_NUM_MONTH_DAY, callback, context) && + InvokeCallbackForDatePattern(locale, DateFormat::kShort, callback, context) && + InvokeCallbackForDatePattern(locale, DateFormat::kMedium, callback, context); case LongDates: - // TODO: need to replace the "EEEE"s with "dddd"s for .net - // Also, "LLLL"s to "MMMM"s - // Also, "G"s to "g"s - return InvokeCallbackForDateTimePattern(locale, "eeeeMMMMddyyyy", callback, context); + // LongDates map to kFull and kLong in ICU. + return InvokeCallbackForDatePattern(locale, DateFormat::kFull, callback, context) && + InvokeCallbackForDatePattern(locale, DateFormat::kLong, callback, context); case YearMonths: - return InvokeCallbackForDateTimePattern(locale, "yyyyMMMM", callback, context); + return InvokeCallbackForDateTimePattern(locale, UDAT_YEAR_MONTH, callback, context); case DayNames: return EnumWeekdays(locale, calendarId, DateFormatSymbols::STANDALONE, DateFormatSymbols::WIDE, callback, context); case AbbrevDayNames: diff --git a/src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs b/src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs index 6a62909023..cfca8a3533 100644 --- a/src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs +++ b/src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Runtime.InteropServices; using System.Text; @@ -42,10 +43,11 @@ namespace System.Globalization 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 &= EnumCalendarInfo(localeName, calendarId, CalendarDataType.ShortDates, out this.saShortDates); - result &= EnumCalendarInfo(localeName, calendarId, CalendarDataType.LongDates, out this.saLongDates); - result &= EnumCalendarInfo(localeName, calendarId, CalendarDataType.YearMonths, out this.saYearMonths); + 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); @@ -128,28 +130,163 @@ namespace System.Globalization return false; } - private bool EnumMonthNames(string localeName, CalendarId calendarId, CalendarDataType dataType, out string[] monthNames) + 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<string> datePatternsList = callbackContext.Results; + + datePatterns = new string[datePatternsList.Count]; + for (int i = 0; i < datePatternsList.Count; i++) + { + datePatterns[i] = NormalizeDatePattern(datePatternsList[i]); + } + } + + return result; + } + + /// <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 = new StringBuilder(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"; + Contract.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 destination.ToString(); + } + + 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; - List<string> monthNameList = new List<string>(13); - bool result = EnumCalendarInfo(localeName, calendarId, dataType, monthNameList); + 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 (monthNameList.Count == 12) + if (callbackContext.Results.Count == 12) { - monthNameList.Add(string.Empty); + callbackContext.Results.Add(string.Empty); } - monthNames = monthNameList.ToArray(); + monthNames = callbackContext.Results.ToArray(); } return result; } - private bool EnumEraNames(string localeName, CalendarId calendarId, CalendarDataType dataType, out string[] eraNames) + private static bool EnumEraNames(string localeName, CalendarId calendarId, CalendarDataType dataType, out string[] eraNames) { bool result = EnumCalendarInfo(localeName, calendarId, dataType, out eraNames); @@ -168,19 +305,19 @@ namespace System.Globalization { calendarData = null; - List<string> calendarDataList = new List<string>(); - bool result = EnumCalendarInfo(localeName, calendarId, dataType, calendarDataList); + CallbackContext callbackContext = new CallbackContext(); + bool result = EnumCalendarInfo(localeName, calendarId, dataType, callbackContext); if (result) { - calendarData = calendarDataList.ToArray(); + calendarData = callbackContext.Results.ToArray(); } return result; } - private static bool EnumCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, List<string> calendarDataList) + private static bool EnumCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, CallbackContext callbackContext) { - GCHandle context = GCHandle.Alloc(calendarDataList); + GCHandle context = GCHandle.Alloc(callbackContext); try { return Interop.GlobalizationInterop.EnumCalendarInfo(EnumCalendarInfoCallback, localeName, calendarId, dataType, (IntPtr)context); @@ -193,8 +330,34 @@ namespace System.Globalization private static void EnumCalendarInfoCallback(string calendarString, IntPtr context) { - List<string> calendarDataList = (List<string>)((GCHandle)context).Target; - calendarDataList.Add(calendarString); + 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); + } + + private class CallbackContext + { + private List<string> _results = new List<string>(); + + public CallbackContext() + { + } + + public List<string> Results { get { return _results; } } + + public bool DisallowDuplicates { get; set; } } } } |