From 8210f415fe705947643a917d3d8a2c0d68e2d15c Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 12 Jan 2026 09:58:31 +0800 Subject: [PATCH 1/8] Enhance TimeZoneHelper with offset and validation https://abp.io/support/questions/10295/TimeZoneHelperGetTimezones-throws-TimeZoneNotFoundException-on-Linux --- Directory.Packages.props | 2 +- .../Volo/Abp/Timing/TimeZoneHelper.cs | 29 +++++++++++++- .../Volo/Abp/Timing/TimeZoneHelper_Tests.cs | 39 +++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 framework/test/Volo.Abp.Timing.Tests/Volo/Abp/Timing/TimeZoneHelper_Tests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 453ddc618f..de499d0b7b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -181,7 +181,7 @@ - + diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs index 6101585878..f4a44db29e 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs @@ -7,14 +7,41 @@ namespace Volo.Abp.Timing; public static class TimeZoneHelper { + /// + /// Returns timezone list ordered by display name, enriched with UTC offset, filtering out invalid ids. + /// public static List GetTimezones(List timezones) { return timezones .OrderBy(x => x.Name) - .Select(x => new NameValue( $"{x.Name} ({GetTimezoneOffset(TZConvert.GetTimeZoneInfo(x.Name))})", x.Name)) + .Select(TryCreateNameValueWithOffset) + .Where(x => x != null) + .Select(x => x!) .ToList(); } + /// + /// Builds a that includes the UTC offset in the name; returns null if the id is not found. + /// + public static NameValue? TryCreateNameValueWithOffset(NameValue timeZone) + { + try + { + var timeZoneInfo = TZConvert.GetTimeZoneInfo(timeZone.Name); + var name = $"{timeZone.Name} ({GetTimezoneOffset(timeZoneInfo)})"; + return new NameValue(name, timeZoneInfo.StandardName); + } + catch (TimeZoneNotFoundException) + { + // ignore + } + + return null; + } + + /// + /// Formats the base UTC offset as "+hh:mm" or "-hh:mm" for display purposes. + /// public static string GetTimezoneOffset(TimeZoneInfo timeZoneInfo) { if (timeZoneInfo.BaseUtcOffset < TimeSpan.Zero) diff --git a/framework/test/Volo.Abp.Timing.Tests/Volo/Abp/Timing/TimeZoneHelper_Tests.cs b/framework/test/Volo.Abp.Timing.Tests/Volo/Abp/Timing/TimeZoneHelper_Tests.cs new file mode 100644 index 0000000000..ced819f1ba --- /dev/null +++ b/framework/test/Volo.Abp.Timing.Tests/Volo/Abp/Timing/TimeZoneHelper_Tests.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using Shouldly; +using TimeZoneConverter; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Timing; + +public class TimeZoneHelper_Tests : AbpIntegratedTest +{ + [Fact] + public void GetTimezones_Test() + { + var validTimeZoneId = "UTC"; + var invalidTimeZoneId = "Invalid/Zone"; + + var timezones = new List + { + new(invalidTimeZoneId, invalidTimeZoneId), + new(validTimeZoneId, validTimeZoneId) + }; + + var result = TimeZoneHelper.GetTimezones(timezones); + + result.Count.ShouldBe(1); + + var expectedTimeZoneInfo = TZConvert.GetTimeZoneInfo(validTimeZoneId); + var expectedName = $"{validTimeZoneId} ({TimeZoneHelper.GetTimezoneOffset(expectedTimeZoneInfo)})"; + + result[0].Name.ShouldBe(expectedName); + result[0].Value.ShouldBe(expectedTimeZoneInfo.StandardName); + } + + [Fact] + public void TryCreateNameValueWithOffset_Should_Return_Null_For_Invalid_Timezone() + { + TimeZoneHelper.TryCreateNameValueWithOffset(new NameValue("Invalid/Zone", "Invalid/Zone")).ShouldBeNull(); + } +} From 1ba16383e362aec4b6882d1da0113f21e11889c3 Mon Sep 17 00:00:00 2001 From: Ma Liming Date: Mon, 12 Jan 2026 10:11:55 +0800 Subject: [PATCH 2/8] Update framework/test/Volo.Abp.Timing.Tests/Volo/Abp/Timing/TimeZoneHelper_Tests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Volo/Abp/Timing/TimeZoneHelper_Tests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/test/Volo.Abp.Timing.Tests/Volo/Abp/Timing/TimeZoneHelper_Tests.cs b/framework/test/Volo.Abp.Timing.Tests/Volo/Abp/Timing/TimeZoneHelper_Tests.cs index ced819f1ba..ee770a2fa4 100644 --- a/framework/test/Volo.Abp.Timing.Tests/Volo/Abp/Timing/TimeZoneHelper_Tests.cs +++ b/framework/test/Volo.Abp.Timing.Tests/Volo/Abp/Timing/TimeZoneHelper_Tests.cs @@ -9,7 +9,7 @@ namespace Volo.Abp.Timing; public class TimeZoneHelper_Tests : AbpIntegratedTest { [Fact] - public void GetTimezones_Test() + public void GetTimezones_Should_Filter_Invalid_Timezones() { var validTimeZoneId = "UTC"; var invalidTimeZoneId = "Invalid/Zone"; From 6b745b56d69ddd7c4fbd5d5eacef0295f5410231 Mon Sep 17 00:00:00 2001 From: Ma Liming Date: Mon, 12 Jan 2026 10:12:06 +0800 Subject: [PATCH 3/8] Update framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs index f4a44db29e..d4f3829dca 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs @@ -15,8 +15,7 @@ public static class TimeZoneHelper return timezones .OrderBy(x => x.Name) .Select(TryCreateNameValueWithOffset) - .Where(x => x != null) - .Select(x => x!) + .OfType() .ToList(); } From 65d8b4281cc654439dfe42944d0fd24678ff4489 Mon Sep 17 00:00:00 2001 From: Ma Liming Date: Mon, 12 Jan 2026 10:12:35 +0800 Subject: [PATCH 4/8] Update framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs index d4f3829dca..ec68a29116 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs @@ -32,7 +32,9 @@ public static class TimeZoneHelper } catch (TimeZoneNotFoundException) { - // ignore + // Invalid or unknown timezone IDs are expected here (e.g. from user input or + // external sources). We intentionally swallow this exception and return null + // so callers (like GetTimezones) can filter out invalid entries. } return null; From c7c075c78ab376dd6869f503d38d9b04ee3bde9d Mon Sep 17 00:00:00 2001 From: Ma Liming Date: Mon, 12 Jan 2026 10:12:55 +0800 Subject: [PATCH 5/8] Update framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs index ec68a29116..446a4a483c 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs @@ -28,7 +28,7 @@ public static class TimeZoneHelper { var timeZoneInfo = TZConvert.GetTimeZoneInfo(timeZone.Name); var name = $"{timeZone.Name} ({GetTimezoneOffset(timeZoneInfo)})"; - return new NameValue(name, timeZoneInfo.StandardName); + return new NameValue(name, timeZone.Name); } catch (TimeZoneNotFoundException) { From e50869a8e73b600ee40c982d6e05515ee6758e13 Mon Sep 17 00:00:00 2001 From: Ma Liming Date: Mon, 12 Jan 2026 10:26:39 +0800 Subject: [PATCH 6/8] Update framework/test/Volo.Abp.Timing.Tests/Volo/Abp/Timing/TimeZoneHelper_Tests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Volo/Abp/Timing/TimeZoneHelper_Tests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/test/Volo.Abp.Timing.Tests/Volo/Abp/Timing/TimeZoneHelper_Tests.cs b/framework/test/Volo.Abp.Timing.Tests/Volo/Abp/Timing/TimeZoneHelper_Tests.cs index ee770a2fa4..54d8d37361 100644 --- a/framework/test/Volo.Abp.Timing.Tests/Volo/Abp/Timing/TimeZoneHelper_Tests.cs +++ b/framework/test/Volo.Abp.Timing.Tests/Volo/Abp/Timing/TimeZoneHelper_Tests.cs @@ -28,7 +28,7 @@ public class TimeZoneHelper_Tests : AbpIntegratedTest var expectedName = $"{validTimeZoneId} ({TimeZoneHelper.GetTimezoneOffset(expectedTimeZoneInfo)})"; result[0].Name.ShouldBe(expectedName); - result[0].Value.ShouldBe(expectedTimeZoneInfo.StandardName); + result[0].Value.ShouldBe(validTimeZoneId); } [Fact] From 6ee810bcb7e4f956f738398be6c72990760bc5be Mon Sep 17 00:00:00 2001 From: Ma Liming Date: Mon, 12 Jan 2026 10:26:45 +0800 Subject: [PATCH 7/8] Update framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs index 446a4a483c..0bf9169989 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs @@ -20,7 +20,8 @@ public static class TimeZoneHelper } /// - /// Builds a that includes the UTC offset in the name; returns null if the id is not found. + /// Builds a with the original timezone ID in Value and a display name that includes + /// the UTC offset in the Name property; returns null if the id is not found. /// public static NameValue? TryCreateNameValueWithOffset(NameValue timeZone) { From f70b6f1f827f8464b95dd5deb757eed905d75c41 Mon Sep 17 00:00:00 2001 From: Ma Liming Date: Mon, 12 Jan 2026 14:34:16 +0800 Subject: [PATCH 8/8] Change exception handling in TimeZoneHelper Catch general exceptions in TimeZoneHelper to handle unexpected errors. --- framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs index 0bf9169989..23446069a9 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimeZoneHelper.cs @@ -31,7 +31,7 @@ public static class TimeZoneHelper var name = $"{timeZone.Name} ({GetTimezoneOffset(timeZoneInfo)})"; return new NameValue(name, timeZone.Name); } - catch (TimeZoneNotFoundException) + catch (Exception) { // Invalid or unknown timezone IDs are expected here (e.g. from user input or // external sources). We intentionally swallow this exception and return null