Skip to content

Commit

Permalink
Fixes #3196: DatePicker Range Guard (#3197)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
MaciekWin3 authored Jan 20, 2024
1 parent 33070ec commit ad234aa
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 31 deletions.
73 changes: 60 additions & 13 deletions Terminal.Gui/Views/DatePicker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -53,15 +64,6 @@ public DatePicker (DateTime date)
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";
Expand All @@ -77,7 +79,8 @@ private void SetInitialProperties (DateTime date)
X = Pos.Right (_dateLabel),
Y = 0,
Width = Dim.Fill (1),
Height = 1
Height = 1,
Culture = Culture,
};

_calendar = new TableView () {
Expand Down Expand Up @@ -146,10 +149,22 @@ private void SetInitialProperties (DateTime date)

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);
}
Expand Down Expand Up @@ -226,6 +241,38 @@ private void SelectDayOnCalendar (int day)

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)
{
Expand Down
78 changes: 60 additions & 18 deletions UnitTests/Views/DatePickerTests.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,44 @@
using System;
using System.Globalization;
using Terminal.Gui;
using Xunit;

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;

Expand All @@ -36,19 +47,50 @@ public void DatePicker_SetDate_ShouldChangeText ()
}

[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);
}
}

0 comments on commit ad234aa

Please sign in to comment.