From 8e7431e51cd088e58fb5e48dcd6615b86b479ca6 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Wed, 27 Jan 2016 17:41:14 -0600 Subject: [PATCH] TimeZoneInfo.DisplayName values are not localized on Linux Fixed by calling ICU's ucal_getTimeZoneDisplayName to read the display names for the current locale. Fix https://github.com/dotnet/corefx/issues/2748 --- .../System.Globalization.Native/calendarData.cpp | 52 ++++---------------- src/corefx/System.Globalization.Native/errors.h | 36 ++++++++++++++ .../System.Globalization.Native/timeZoneInfo.cpp | 41 ++++++++++++++-- .../Interop.Calendar.cs | 3 +- .../Interop.ResultCode.cs | 17 +++++++ .../Interop.TimeZoneInfo.cs | 17 ++++++- .../System.Globalization.Native/Interop.Utils.cs | 52 ++++++++++++++++++++ .../System/Globalization/CalendarData.Unix.cs | 55 +++++----------------- src/mscorlib/mscorlib.shared.sources.props | 2 + src/mscorlib/src/System/TimeZoneInfo.cs | 32 ++++++++++++- 10 files changed, 215 insertions(+), 92 deletions(-) create mode 100644 src/corefx/System.Globalization.Native/errors.h create mode 100644 src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.ResultCode.cs create mode 100644 src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.Utils.cs diff --git a/src/corefx/System.Globalization.Native/calendarData.cpp b/src/corefx/System.Globalization.Native/calendarData.cpp index fb035a9..f91cc0c 100644 --- a/src/corefx/System.Globalization.Native/calendarData.cpp +++ b/src/corefx/System.Globalization.Native/calendarData.cpp @@ -9,6 +9,7 @@ #include "config.h" #include "locale.hpp" #include "holders.h" +#include "errors.h" #define GREGORIAN_NAME "gregorian" #define JAPANESE_NAME "japanese" @@ -82,43 +83,11 @@ enum CalendarDataType : int32_t AbbrevEraNames = 14, }; -/* -* These values should be kept in sync with -* System.Globalization.CalendarDataResult -*/ -enum CalendarDataResult : int32_t -{ - Success = 0, - UnknownError = 1, - InsufficentBuffer = 2, -}; - // the function pointer definition for the callback used in EnumCalendarInfo typedef void (*EnumCalendarInfoCallback)(const UChar*, const void*); /* Function: -GetCalendarDataResult - -Converts a UErrorCode to a CalendarDataResult. -*/ -CalendarDataResult GetCalendarDataResult(UErrorCode err) -{ - if (U_SUCCESS(err)) - { - return Success; - } - - if (err == U_BUFFER_OVERFLOW_ERROR) - { - return InsufficentBuffer; - } - - return UnknownError; -} - -/* -Function: GetCalendarName Gets the associated ICU calendar name for the CalendarId. @@ -247,18 +216,18 @@ GetMonthDayPattern Gets the Month-Day DateTime pattern for the specified locale. */ -CalendarDataResult GetMonthDayPattern(const char* locale, UChar* sMonthDay, int32_t stringCapacity) +ResultCode GetMonthDayPattern(const char* locale, UChar* sMonthDay, int32_t stringCapacity) { UErrorCode err = U_ZERO_ERROR; UDateTimePatternGenerator* pGenerator = udatpg_open(locale, &err); UDateTimePatternGeneratorHolder generatorHolder(pGenerator, err); if (U_FAILURE(err)) - return GetCalendarDataResult(err); + return GetResultCode(err); udatpg_getBestPattern(pGenerator, UDAT_MONTH_DAY_UCHAR, -1, sMonthDay, stringCapacity, &err); - return GetCalendarDataResult(err); + return GetResultCode(err); } /* @@ -267,8 +236,7 @@ GetNativeCalendarName Gets the native calendar name. */ -CalendarDataResult -GetNativeCalendarName(const char* locale, CalendarId calendarId, UChar* nativeName, int32_t stringCapacity) +ResultCode GetNativeCalendarName(const char* locale, CalendarId calendarId, UChar* nativeName, int32_t stringCapacity) { UErrorCode err = U_ZERO_ERROR; ULocaleDisplayNames* pDisplayNames = uldn_open(locale, ULDN_STANDARD_NAMES, &err); @@ -276,7 +244,7 @@ GetNativeCalendarName(const char* locale, CalendarId calendarId, UChar* nativeNa uldn_keyValueDisplayName(pDisplayNames, "calendar", GetCalendarName(calendarId), nativeName, stringCapacity, &err); - return GetCalendarDataResult(err); + return GetResultCode(err); } /* @@ -286,7 +254,7 @@ GetCalendarInfo Gets a single string of calendar information by filling the result parameter with the requested value. */ -extern "C" CalendarDataResult GlobalizationNative_GetCalendarInfo( +extern "C" ResultCode GlobalizationNative_GetCalendarInfo( const UChar* localeName, CalendarId calendarId, CalendarDataType dataType, UChar* result, int32_t resultCapacity) { UErrorCode err = U_ZERO_ERROR; @@ -542,9 +510,9 @@ The context parameter is passed through to the callback along with each string. */ extern "C" int32_t GlobalizationNative_EnumCalendarInfo( EnumCalendarInfoCallback callback, - const UChar* localeName, - CalendarId calendarId, - CalendarDataType dataType, + const UChar* localeName, + CalendarId calendarId, + CalendarDataType dataType, const void* context) { UErrorCode err = U_ZERO_ERROR; diff --git a/src/corefx/System.Globalization.Native/errors.h b/src/corefx/System.Globalization.Native/errors.h new file mode 100644 index 0000000..2bfbdb2 --- /dev/null +++ b/src/corefx/System.Globalization.Native/errors.h @@ -0,0 +1,36 @@ +// 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. + +#pragma once + +#include + +/* +* These values should be kept in sync with +* Interop.GlobalizationInterop.ResultCode +*/ +enum ResultCode : int32_t +{ + Success = 0, + UnknownError = 1, + InsufficentBuffer = 2, +}; + +/* +Converts a UErrorCode to a ResultCode. +*/ +static ResultCode GetResultCode(UErrorCode err) +{ + if (err == U_BUFFER_OVERFLOW_ERROR || err == U_STRING_NOT_TERMINATED_WARNING) + { + return InsufficentBuffer; + } + + if (U_SUCCESS(err)) + { + return Success; + } + + return UnknownError; +} diff --git a/src/corefx/System.Globalization.Native/timeZoneInfo.cpp b/src/corefx/System.Globalization.Native/timeZoneInfo.cpp index 025bccd..d0e01e5 100644 --- a/src/corefx/System.Globalization.Native/timeZoneInfo.cpp +++ b/src/corefx/System.Globalization.Native/timeZoneInfo.cpp @@ -5,11 +5,13 @@ #include #include +#include -/* -Function: -ReadLink +#include "locale.hpp" +#include "holders.h" +#include "errors.h" +/* Gets the symlink value for the path. */ extern "C" int32_t GlobalizationNative_ReadLink(const char* path, char* result, size_t resultCapacity) @@ -22,3 +24,36 @@ extern "C" int32_t GlobalizationNative_ReadLink(const char* path, char* result, result[r] = '\0'; return true; } + +/* +These values should be kept in sync with the managed Interop.GlobalizationInterop.TimeZoneDisplayNameType enum. +*/ +enum TimeZoneDisplayNameType : int32_t +{ + Generic = 0, + Standard = 1, + DaylightSavings = 2, +}; + +/* +Gets the localized display name for the specified time zone. +*/ +extern "C" ResultCode GlobalizationNative_GetTimeZoneDisplayName( + const UChar* localeName, const UChar* timeZoneId, TimeZoneDisplayNameType type, UChar* result, int32_t resultLength) +{ + UErrorCode err = U_ZERO_ERROR; + char locale[ULOC_FULLNAME_CAPACITY]; + GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, false, &err); + + int32_t timeZoneIdLength = -1; // timeZoneId is NULL-terminated + UCalendar* calendar = ucal_open(timeZoneId, timeZoneIdLength, locale, UCAL_DEFAULT, &err); + UCalendarHolder calendarHolder(calendar, err); + + // TODO (https://github.com/dotnet/corefx/issues/5741): need to support Generic names, but ICU "C" api + // has no public option for this. For now, just use the ICU standard name for both Standard and Generic + // (which is the same behavior on Windows with the mincore TIME_ZONE_INFORMATION APIs). + ucal_getTimeZoneDisplayName( + calendar, type == DaylightSavings ? UCAL_DST : UCAL_STANDARD, locale, result, resultLength, &err); + + return GetResultCode(err); +} diff --git a/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.Calendar.cs b/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.Calendar.cs index a0076bc..7b3caea 100644 --- a/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.Calendar.cs +++ b/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.Calendar.cs @@ -19,10 +19,9 @@ internal static partial class Interop internal static extern int GetCalendars(string localeName, CalendarId[] calendars, int calendarsCapacity); [DllImport(Libraries.GlobalizationInterop, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetCalendarInfo")] - internal static extern CalendarDataResult GetCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType calendarDataType, [Out] StringBuilder result, int resultCapacity); + internal static extern ResultCode GetCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType calendarDataType, [Out] StringBuilder result, int resultCapacity); [DllImport(Libraries.GlobalizationInterop, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_EnumCalendarInfo")] - [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool EnumCalendarInfo(EnumCalendarInfoCallback callback, string localeName, CalendarId calendarId, CalendarDataType calendarDataType, IntPtr context); [DllImport(Libraries.GlobalizationInterop, EntryPoint = "GlobalizationNative_GetLatestJapaneseEra")] diff --git a/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.ResultCode.cs b/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.ResultCode.cs new file mode 100644 index 0000000..4621580 --- /dev/null +++ b/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.ResultCode.cs @@ -0,0 +1,17 @@ +// 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. + +internal static partial class Interop +{ + internal static partial class GlobalizationInterop + { + // needs to be kept in sync with ResultCode in System.Globalization.Native + internal enum ResultCode + { + Success = 0, + UnknownError = 1, + InsufficentBuffer = 2, + } + } +} diff --git a/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.TimeZoneInfo.cs b/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.TimeZoneInfo.cs index 9b17aa3..26a9fe0 100644 --- a/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.TimeZoneInfo.cs +++ b/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.TimeZoneInfo.cs @@ -10,7 +10,22 @@ internal static partial class Interop internal static partial class GlobalizationInterop { [DllImport(Libraries.GlobalizationInterop, CharSet = CharSet.Ansi, EntryPoint = "GlobalizationNative_ReadLink")] // readlink requires char* - [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool ReadLink(string filePath, [Out] StringBuilder result, uint resultCapacity); + + // needs to be kept in sync with TimeZoneDisplayNameType in System.Globalization.Native + internal enum TimeZoneDisplayNameType + { + Generic = 0, + Standard = 1, + DaylightSavings = 2, + } + + [DllImport(Libraries.GlobalizationInterop, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_GetTimeZoneDisplayName")] + internal static extern ResultCode GetTimeZoneDisplayName( + string localeName, + string timeZoneId, + TimeZoneDisplayNameType type, + [Out] StringBuilder result, + int resultLength); } } diff --git a/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.Utils.cs b/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.Utils.cs new file mode 100644 index 0000000..33b10c0 --- /dev/null +++ b/src/mscorlib/corefx/Interop/Unix/System.Globalization.Native/Interop.Utils.cs @@ -0,0 +1,52 @@ +// 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.Text; + +internal static partial class Interop +{ + /// + /// Helper for making interop calls that return a string, but we don't know + /// the correct size of buffer to make. So invoke the interop call with an + /// increasing buffer until the size is big enough. + /// + internal static bool CallStringMethod( + Func interopCall, + TArg1 arg1, + TArg2 arg2, + TArg3 arg3, + out string result) + { + const int initialStringSize = 80; + const int maxDoubleAttempts = 5; + + StringBuilder stringBuilder = StringBuilderCache.Acquire(initialStringSize); + + for (int i = 0; i < maxDoubleAttempts; i++) + { + GlobalizationInterop.ResultCode resultCode = interopCall(arg1, arg2, arg3, stringBuilder); + + if (resultCode == GlobalizationInterop.ResultCode.Success) + { + result = StringBuilderCache.GetStringAndRelease(stringBuilder); + return true; + } + else if (resultCode == GlobalizationInterop.ResultCode.InsufficentBuffer) + { + // increase the string size and loop + stringBuilder.EnsureCapacity(stringBuilder.Capacity * 2); + } + else + { + // if there is an unknown error, don't proceed + break; + } + } + + StringBuilderCache.Release(stringBuilder); + result = null; + return false; + } +} diff --git a/src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs b/src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs index 26a6b3e..6c6a18e 100644 --- a/src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs +++ b/src/mscorlib/corefx/System/Globalization/CalendarData.Unix.cs @@ -30,14 +30,6 @@ namespace System.Globalization AbbrevEraNames = 14, } - // needs to be kept in sync with CalendarDataResult in System.Globalization.Native - internal enum CalendarDataResult - { - Success = 0, - UnknownError = 1, - InsufficentBuffer = 2, - } - internal partial class CalendarData { private bool LoadCalendarDataFromSystem(String localeName, CalendarId calendarId) @@ -97,41 +89,18 @@ namespace System.Globalization [SecuritySafeCritical] private static bool GetCalendarInfo(string localeName, CalendarId calendarId, CalendarDataType dataType, out string calendarString) { - calendarString = null; - - const int initialStringSize = 80; - const int maxDoubleAttempts = 5; - - for (int i = 0; i < maxDoubleAttempts; i++) - { - StringBuilder stringBuilder = StringBuilderCache.Acquire((int)(initialStringSize * Math.Pow(2, i))); - - CalendarDataResult result = Interop.GlobalizationInterop.GetCalendarInfo( - localeName, - calendarId, - dataType, - stringBuilder, - stringBuilder.Capacity); - - if (result == CalendarDataResult.Success) - { - calendarString = StringBuilderCache.GetStringAndRelease(stringBuilder); - return true; - } - else - { - StringBuilderCache.Release(stringBuilder); - - if (result != CalendarDataResult.InsufficentBuffer) - { - return false; - } - - // else, it is an InsufficentBuffer error, so loop and increase the string size - } - } - - return false; + return Interop.CallStringMethod( + (locale, calId, type, stringBuilder) => + Interop.GlobalizationInterop.GetCalendarInfo( + locale, + calId, + type, + stringBuilder, + stringBuilder.Capacity), + localeName, + calendarId, + dataType, + out calendarString); } private static bool EnumDatePatterns(string localeName, CalendarId calendarId, CalendarDataType dataType, out string[] datePatterns) diff --git a/src/mscorlib/mscorlib.shared.sources.props b/src/mscorlib/mscorlib.shared.sources.props index e14bdd6..8b1bce3 100644 --- a/src/mscorlib/mscorlib.shared.sources.props +++ b/src/mscorlib/mscorlib.shared.sources.props @@ -789,7 +789,9 @@ + + diff --git a/src/mscorlib/src/System/TimeZoneInfo.cs b/src/mscorlib/src/System/TimeZoneInfo.cs index baa1fac..08ddc3e 100644 --- a/src/mscorlib/src/System/TimeZoneInfo.cs +++ b/src/mscorlib/src/System/TimeZoneInfo.cs @@ -1175,7 +1175,9 @@ namespace System { m_displayName = c_localId; m_baseUtcOffset = TimeSpan.Zero; - // find the best matching baseUtcOffset and display strings based on the current utcNow value + // find the best matching baseUtcOffset and display strings based on the current utcNow value. + // NOTE: read the display strings from the the tzfile now in case they can't be loaded later + // from the globalization data. DateTime utcNow = DateTime.UtcNow; for (int i = 0; i < dts.Length && dts[i] <= utcNow; i++) { int type = typeOfLocalTime[i]; @@ -1203,6 +1205,10 @@ namespace System { } m_displayName = m_standardDisplayName; + GetDisplayName(Interop.GlobalizationInterop.TimeZoneDisplayNameType.Generic, ref m_displayName); + GetDisplayName(Interop.GlobalizationInterop.TimeZoneDisplayNameType.Standard, ref m_standardDisplayName); + GetDisplayName(Interop.GlobalizationInterop.TimeZoneDisplayNameType.DaylightSavings, ref m_daylightDisplayName); + // TZif supports seconds-level granularity with offsets but TimeZoneInfo only supports minutes since it aligns // with DateTimeOffset, SQL Server, and the W3C XML Specification if (m_baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0) { @@ -1216,6 +1222,30 @@ namespace System { ValidateTimeZoneInfo(m_id, m_baseUtcOffset, m_adjustmentRules, out m_supportsDaylightSavingTime); } + + private void GetDisplayName(Interop.GlobalizationInterop.TimeZoneDisplayNameType nameType, ref string displayName) + { + string timeZoneDisplayName; + bool result = Interop.CallStringMethod( + (locale, id, type, stringBuilder) => Interop.GlobalizationInterop.GetTimeZoneDisplayName( + locale, + id, + type, + stringBuilder, + stringBuilder.Capacity), + CultureInfo.CurrentUICulture.Name, + m_id, + nameType, + out timeZoneDisplayName); + + // If there is an unknown error, don't set the displayName field. + // It will be set to the abbreviation that was read out of the tzfile. + if (result) + { + displayName = timeZoneDisplayName; + } + } + #endif // PLATFORM_UNIX private TimeZoneInfo( -- 2.7.4