2
0
Эх сурвалжийг харах

Fixes #3196: DatePicker Range Guard (#3197)

* Disable next and previous button when Date would go out of range

* Make DatePicker compatible with DateField

* Update unit tests

* Fix unit tests

* Fix test comments
Maciej 1 жил өмнө
parent
commit
ad234aa870

+ 60 - 13
Terminal.Gui/Views/DatePicker.cs

@@ -24,10 +24,21 @@ public class DatePicker : View {
 
 	private DateTime _date = DateTime.Now;
 
+
 	/// <summary>
-	/// Format of date. The default is MM/dd/yyyy.
+	/// CultureInfo for date. The default is CultureInfo.CurrentCulture.
 	/// </summary>
-	public string Format { get; set; } = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
+	public CultureInfo Culture {
+		get => CultureInfo.CurrentCulture;
+		set {
+			if (value is not null) {
+				CultureInfo.CurrentCulture = value;
+				Text = Date.ToString (Format);
+			}
+		}
+	}
+
+	private string Format => StandardizeDateFormat (Culture.DateTimeFormat.ShortDatePattern);
 
 	/// <summary>
 	/// Get or set the date.
@@ -53,15 +64,6 @@ public class DatePicker : View {
 		SetInitialProperties (date);
 	}
 
-	/// <summary>
-	/// Initializes a new instance of <see cref="DatePicker"/> with the specified date and format.
-	/// </summary>
-	public DatePicker (DateTime date, string format)
-	{
-		Format = format;
-		SetInitialProperties (date);
-	}
-
 	private void SetInitialProperties (DateTime date)
 	{
 		Title = "Date Picker";
@@ -77,7 +79,8 @@ public class DatePicker : View {
 			X = Pos.Right (_dateLabel),
 			Y = 0,
 			Width = Dim.Fill (1),
-			Height = 1
+			Height = 1,
+			Culture = Culture,
 		};
 
 		_calendar = new TableView () {
@@ -146,10 +149,22 @@ public class DatePicker : View {
 
 	private void DateField_DateChanged (object sender, DateTimeEventArgs<DateTime> e)
 	{
+		Date = e.NewValue;
 		if (e.NewValue.Date.Day != _date.Day) {
 			SelectDayOnCalendar (e.NewValue.Day);
 		}
-		Date = e.NewValue;
+
+		if (_date.Month == DateTime.MinValue.Month && _date.Year == DateTime.MinValue.Year) {
+			_previousMonthButton.Enabled = false;
+		} else {
+			_previousMonthButton.Enabled = true;
+		}
+
+		if (_date.Month == DateTime.MaxValue.Month && _date.Year == DateTime.MaxValue.Year) {
+			_nextMonthButton.Enabled = false;
+		} else {
+			_nextMonthButton.Enabled = true;
+		}
 		CreateCalendar ();
 		SelectDayOnCalendar (_date.Day);
 	}
@@ -226,6 +241,38 @@ public class DatePicker : View {
 
 	private string GetBackButtonText () => Glyphs.LeftArrow.ToString () + Glyphs.LeftArrow.ToString ();
 
+	private static string StandardizeDateFormat (string format) =>
+	    format switch {
+		    "MM/dd/yyyy" => "MM/dd/yyyy",
+		    "yyyy-MM-dd" => "yyyy-MM-dd",
+		    "yyyy/MM/dd" => "yyyy/MM/dd",
+		    "dd/MM/yyyy" => "dd/MM/yyyy",
+		    "d?/M?/yyyy" => "dd/MM/yyyy",
+		    "dd.MM.yyyy" => "dd.MM.yyyy",
+		    "dd-MM-yyyy" => "dd-MM-yyyy",
+		    "dd/MM yyyy" => "dd/MM/yyyy",
+		    "d. M. yyyy" => "dd.MM.yyyy",
+		    "yyyy.MM.dd" => "yyyy.MM.dd",
+		    "g yyyy/M/d" => "yyyy/MM/dd",
+		    "d/M/yyyy" => "dd/MM/yyyy",
+		    "d?/M?/yyyy g" => "dd/MM/yyyy",
+		    "d-M-yyyy" => "dd-MM-yyyy",
+		    "d.MM.yyyy" => "dd.MM.yyyy",
+		    "d.MM.yyyy '?'." => "dd.MM.yyyy",
+		    "M/d/yyyy" => "MM/dd/yyyy",
+		    "d. M. yyyy." => "dd.MM.yyyy",
+		    "d.M.yyyy." => "dd.MM.yyyy",
+		    "g yyyy-MM-dd" => "yyyy-MM-dd",
+		    "d.M.yyyy" => "dd.MM.yyyy",
+		    "d/MM/yyyy" => "dd/MM/yyyy",
+		    "yyyy/M/d" => "yyyy/MM/dd",
+		    "dd. MM. yyyy." => "dd.MM.yyyy",
+		    "yyyy. MM. dd." => "yyyy.MM.dd",
+		    "yyyy. M. d." => "yyyy.MM.dd",
+		    "d. MM. yyyy" => "dd.MM.yyyy",
+		    _ => "dd/MM/yyyy"
+	    };
+
 	///<inheritdoc/>
 	protected override void Dispose (bool disposing)
 	{

+ 60 - 18
UnitTests/Views/DatePickerTests.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Globalization;
-using Terminal.Gui;
 using Xunit;
 
 namespace Terminal.Gui.ViewsTests;
@@ -8,26 +7,38 @@ namespace Terminal.Gui.ViewsTests;
 public class DatePickerTests {
 
 	[Fact]
-	public void DatePicker_SetFormat_ShouldChangeFormat ()
+	public void DatePicker_ChangingCultureChangesFormat ()
 	{
-		var datePicker = new DatePicker {
-			Format = "dd/MM/yyyy"
-		};
-		Assert.Equal ("dd/MM/yyyy", datePicker.Format);
+		var date = new DateTime (2000, 7, 23);
+		var datePicker = new DatePicker (date);
+
+		datePicker.Culture = CultureInfo.GetCultureInfo ("en-GB");
+		Assert.Equal ("23/07/2000", datePicker.Text);
+
+		datePicker.Culture = CultureInfo.GetCultureInfo ("pl-PL");
+		Assert.Equal ("23.07.2000", datePicker.Text);
+
+		// Deafult date format for en-US is M/d/yyyy but we are using StandardizeDateFormat method
+		// to convert it to the format that has 2 digits for month and day.
+		datePicker.Culture = CultureInfo.GetCultureInfo ("en-US");
+		Assert.Equal ("07/23/2000", datePicker.Text);
 	}
 
 	[Fact]
 	public void DatePicker_Initialize_ShouldSetCurrentDate ()
 	{
 		var datePicker = new DatePicker ();
-		var format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
-		Assert.Equal (DateTime.Now.ToString (format), datePicker.Text);
+		Assert.Equal (DateTime.Now.Date.Day, datePicker.Date.Day);
+		Assert.Equal (DateTime.Now.Date.Month, datePicker.Date.Month);
+		Assert.Equal (DateTime.Now.Date.Year, datePicker.Date.Year);
 	}
 
 	[Fact]
 	public void DatePicker_SetDate_ShouldChangeText ()
 	{
-		var datePicker = new DatePicker ();
+		var datePicker = new DatePicker () {
+			Culture = CultureInfo.GetCultureInfo ("en-GB")
+		};
 		var newDate = new DateTime (2024, 1, 15);
 		var format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
 
@@ -36,19 +47,50 @@ public class DatePickerTests {
 	}
 
 	[Fact]
-	public void DatePicker_ShowDatePickerDialog_ShouldChangeDate ()
+	[AutoInitShutdown]
+	public void DatePicker_ShouldNot_SetDateOutOfRange_UsingPreviousMonthButton ()
 	{
-		var datePicker = new DatePicker ();
-		var format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
-		var originalDate = datePicker.Date;
+		var date = new DateTime (1, 2, 15);
+		var datePicker = new DatePicker (date);
 
-		datePicker.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Clicked, X = 4, Y = 1 });
+		// Move focus to previous month button
+		Application.Top.Add (datePicker);
+		Application.Begin (Application.Top);
 
-		var newDate = new DateTime (2024, 2, 20);
-		datePicker.Date = newDate;
+		// set focus to the previous month button
+		datePicker.FocusNext ();
+		datePicker.FocusNext ();
 
-		Assert.Equal (newDate.ToString (format), datePicker.Text);
+		// Change month to January 
+		Assert.True (datePicker.NewKeyDownEvent (new (KeyCode.Enter)));
+		Assert.Equal (1, datePicker.Date.Month);
+
+		// Date should not change as previous month button is disabled
+		Assert.False (datePicker.NewKeyDownEvent (new (KeyCode.Enter)));
+		Assert.Equal (1, datePicker.Date.Month);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void DatePicker_ShouldNot_SetDateOutOfRange_UsingNextMonthButton ()
+	{
+		var date = new DateTime (9999, 11, 15);
+		var datePicker = new DatePicker (date);
+
+		Application.Top.Add (datePicker);
+		Application.Begin (Application.Top);
+
+		// Set focus to next month button
+		datePicker.FocusNext ();
+		datePicker.FocusNext ();
+		datePicker.FocusNext ();
+
+		// Change month to December
+		Assert.True (datePicker.NewKeyDownEvent (new (KeyCode.Enter)));
+		Assert.Equal (12, datePicker.Date.Month);
 
-		datePicker.Date = originalDate;
+		// Date should not change as next month button is disabled
+		Assert.False (datePicker.NewKeyDownEvent (new (KeyCode.Enter)));
+		Assert.Equal (12, datePicker.Date.Month);
 	}
 }