summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/corefx/System.Globalization.Native/calendarData.cpp42
-rw-r--r--src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs197
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; }
}
}
}