Skip to content

Commit

Permalink
Merge pull request dotnet#10 from eerhardt/icu-normalization-casing-idna
Browse files Browse the repository at this point in the history
Fixing date patterns on Linux.
  • Loading branch information
eerhardt committed Sep 14, 2015
2 parents 621c650 + d0360e1 commit b2ecb4c
Show file tree
Hide file tree
Showing 2 changed files with 215 additions and 24 deletions.
42 changes: 35 additions & 7 deletions src/corefx/System.Globalization.Native/calendarData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "locale.hpp"

#include <unicode/dtfmtsym.h>
#include <unicode/smpdtfmt.h>
#include <unicode/dtptngen.h>
#include <unicode/locdspnm.h>

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -297,6 +298,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
Expand Down Expand Up @@ -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:
Expand Down
197 changes: 180 additions & 17 deletions src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -42,10 +43,11 @@ 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 &= 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);
Expand Down Expand Up @@ -128,28 +130,163 @@ private static bool GetCalendarInfo(string localeName, CalendarId calendarId, Ca
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);

Expand All @@ -168,19 +305,19 @@ internal static bool EnumCalendarInfo(string localeName, CalendarId calendarId,
{
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);
Expand All @@ -193,8 +330,34 @@ private static bool EnumCalendarInfo(string localeName, CalendarId calendarId, C

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; }
}
}
}

0 comments on commit b2ecb4c

Please sign in to comment.