Hi.  I contributed most of the improvements to time zone display name logic for .NET, so I can offer some clarity here.  The original PR for this work is at: https://github.com/dotnet/runtime/pull/48931.  There's also a detailed blog post here:
https://devblogs.microsoft.com/dotnet/date-time-and-time-zone-enhancements-in-net-6/#time-zone-display-names-on-linux-and-macos

First off - Ignore anything related to the System.TimeZone class.  That only exists for legacy reasons and is generally considered deprecated.  Only System.TimeZoneInfo should be used in .NET today.

As of .NET 6, when running on Linux or MacOS, TimeZoneInfo gets its display names from ICU4C.  As you suspected, it cannot leverage ICU4J.  It is also limited to the C APIs.  It cannot use ICU's C++ APIs.

To construct time zone display names, .NET makes calls to both ucal_getTimeZoneDisplayName, and udat_format with several different formats. You can see these ICU calls and others in the following file:

https://github.com/dotnet/runtime/blob/f20509b9ea563da18af963976b7db0e523e6837e/src/native/libs/System.Globalization.Native/pal_timeZoneInfo.c

You'll also want to look at the implementation of the GetFullValueForDisplayNameField method here:

https://github.com/dotnet/runtime/blob/f20509b9ea563da18af963976b7db0e523e6837e/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.FullGlobalizationData.Unix.cs#L156

(git hashes are just the current values, for permalink purposes)

Between the two files, you'll notice that to use ICU alone to build a list of time zone display names is quite challenging.  It's not quite as simple as just taking the LONG_GENERIC of each one.  For example, if you do that, you'll find at least 13 different entries that all have the same display name, "Mountain Standard Time".  While useful on a single zone, they're useless when distinguishing one zone from the next in a list.  Additionally, the word "Standard" can get in the way.  Ultimately one desires names like "Mountain Time (Phoenix)" vs "Mountain Time (Denver)" - so that they are disambiguated.

There are lots of other edge cases though, and some of it requires an opinionated mindset.  For example, see some of the overrides here:

https://github.com/dotnet/runtime/blob/f20509b9ea563da18af963976b7db0e523e6837e/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.FullGlobalizationData.Unix.cs#L17-L25

Hope that helps.  Feel free to ask any follow up questions.

-Matt


On Tue, Oct 10, 2023 at 5:12 PM Guy Harris via tz <tz@iana.org> wrote:
On Oct 10, 2023, at 4:48 PM, Doug Ewell <doug@ewellic.org> wrote:

> This is what we get from a known, reliable source that offers human-readable names in English (or, importantly, 140 other languages) corresponding to tzids. Some of the names are probably not perfect for a picker. We already know that “(other)” and “(most locations)” are not always perfect either, especially when presented out of context: Other than what? What does “most” include?

Perhaps it's time for somebody to run some UI experiments to determine what works best for what groups of users.  (Complaints that you live in city X but the selector only offers city Y count against "works best".)

> We’re using a standard .NET Core call that queries ICU in a Linux container, and makes a Windows call when running on Windows. There are no options to select names for standard, daylight, or generic, and I don’t know why it chooses standard by default. The ICU folks might know.

I'm guessing it's easier for .NET languages to use ICU4C than ICU4J; the icu::TimeZone Class Reference:

        https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1TimeZone.html#a07cc5464421c1ae84f55ada930cf03df

appears to offer several flavors of the getDisplayName method, with:

        UnicodeString &getDisplayName(UBool inDaylight, EDisplayType style, UnicodeString &result) const

and

        UnicodeString &getDisplayName(UBool inDaylight, EDisplayType style, const Locale &locale, UnicodeString &result) const

having the style argument, which is an EDisplayType:

        https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1TimeZone.html#a07cc5464421c1ae84f55ada930cf03df

I'm guessing that SHORT and LONG use the inDaylight value to determine whether to provide the short or long standard time or the short or long daylight time name, and SHORT_GENERIC and LONG_GENERIC provide the short or long generic name.

At least from what I see on Microsoft Learn, the .NET 7.0 TimeZone class:

        https://learn.microsoft.com/en-us/dotnet/api/system.timezone?view=net-7.0

only has the standard and daylight names as properties

However, the .NET 7.0 TimeZoneInfo class:

        https://learn.microsoft.com/en-us/dotnet/api/system.timezoneinfo?view=net-7.0

also has the DisplayName property, which "Gets the general display name that represents the time zone", and might provide the generic name when it's using ICU.