diff --git a/Jint.Tests/Runtime/DateTests.cs b/Jint.Tests/Runtime/DateTests.cs index 17b1c93cf1..6ced862c92 100644 --- a/Jint.Tests/Runtime/DateTests.cs +++ b/Jint.Tests/Runtime/DateTests.cs @@ -114,6 +114,44 @@ public void ToStringUsesDaylightNameWhenInDst() Assert.Contains("(Eastern Standard Time)", engine.Evaluate("new Date(2022, 0, 4).toString()").AsString()); } + [Fact] + public void ToLocaleStringUsesShortTimeZoneAbbreviation() + { + TimeZoneInfo timeZoneInfo; + try + { + timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"); + } + catch (TimeZoneNotFoundException) + { + timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("America/New_York"); + } + + var engine = new Engine(options => options.LocalTimeZone(timeZoneInfo)); + + const string script = """ + (function() { + var d = new Date(2022, 6, 4); // July 4, 2022 - DST (EDT) + return d.toLocaleString('en-US', { timeZoneName: 'short' }); + })(); + """; + + // Should return "EDT" abbreviation, not "Eastern Daylight Time" + var result = engine.Evaluate(script).AsString(); + Assert.Contains("EDT", result); + + const string scriptWinter = """ + (function() { + var d = new Date(2022, 0, 4); // January 4, 2022 - standard time (EST) + return d.toLocaleString('en-US', { timeZoneName: 'short' }); + })(); + """; + + // Should return "EST" abbreviation, not "Eastern Standard Time" + var resultWinter = engine.Evaluate(scriptWinter).AsString(); + Assert.Contains("EST", resultWinter); + } + [Theory] [InlineData("Thu, 30 Jan 2020 08:00:00 PST", 1580400000000)] [InlineData("Thursday January 01 1970 00:00:25 UTC", 25000)] diff --git a/Jint/Native/Intl/Data/MetaZoneData.cs b/Jint/Native/Intl/Data/MetaZoneData.cs index 68c60e0bbf..9116f14fa2 100644 --- a/Jint/Native/Intl/Data/MetaZoneData.cs +++ b/Jint/Native/Intl/Data/MetaZoneData.cs @@ -515,6 +515,100 @@ internal readonly record struct MetaZoneNames( // Apia (Pacific/Apia mapped above under Samoa section - current metazone is Apia) + // Windows timezone ID aliases (for .NET Framework compatibility where TryConvertWindowsIdToIanaId is unavailable) + // These map Windows timezone IDs directly to metazone names. + // America + ["Eastern Standard Time"] = "America_Eastern", + ["US Eastern Standard Time"] = "America_Eastern", + ["Central Standard Time"] = "America_Central", + ["US Central Standard Time"] = "America_Central", + ["Canada Central Standard Time"] = "America_Central", + ["Central America Standard Time"] = "America_Central", + ["Mexico Standard Time"] = "America_Central", + ["Mountain Standard Time"] = "America_Mountain", + ["US Mountain Standard Time"] = "America_Mountain", + ["Pacific Standard Time"] = "America_Pacific", + ["Pacific Standard Time (Mexico)"] = "America_Pacific", + ["Alaskan Standard Time"] = "Alaska", + ["Hawaiian Standard Time"] = "Hawaii_Aleutian", + ["Aleutian Standard Time"] = "Hawaii_Aleutian", + ["Atlantic Standard Time"] = "Atlantic", + ["Turks And Caicos Standard Time"] = "Atlantic", + ["Newfoundland Standard Time"] = "Newfoundland", + ["Greenland Standard Time"] = "Greenland_Western", + ["E. South America Standard Time"] = "Brasilia", + ["Bahia Standard Time"] = "Brasilia", + ["Central Brazilian Standard Time"] = "Amazon", + ["SA Western Standard Time"] = "Amazon", + ["Argentina Standard Time"] = "Argentina", + ["SA Eastern Standard Time"] = "Brasilia", + ["SA Pacific Standard Time"] = "Colombia", + ["Venezuela Standard Time"] = "Venezuela", + ["Paraguay Standard Time"] = "Paraguay", + ["Uruguay Standard Time"] = "Uruguay", + ["Cuba Standard Time"] = "Cuba", + // Europe + ["GMT Standard Time"] = "GMT", + ["Greenwich Standard Time"] = "GMT", + ["W. Europe Standard Time"] = "Europe_Central", + ["Central Europe Standard Time"] = "Europe_Central", + ["Central European Standard Time"] = "Europe_Central", + ["Romance Standard Time"] = "Europe_Central", + ["W. Central Africa Standard Time"] = "Africa_Western", + ["GTB Standard Time"] = "Europe_Eastern", + ["FLE Standard Time"] = "Europe_Eastern", + ["E. Europe Standard Time"] = "Europe_Eastern", + ["Turkey Standard Time"] = "Europe_Eastern", + ["Russian Standard Time"] = "Moscow", + ["Belarus Standard Time"] = "Moscow", + // Asia + ["China Standard Time"] = "China", + ["Taipei Standard Time"] = "Taipei", + ["Tokyo Standard Time"] = "Japan", + ["Korea Standard Time"] = "Korea", + ["Singapore Standard Time"] = "Singapore", + ["India Standard Time"] = "India", + ["Pakistan Standard Time"] = "Pakistan", + ["Bangladesh Standard Time"] = "Bangladesh", + ["SE Asia Standard Time"] = "Indochina", + ["Arabian Standard Time"] = "Gulf", + ["Arab Standard Time"] = "Arabian", + ["Arabic Standard Time"] = "Arabian", + ["Iran Standard Time"] = "Iran", + ["Israel Standard Time"] = "Israel", + ["W. Asia Standard Time"] = "Uzbekistan", + ["Central Asia Standard Time"] = "Kazakhstan_Eastern", + ["Ekaterinburg Standard Time"] = "Yekaterinburg", + ["N. Central Asia Standard Time"] = "Novosibirsk", + ["North Asia Standard Time"] = "Krasnoyarsk", + ["North Asia East Standard Time"] = "Irkutsk", + ["Yakutsk Standard Time"] = "Yakutsk", + ["Russia Time Zone 8"] = "Yakutsk", + ["Vladivostok Standard Time"] = "Vladivostok", + ["Magadan Standard Time"] = "Magadan", + ["Kamchatka Standard Time"] = "Kamchatka", + ["Myanmar Standard Time"] = "Myanmar", + ["Afghanistan Standard Time"] = "Afghanistan", + ["Nepal Standard Time"] = "Nepal", + ["Sri Lanka Standard Time"] = "Lanka", + ["Malaysia Standard Time"] = "Malaysia", + ["West Asia Standard Time"] = "Pakistan", + // Australia/Pacific + ["AUS Eastern Standard Time"] = "Australia_Eastern", + ["E. Australia Standard Time"] = "Australia_Eastern", + ["Tasmania Standard Time"] = "Australia_Eastern", + ["Cen. Australia Standard Time"] = "Australia_Central", + ["AUS Central Standard Time"] = "Australia_Central", + ["W. Australia Standard Time"] = "Australia_Western", + ["New Zealand Standard Time"] = "New_Zealand", + ["Fiji Standard Time"] = "Fiji", + ["Tonga Standard Time"] = "Tonga", + ["Samoa Standard Time"] = "Apia", + // Africa + ["South Africa Standard Time"] = "Africa_Southern", + ["E. Africa Standard Time"] = "Africa_Eastern", + ["Egypt Standard Time"] = "Africa_Eastern", + }; /// diff --git a/Jint/Native/Intl/JsDateTimeFormat.cs b/Jint/Native/Intl/JsDateTimeFormat.cs index 4914b9f407..42757fdb52 100644 --- a/Jint/Native/Intl/JsDateTimeFormat.cs +++ b/Jint/Native/Intl/JsDateTimeFormat.cs @@ -1505,7 +1505,50 @@ private string GetTimeZoneDisplayName(DateTime utcDateTime, bool longName, bool return longName ? TimeZone : parts[parts.Length - 1]; } } - return longName ? TimeZoneInfo.Local.StandardName : TimeZoneInfo.Local.Id; + // No explicit timezone: use the engine's default timezone + var defaultTz = _engine.Options.TimeSystem.DefaultTimeZone; + + var defaultIsDst = false; + try + { + defaultIsDst = defaultTz.IsDaylightSavingTime(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Utc)); + } + catch + { + // If DST check fails, defaultIsDst stays false + } + + // Try to get an IANA timezone ID for the CLDR lookup. + // The IanaToMetaZone table also includes Windows timezone ID aliases for .NET Framework compatibility. + var defaultTzId = defaultTz.Id; +#if NET6_0_OR_GREATER + if (!defaultTz.HasIanaId && TimeZoneInfo.TryConvertWindowsIdToIanaId(defaultTzId, out var defaultIanaId)) + { + defaultTzId = defaultIanaId; + } +#endif + + var defaultCldrName = Data.MetaZoneData.GetDisplayName(defaultTzId, defaultIsDst, longName, generic); + if (defaultCldrName != null) + { + return defaultCldrName; + } + + // Fallback to .NET TimeZoneInfo names + if (longName) + { + return defaultIsDst ? defaultTz.DaylightName : defaultTz.StandardName; + } + + // For short names: try to parse an abbreviation from IANA-style IDs (e.g. "America/New_York" → "New York") + // Windows timezone IDs don't contain '/', so fall back to DST-aware names + if (defaultTzId.Contains('/')) + { + var defaultParts = defaultTzId.Split('/'); + return defaultParts[defaultParts.Length - 1].Replace('_', ' '); + } + + return defaultIsDst ? defaultTz.DaylightName : defaultTz.StandardName; } ///