Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change reading time zone display names #56171

Merged
merged 2 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 0 additions & 16 deletions src/libraries/Common/src/Interop/Windows/Kernel32/Interop.MUI.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -1603,9 +1603,6 @@
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MEMORYSTATUSEX.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.MEMORYSTATUSEX.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MUI.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.MUI.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MultiByteToWideChar.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.MultiByteToWideChar.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ public sealed partial class TimeZoneInfo
"Zulu"
};

private static readonly TimeZoneInfo s_utcTimeZone = CreateUtcTimeZone();

private TimeZoneInfo(byte[] data, string id, bool dstDisabled)
{
_id = id;
Expand Down
162 changes: 27 additions & 135 deletions src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ public sealed partial class TimeZoneInfo
private const int MaxKeyLength = 255;
private const string InvariantUtcStandardDisplayName = "Coordinated Universal Time";

private static readonly Dictionary<string, string> s_fileMuiPathCache = new();
private static readonly TimeZoneInfo s_utcTimeZone = CreateUtcTimeZone(); // must be initialized after s_fileMuiPathCache

private sealed partial class CachedData
{
private static TimeZoneInfo GetCurrentOneYearLocal()
Expand Down Expand Up @@ -738,115 +735,12 @@ private static bool TryCompareTimeZoneInformationToRegistry(in TIME_ZONE_INFORMA
}
}

/// <summary>
/// Helper function for getting the MUI path for a given resource and culture, using a cache to prevent duplicating work.
/// Searches the installed OS languages for either an exact matching culture, or one that has the same parent.
/// If not found, uses the preferred default OS UI language, to align with prior behavior.
/// </summary>
private static string GetCachedFileMuiPath(string filePath, CultureInfo cultureInfo)
{
string? result;
string cacheKey = $"{cultureInfo.Name};{filePath}";

lock (s_fileMuiPathCache)
{
if (s_fileMuiPathCache.TryGetValue(cacheKey, out result))
{
return result;
}
}

result = GetFileMuiPath(filePath, cultureInfo);

lock (s_fileMuiPathCache)
{
s_fileMuiPathCache.TryAdd(cacheKey, result);
}

return result;
}

/// <summary>
/// Helper function for getting the MUI path for a given resource and culture.
/// Searches the installed OS languages for either an exact matching culture, or one that has the same parent.
/// If not found, uses the preferred default OS UI language, to align with prior behavior.
/// </summary>
private static unsafe string GetFileMuiPath(string filePath, CultureInfo cultureInfo)
{
char* fileMuiPath = stackalloc char[Interop.Kernel32.MAX_PATH + 1];
char* language = stackalloc char[Interop.Kernel32.LOCALE_NAME_MAX_LENGTH + 1];
uint fileMuiPathLength = Interop.Kernel32.MAX_PATH;
uint languageLength = Interop.Kernel32.LOCALE_NAME_MAX_LENGTH;
ulong enumerator = 0;

while (true)
{
// Search all installed languages. The enumerator is re-used between loop iterations.
language[0] = '\0';
bool succeeded = Interop.Kernel32.GetFileMUIPath(
Interop.Kernel32.MUI_USE_INSTALLED_LANGUAGES,
filePath, language, ref languageLength,
fileMuiPath, ref fileMuiPathLength, ref enumerator);
fileMuiPath[Interop.Kernel32.MAX_PATH] = '\0';

if (!succeeded)
{
// Recurse to search using the parent of the desired culture.
if (cultureInfo.Parent.Name != string.Empty)
{
return GetFileMuiPath(filePath, cultureInfo.Parent);
}

// Final fallback, using the preferred installed UI language.
enumerator = 0;
language[0] = '\0';
succeeded = Interop.Kernel32.GetFileMUIPath(
Interop.Kernel32.MUI_USER_PREFERRED_UI_LANGUAGES,
filePath, language, ref languageLength,
fileMuiPath, ref fileMuiPathLength, ref enumerator);
fileMuiPath[Interop.Kernel32.MAX_PATH] = '\0';

if (succeeded)
{
return new string(fileMuiPath);
}

return string.Empty;
}

// Lookup succeeded. Check for exact match to the desired culture.
language[Interop.Kernel32.LOCALE_NAME_MAX_LENGTH] = '\0';
ReadOnlySpan<char> lang = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(language);
if (lang.Equals(cultureInfo.Name, StringComparison.OrdinalIgnoreCase))
{
return new string(fileMuiPath);
}

// Check for match of any parent of the language returned to the desired culture.
var ci = CultureInfo.GetCultureInfo(lang.ToString());
while (ci.Parent.Name != string.Empty)
{
if (ci.Parent.Name.Equals(cultureInfo.Name, StringComparison.OrdinalIgnoreCase))
{
return new string(fileMuiPath);
}

ci = ci.Parent;
}

// Not found yet. Continue with next iteration.
}
}

/// <summary>
/// Helper function for retrieving a localized string resource via MUI.
/// The function expects a string in the form: "@resource.dll, -123"
///
/// "resource.dll" is a language-neutral portable executable (LNPE) file in
/// the %windir%\system32 directory. The OS is queried to find the best-fit
/// localized resource file for this LNPE (ex: %windir%\system32\en-us\resource.dll.mui).
/// If a localized resource file exists, we LoadString resource ID "123" and
/// return it to our caller.
/// Try to find the time zone resources Dll matching the CurrentUICulture or one of its parent cultures.
/// We try to check of such resource module e.g. %windir%\system32\[UI Culture Name]\tzres.dll.mui exist.
/// If a localized resource file exists, we LoadString resource with the id specified inside resource input
/// string and and return it to our caller.
/// </summary>
private static string GetLocalizedNameByMuiNativeResource(string resource)
{
Expand All @@ -855,9 +749,6 @@ private static string GetLocalizedNameByMuiNativeResource(string resource)
return string.Empty;
}

// Use the current UI culture when culture not specified
CultureInfo cultureInfo = CultureInfo.CurrentUICulture;

// parse "@tzres.dll, -100"
//
// filePath = "C:\Windows\System32\tzres.dll"
Expand All @@ -869,41 +760,42 @@ private static string GetLocalizedNameByMuiNativeResource(string resource)
return string.Empty;
}

