From f6c5720e7a9e6a16d49dcbae32d5da8ad3433ae1 Mon Sep 17 00:00:00 2001 From: Marko Prosen <106885623+maprosen@users.noreply.github.com> Date: Sun, 12 Nov 2023 04:13:03 +0100 Subject: [PATCH] Fix TimePicker empty designator on 12 hour clock (#13469) * Test - On empty PM/AM designator on culture info the time picker should show AM/PM * On empty PM/AM designator on culture info the time picker should show AM/PM --- .../DateTimePickers/DateTimePickerPanel.cs | 4 +- .../DateTimePickers/TimePicker.cs | 7 ++-- src/Avalonia.Controls/Utils/TimeUtils.cs | 16 ++++++++ .../TimePickerTests.cs | 34 +++++++++++++++++ .../UseEmptyDesignatorCultureAttribute.cs | 37 +++++++++++++++++++ 5 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 src/Avalonia.Controls/Utils/TimeUtils.cs create mode 100644 tests/Avalonia.UnitTests/UseEmptyDesignatorCultureAttribute.cs diff --git a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs index daa8f1ce13..cbe2eddf53 100644 --- a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs +++ b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Linq; using Avalonia.Controls.Presenters; +using Avalonia.Controls.Utils; using Avalonia.Input; using Avalonia.Input.GestureRecognizers; using Avalonia.Interactivity; @@ -516,8 +517,7 @@ namespace Avalonia.Controls.Primitives case DateTimePickerPanelType.Minute: return new TimeSpan(0, value, 0).ToString(ItemFormat); case DateTimePickerPanelType.TimePeriod: - return value == MinimumValue ? CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator : - CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator; + return value == MinimumValue ? TimeUtils.GetAMDesignator() : TimeUtils.GetPMDesignator(); default: return ""; } diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs index acc7808251..e5914c90ee 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs @@ -5,6 +5,7 @@ using Avalonia.Data; using Avalonia.Layout; using System; using System.Globalization; +using Avalonia.Controls.Utils; namespace Avalonia.Controls { @@ -227,8 +228,7 @@ namespace Avalonia.Controls _minuteText.Text = newTime.ToString("mm"); PseudoClasses.Set(":hasnotime", false); - _periodText.Text = time.Value.Hours >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator : - CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator; + _periodText.Text = time.Value.Hours >= 12 ? TimeUtils.GetPMDesignator() : TimeUtils.GetAMDesignator(); } else { @@ -236,8 +236,7 @@ namespace Avalonia.Controls _minuteText.Text = "minute"; PseudoClasses.Set(":hasnotime", true); - _periodText.Text = DateTime.Now.Hour >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator : - CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator; + _periodText.Text = DateTime.Now.Hour >= 12 ? TimeUtils.GetPMDesignator() : TimeUtils.GetAMDesignator(); } } diff --git a/src/Avalonia.Controls/Utils/TimeUtils.cs b/src/Avalonia.Controls/Utils/TimeUtils.cs new file mode 100644 index 0000000000..ea93e90fad --- /dev/null +++ b/src/Avalonia.Controls/Utils/TimeUtils.cs @@ -0,0 +1,16 @@ +using System.Globalization; + +namespace Avalonia.Controls.Utils; + +internal static class TimeUtils +{ + public static string GetPMDesignator() => + !string.IsNullOrEmpty(CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator) ? + CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator : + CultureInfo.InvariantCulture.DateTimeFormat.PMDesignator; + + public static string GetAMDesignator() => + !string.IsNullOrEmpty(CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator) ? + CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator : + CultureInfo.InvariantCulture.DateTimeFormat.AMDesignator; +} diff --git a/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs b/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs index 1449f3f759..793c2c6b86 100644 --- a/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs @@ -98,6 +98,40 @@ namespace Avalonia.Controls.UnitTests Assert.True(minuteText.Text == "minute"); } } + + [Fact] + [UseEmptyDesignatorCulture] + public void Using_12HourClock_On_Culture_With_Empty_Period_Should_Show_Period() + { + using (UnitTestApplication.Start(Services)) + { + TimePicker timePicker = new TimePicker() + { + Template = CreateTemplate(), ClockIdentifier = "12HourClock", + }; + timePicker.ApplyTemplate(); + + var desc = timePicker.GetVisualDescendants(); + Assert.True(desc.Count() > 1); //Should be layoutroot grid & button + + Assert.True(desc.ElementAt(1) is Button); + + var container = (desc.ElementAt(1) as Button).Content as Grid; + Assert.True(container != null); + + var periodTextHost = container.Children[4] as Border; + Assert.NotNull(periodTextHost); + var periodText = periodTextHost.Child as TextBlock; + Assert.NotNull(periodTextHost); + + TimeSpan ts = TimeSpan.FromHours(10); + timePicker.SelectedTime = ts; + Assert.False(string.IsNullOrEmpty(periodText.Text)); + + timePicker.SelectedTime = null; + Assert.False(string.IsNullOrEmpty(periodText.Text)); + } + } private static TestServices Services => TestServices.MockThreadingInterface.With( fontManagerImpl: new HeadlessFontManagerStub(), diff --git a/tests/Avalonia.UnitTests/UseEmptyDesignatorCultureAttribute.cs b/tests/Avalonia.UnitTests/UseEmptyDesignatorCultureAttribute.cs new file mode 100644 index 0000000000..1d10c584a1 --- /dev/null +++ b/tests/Avalonia.UnitTests/UseEmptyDesignatorCultureAttribute.cs @@ -0,0 +1,37 @@ +#nullable enable + +using System; +using System.Globalization; +using System.Reflection; +using Xunit.Sdk; + +namespace Avalonia.UnitTests; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public sealed class UseEmptyDesignatorCultureAttribute : BeforeAfterTestAttribute +{ + private CultureInfo? _previousCulture; + private CultureInfo? _previousUICulture; + + private CultureInfo CultureInfo { get; } = + new(string.Empty, false) { DateTimeFormat = { AMDesignator = string.Empty, PMDesignator = string.Empty } }; + + public override void Before(MethodInfo methodUnderTest) + { + base.Before(methodUnderTest); + + _previousCulture = CultureInfo.CurrentCulture; + _previousUICulture = CultureInfo.CurrentUICulture; + + CultureInfo.CurrentCulture = CultureInfo; + CultureInfo.CurrentUICulture = CultureInfo; + } + + public override void After(MethodInfo methodUnderTest) + { + CultureInfo.CurrentCulture = _previousCulture!; + CultureInfo.CurrentUICulture = _previousUICulture!; + + base.After(methodUnderTest); + } +}