Fixed by calling ICU's ucal_getTimeZoneDisplayName to read the display names for the current locale.
Fix https://github.com/dotnet/corefx/issues/2748
#include "config.h"
#include "locale.hpp"
#include "holders.h"
+#include "errors.h"
#define GREGORIAN_NAME "gregorian"
#define JAPANESE_NAME "japanese"
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.
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);
}
/*
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);
uldn_keyValueDisplayName(pDisplayNames, "calendar", GetCalendarName(calendarId), nativeName, stringCapacity, &err);
- return GetCalendarDataResult(err);
+ return GetResultCode(err);
}
/*
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;
*/
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;
--- /dev/null
+// 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 <unicode/utypes.h>
+
+/*
+* 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;
+}
#include <stdint.h>
#include <unistd.h>
+#include <unicode/ucal.h>
-/*
-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)
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);
+}
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")]
--- /dev/null
+// 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,
+ }
+ }
+}
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);
}
}
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// 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.
+ /// </summary>
+ internal static bool CallStringMethod<TArg1, TArg2, TArg3>(
+ Func<TArg1, TArg2, TArg3, StringBuilder, GlobalizationInterop.ResultCode> 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;
+ }
+}
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)
[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)
<GlobalizationSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Globalization.Native\Interop.Casing.cs" />
<GlobalizationSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Globalization.Native\Interop.Collation.cs" />
<GlobalizationSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Globalization.Native\Interop.Locale.cs" />
+ <GlobalizationSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Globalization.Native\Interop.ResultCode.cs" />
<GlobalizationSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Globalization.Native\Interop.TimeZoneInfo.cs" />
+ <GlobalizationSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Globalization.Native\Interop.Utils.cs" />
<GlobalizationSources Include="$(CoreFxSourcesRoot)\System\Globalization\CalendarData.Unix.cs" />
<GlobalizationSources Include="$(CoreFxSourcesRoot)\System\Globalization\CompareInfo.Unix.cs" />
<GlobalizationSources Include="$(CoreFxSourcesRoot)\System\Globalization\CultureData.Unix.cs" />
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];
}
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) {
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(