Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ internal enum TimeZoneDisplayNameType
DaylightSavings = 2,
GenericLocation = 3,
ExemplarCity = 4,
TimeZoneName = 5,
}
}
}
21 changes: 21 additions & 0 deletions src/libraries/Common/src/Interop/Interop.TimeZoneInfo.iOS.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Globalization
{
[LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetTimeZoneDisplayNameNative", StringMarshalling = StringMarshalling.Utf16)]
internal static unsafe partial ResultCode GetTimeZoneDisplayNameNative(
string localeName,
int lNameLength,
string timeZoneId,
int idLength,
TimeZoneDisplayNameType type,
char* result,
int resultLength);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<ILLinkSharedDirectory>$(MSBuildThisFileDirectory)ILLink\</ILLinkSharedDirectory>
<IsBigEndian Condition="'$(Platform)' == 's390x'">true</IsBigEndian>
<Is64Bit Condition="'$(Platform)' == 'arm64' or '$(Platform)' == 'x64' or '$(Platform)' == 's390x' or '$(Platform)' == 'loongarch64' or '$(Platform)' == 'ppc64le' or '$(Platform)' == 'riscv64'">true</Is64Bit>
<UseMinimalGlobalizationData Condition="'$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true' or '$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true'">true</UseMinimalGlobalizationData>
<UseMinimalGlobalizationData Condition="'$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true'">true</UseMinimalGlobalizationData>
</PropertyGroup>
<PropertyGroup>
<DefineConstants Condition="'$(IsBigEndian)' == 'true'">$(DefineConstants);BIGENDIAN</DefineConstants>
Expand Down Expand Up @@ -1363,6 +1363,9 @@
<Compile Include="$(CommonPath)Interop\Interop.TimeZoneInfo.cs" Condition="'$(UseMinimalGlobalizationData)' != 'true'">
<Link>Common\Interop\Interop.TimeZoneInfo.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Interop.TimeZoneInfo.iOS.cs" Condition="'$(IsiOSLike)' == 'true'">
<Link>Common\Interop\Interop.TimeZoneInfo.iOS.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Interop.Utils.cs">
<Link>Common\Interop\Interop.Utils.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ public sealed partial class TimeZoneInfo
// Helper function to get the standard display name for the UTC static time zone instance
private static string GetUtcStandardDisplayName()
{
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
if (!GlobalizationMode.Hybrid)
{
// For this target, be consistent with other time zone display names that use an abbreviation.
return "UTC";
}
#endif
// Don't bother looking up the name for invariant or English cultures
CultureInfo uiCulture = CultureInfo.CurrentUICulture;
if (GlobalizationMode.Invariant || uiCulture.Name.Length == 0 || uiCulture.TwoLetterISOLanguageName == "en")
Expand All @@ -48,6 +55,13 @@ private static string GetUtcStandardDisplayName()
// Helper function to get the full display name for the UTC static time zone instance
private static string GetUtcFullDisplayName(string timeZoneId, string standardDisplayName)
{
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
if (!GlobalizationMode.Hybrid)
{
// For this target, be consistent with other time zone display names that use the ID.
return $"(UTC) {timeZoneId}";
}
#endif
return $"(UTC) {standardDisplayName}";
}
#pragma warning restore IDE0060
Expand Down Expand Up @@ -95,6 +109,10 @@ private static unsafe void GetDisplayName(string timeZoneId, Interop.Globalizati
{
fixed (char* bufferPtr = buffer)
{
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
if (GlobalizationMode.Hybrid)
return Interop.Globalization.GetTimeZoneDisplayNameNative(locale, locale.Length, id, id.Length, type, bufferPtr, buffer.Length);
#endif
return Interop.Globalization.GetTimeZoneDisplayName(locale, id, type, bufferPtr, buffer.Length);
}
},
Expand All @@ -111,6 +129,10 @@ private static unsafe void GetDisplayName(string timeZoneId, Interop.Globalizati
{
fixed (char* bufferPtr = buffer)
{
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
if (GlobalizationMode.Hybrid)
return Interop.Globalization.GetTimeZoneDisplayNameNative(locale, locale.Length, id, id.Length, type, bufferPtr, buffer.Length);
#endif
return Interop.Globalization.GetTimeZoneDisplayName(locale, id, type, bufferPtr, buffer.Length);
}
},
Expand All @@ -131,24 +153,34 @@ private static unsafe void GetDisplayName(string timeZoneId, Interop.Globalizati
// Helper function that builds the value backing the DisplayName field from globalization data.
private static void GetFullValueForDisplayNameField(string timeZoneId, TimeSpan baseUtcOffset, ref string? displayName)
{
CultureInfo uiCulture = UICulture;
// Get the base offset to prefix in front of the time zone.
// Only UTC and its aliases have "(UTC)", handled earlier. All other zones include an offset, even if it's zero.
string baseOffsetText = string.Create(null, stackalloc char[128], $"(UTC{(baseUtcOffset >= TimeSpan.Zero ? '+' : '-')}{baseUtcOffset:hh\\:mm})");

#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
if (GlobalizationMode.Hybrid)
{
string? timeZoneName = null;
GetDisplayName(timeZoneId, Interop.Globalization.TimeZoneDisplayNameType.TimeZoneName, uiCulture.Name, ref timeZoneName);
// For this target, be consistent with other time zone display names that use the ID.
displayName = $"{baseOffsetText} {timeZoneName}";
return;
}
#endif
// There are a few different ways we might show the display name depending on the data.
// The algorithm used below should avoid duplicating the same words while still achieving the
// goal of providing a unique, discoverable, and intuitive name.

// Try to get the generic name for this time zone.
string? genericName = null;
CultureInfo uiCulture = UICulture;
GetDisplayName(timeZoneId, Interop.Globalization.TimeZoneDisplayNameType.Generic, uiCulture.Name, ref genericName);
if (genericName == null)
{
// We'll use the fallback display name value already set.
return;
}

// Get the base offset to prefix in front of the time zone.
// Only UTC and its aliases have "(UTC)", handled earlier. All other zones include an offset, even if it's zero.
string baseOffsetText = string.Create(null, stackalloc char[128], $"(UTC{(baseUtcOffset >= TimeSpan.Zero ? '+' : '-')}{baseUtcOffset:hh\\:mm})");

// Get the generic location name.
string? genericLocationName = null;
GetDisplayName(timeZoneId, Interop.Globalization.TimeZoneDisplayNameType.GenericLocation, uiCulture.Name, ref genericLocationName);
Expand Down Expand Up @@ -256,8 +288,14 @@ private static string GetExemplarCityName(string timeZoneId, string uiCultureNam
// Helper function that returns an alternative ID using ICU data. Used primarily for converting from Windows IDs.
private static unsafe string? GetAlternativeId(string id, out bool idIsIana)
{
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
// No alternative IDs in this target.
idIsIana = false;
return null;
#else
idIsIana = false;
return TryConvertWindowsIdToIanaId(id, null, out string? ianaId) ? ianaId : null;
#endif
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ public sealed partial class TimeZoneInfo
{
private static unsafe bool TryConvertIanaIdToWindowsId(string ianaId, bool allocate, out string? windowsId)
{
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
windowsId = null;
return false;
#else
if (GlobalizationMode.Invariant ||
GlobalizationMode.UseNls ||
ianaId is null ||
Expand All @@ -28,10 +32,15 @@ ianaId is null ||

windowsId = null;
return false;
#endif
}

private static unsafe bool TryConvertWindowsIdToIanaId(string windowsId, string? region, bool allocate, out string? ianaId)
{
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
ianaId = null;
return false;
#else
// This functionality is not enabled in the browser for the sake of size reduction.
if (GlobalizationMode.Invariant || GlobalizationMode.UseNls || windowsId is null)
{
Expand Down Expand Up @@ -88,6 +97,7 @@ private static unsafe bool TryConvertWindowsIdToIanaId(string windowsId, string?

ianaId = null;
return false;
#endif
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ private static bool TryGetLocalTzFile([NotNullWhen(true)] out byte[]? rawData, [
#pragma warning disable IDE0074 // Use compound assignment
if (tzVariable == null)
{
#if TARGET_IOS || TARGET_TVOS
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
tzVariable = Interop.Sys.GetDefaultTimeZone();
#elif TARGET_WASI || TARGET_BROWSER
if (UseEmbeddedTzDatabase)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ public AdjustmentRule[] GetAdjustmentRules()
if (GlobalizationMode.Invariant)
return displayName;

#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
if (!GlobalizationMode.Hybrid)
return displayName;
#endif
GetFullValueForDisplayNameField(Id, BaseUtcOffset, ref displayName);

return displayName;
Expand All @@ -245,6 +249,10 @@ public AdjustmentRule[] GetAdjustmentRules()
if (GlobalizationMode.Invariant)
return standardDisplayName;

#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
if (!GlobalizationMode.Hybrid)
return standardDisplayName;
#endif
GetStandardDisplayName(Id, ref standardDisplayName);

return standardDisplayName;
Expand All @@ -259,6 +267,10 @@ public AdjustmentRule[] GetAdjustmentRules()
if (GlobalizationMode.Invariant)
return daylightDisplayName;

#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
if (!GlobalizationMode.Hybrid)
return daylightDisplayName;
#endif
GetDaylightDisplayName(Id, ref daylightDisplayName);

return daylightDisplayName;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)-maccatalyst</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TestRuntime>true</TestRuntime>
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
<HybridGlobalization>true</HybridGlobalization>
</PropertyGroup>

<PropertyGroup>
<WithoutCategories Condition="'$(EnableAdditionalTimezoneChecks)' != 'true'">$(WithoutCategories);AdditionalTimezoneChecks</WithoutCategories>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\System\TimeZoneInfoTests.cs" />
<Compile Include="..\System\TimeZoneTests.cs" />
<Compile Include="..\System\TimeZoneNotFoundExceptionTests.cs" />
</ItemGroup>
</Project>
12 changes: 11 additions & 1 deletion src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public static void Names()
// Name abbreviations, if available, are used instead
public static IEnumerable<object[]> Platform_TimeZoneNamesTestData()
{
if (PlatformDetection.IsBrowser || PlatformDetection.IsiOS || PlatformDetection.IstvOS)
if (PlatformDetection.IsBrowser || (!PlatformDetection.IsHybridGlobalizationOnOSX && (PlatformDetection.IsMacCatalyst || PlatformDetection.IsiOS || PlatformDetection.IstvOS)))
return new TheoryData<TimeZoneInfo, string, string, string, string>
{
{ TimeZoneInfo.FindSystemTimeZoneById(s_strPacific), "(UTC-08:00) America/Los_Angeles", null, "PST", "PDT" },
Expand All @@ -100,6 +100,16 @@ public static IEnumerable<object[]> Platform_TimeZoneNamesTestData()
{ s_NewfoundlandTz, "(UTC-03:30) America/St_Johns", null, "NST", "NDT" },
{ s_catamarcaTz, "(UTC-03:00) America/Argentina/Catamarca", null, "-03", "-02" }
};
else if (PlatformDetection.IsHybridGlobalizationOnOSX && (PlatformDetection.IsMacCatalyst || PlatformDetection.IsiOS || PlatformDetection.IstvOS))
return new TheoryData<TimeZoneInfo, string, string, string, string>
{
{ TimeZoneInfo.FindSystemTimeZoneById(s_strPacific), "(UTC-08:00) America/Los_Angeles", null, "Pacific Standard Time", "Pacific Daylight Time" },
{ TimeZoneInfo.FindSystemTimeZoneById(s_strSydney), "(UTC+10:00) Australia/Sydney", null, "Australian Eastern Standard Time", "Australian Eastern Daylight Time" },
{ TimeZoneInfo.FindSystemTimeZoneById(s_strPerth), "(UTC+08:00) Australia/Perth", null, "Australian Western Standard Time", "Australian Western Daylight Time" },
{ TimeZoneInfo.FindSystemTimeZoneById(s_strIran), "(UTC+03:30) Asia/Tehran", "(UTC+03:30) Iran Standard Time (Tehran)", "Iran Standard Time", "Iran Daylight Time" },
{ s_NewfoundlandTz, "(UTC-03:30) America/St_Johns", null, "Newfoundland Standard Time", "Newfoundland Daylight Time" },
{ s_catamarcaTz, "(UTC-03:00) America/Argentina/Catamarca", null, "Argentina Standard Time", "Argentina Summer Time" }
};
else if (PlatformDetection.IsWindows)
return new TheoryData<TimeZoneInfo, string, string, string, string>
{
Expand Down
3 changes: 2 additions & 1 deletion src/mono/mono/mini/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ if(HAVE_SYS_ICU)
pal_collation.m
pal_casing.m
pal_calendarData.m
pal_normalization.m)
pal_normalization.m
pal_timeZoneInfo.m)

addprefix(icu_shim_darwin_sources "${ICU_SHIM_PATH}" "${icu_shim_darwin_sources_base}")
set(icu_shim_sources ${icu_shim_sources} ${icu_shim_darwin_sources})
Expand Down
3 changes: 2 additions & 1 deletion src/native/libs/System.Globalization.Native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ if (CLR_CMAKE_TARGET_APPLE)
pal_collation.m
pal_casing.m
pal_calendarData.m
pal_normalization.m)
pal_normalization.m
pal_timeZoneInfo.m)
set_source_files_properties(${NATIVEGLOBALIZATION_SOURCES_OBJC} PROPERTIES COMPILE_FLAGS "-fobjc-arc ${CLR_CMAKE_COMMON_OBJC_FLAGS}")
set(NATIVEGLOBALIZATION_SOURCES ${NATIVEGLOBALIZATION_SOURCES} ${NATIVEGLOBALIZATION_SOURCES_OBJC})
endif()
Expand Down
1 change: 1 addition & 0 deletions src/native/libs/System.Globalization.Native/entrypoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ static const Entry s_globalizationNative[] =
DllImportEntry(GlobalizationNative_GetLocaleInfoStringNative)
DllImportEntry(GlobalizationNative_GetLocaleNameNative)
DllImportEntry(GlobalizationNative_GetLocaleTimeFormatNative)
DllImportEntry(GlobalizationNative_GetTimeZoneDisplayNameNative)
DllImportEntry(GlobalizationNative_IndexOfNative)
DllImportEntry(GlobalizationNative_IsNormalizedNative)
DllImportEntry(GlobalizationNative_NormalizeStringNative)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
#include "pal_normalization.h"
#import <Foundation/Foundation.h>

#if !__has_feature(objc_arc)
#error This file relies on ARC for memory management, but ARC is not enabled.
#endif

#if defined(TARGET_MACCATALYST) || defined(TARGET_IOS) || defined(TARGET_TVOS)
static NSString* GetNormalizedStringForForm(NormalizationForm normalizationForm, NSString* sourceString)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@ typedef enum
TimeZoneDisplayName_DaylightSavings = 2,
TimeZoneDisplayName_GenericLocation = 3,
TimeZoneDisplayName_ExemplarCity = 4,
TimeZoneDisplayName_TimeZoneName = 5,
} TimeZoneDisplayNameType;

PALEXPORT int32_t GlobalizationNative_WindowsIdToIanaId(const UChar* windowsId, const char* region, UChar* ianaId, int32_t ianaIdLength);
PALEXPORT int32_t GlobalizationNative_IanaIdToWindowsId(const UChar* ianaId, UChar* windowsId, int32_t windowsIdLength);
PALEXPORT ResultCode GlobalizationNative_GetTimeZoneDisplayName(const UChar* localeName, const UChar* timeZoneId, TimeZoneDisplayNameType type, UChar* result, int32_t resultLength);

#ifdef __APPLE__
PALEXPORT int32_t GlobalizationNative_GetTimeZoneDisplayNameNative(const uint16_t* localeName, int32_t lNameLength, const uint16_t* timeZoneId, int32_t timeZoneIdLength,
TimeZoneDisplayNameType type, uint16_t* result, int32_t resultLength);
#endif
Loading