string filePath;
// Get the resource ID
if (!int.TryParse(resources[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out int resourceId))
{
return string.Empty;
}
resourceId = -resourceId;

// Use the current UI culture when culture not specified
CultureInfo cultureInfo = CultureInfo.CurrentUICulture;

// get the path to Windows\System32
string system32 = Environment.SystemDirectory;

// trim the string "@tzres.dll" => "tzres.dll"
ReadOnlySpan<char> tzresDll = resources[0].AsSpan().TrimStart('@');
// trim the string "@tzres.dll" to "tzres.dll" and append the "mui" file extension to it.
string tzresDll = $"{resources[0].AsSpan().TrimStart('@')}.mui";

try
{
filePath = Path.Join(system32, tzresDll);
while (cultureInfo.Name.Length != 0)
{
string filePath = Path.Join(system32, cultureInfo.Name, tzresDll);
if (File.Exists(filePath))
{
// Finally, get the resource from the resource path
return GetLocalizedNameByNativeResource(filePath, resourceId);
}

cultureInfo = cultureInfo.Parent;
}
}
catch (ArgumentException)
{
// there were probably illegal characters in the path
return string.Empty;
}

// Get the MUI File Path
string fileMuiPath = GetCachedFileMuiPath(filePath, cultureInfo);
if (fileMuiPath == string.Empty)
{
// not likely, but we could not resolve a MUI path
return string.Empty;
}

// Get the resource ID
if (!int.TryParse(resources[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out int resourceId))
{
return string.Empty;
}
resourceId = -resourceId;

// Finally, get the resource from the resource path
return GetLocalizedNameByNativeResource(fileMuiPath, resourceId);
return string.Empty;
tarekgh marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ private enum TimeZoneInfoResult
private const string UtcId = "UTC";
private const string LocalId = "Local";

private static readonly TimeZoneInfo s_utcTimeZone = CreateUtcTimeZone();
private static CachedData s_cachedData = new CachedData();

//
Expand Down