//
// DatePicker.cs: DatePicker control
//
// Author: Maciej Winnik
//
using System.Data;
using System.Globalization;
namespace Terminal.Gui;
/// The Date Picker.
public class DatePicker : View
{
private TableView _calendar;
private DateTime _date;
private DateField _dateField;
private Label _dateLabel;
private Button _nextMonthButton;
private Button _previousMonthButton;
private DataTable _table;
/// Initializes a new instance of .
public DatePicker () { SetInitialProperties (DateTime.Now); }
/// Initializes a new instance of with the specified date.
public DatePicker (DateTime date) { SetInitialProperties (date); }
/// CultureInfo for date. The default is CultureInfo.CurrentCulture.
public CultureInfo Culture
{
get => CultureInfo.CurrentCulture;
set
{
if (value is { })
{
CultureInfo.CurrentCulture = value;
Text = Date.ToString (Format);
}
}
}
/// Get or set the date.
public DateTime Date
{
get => _date;
set
{
_date = value;
Text = _date.ToString (Format);
}
}
private string Format => StandardizeDateFormat (Culture.DateTimeFormat.ShortDatePattern);
///
protected override void Dispose (bool disposing)
{
_dateLabel.Dispose ();
_calendar.Dispose ();
_dateField.Dispose ();
_table.Dispose ();
_previousMonthButton.Dispose ();
_nextMonthButton.Dispose ();
base.Dispose (disposing);
}
private void ChangeDayDate (int day)
{
_date = new DateTime (_date.Year, _date.Month, day);
_dateField.Date = _date;
CreateCalendar ();
}
private void CreateCalendar () { _calendar.Table = new DataTableSource (_table = CreateDataTable (_date.Month, _date.Year)); }
private DataTable CreateDataTable (int month, int year)
{
_table = new DataTable ();
GenerateCalendarLabels ();
int amountOfDaysInMonth = DateTime.DaysInMonth (year, month);
var dateValue = new DateTime (year, month, 1);
DayOfWeek dayOfWeek = dateValue.DayOfWeek;
_table.Rows.Add (new object [6]);
for (var i = 1; i <= amountOfDaysInMonth; i++)
{
_table.Rows [^1] [(int)dayOfWeek] = i;
if (dayOfWeek == DayOfWeek.Saturday && i != amountOfDaysInMonth)
{
_table.Rows.Add (new object [7]);
}
dayOfWeek = dayOfWeek == DayOfWeek.Saturday ? DayOfWeek.Sunday : dayOfWeek + 1;
}
int missingRows = 6 - _table.Rows.Count;
for (var i = 0; i < missingRows; i++)
{
_table.Rows.Add (new object [7]);
}
return _table;
}
private void DateField_DateChanged (object sender, DateTimeEventArgs e)
{
Date = e.NewValue;
if (e.NewValue.Date.Day != _date.Day)
{
SelectDayOnCalendar (e.NewValue.Day);
}
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);
}
private void GenerateCalendarLabels ()
{
_calendar.Style.ColumnStyles.Clear ();
for (var i = 0; i < 7; i++)
{
string abbreviatedDayName =
CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedDayName ((DayOfWeek)i);
_calendar.Style.ColumnStyles.Add (
i,
new ColumnStyle
{
MaxWidth = abbreviatedDayName.Length,
MinWidth = abbreviatedDayName.Length,
MinAcceptableWidth = abbreviatedDayName.Length
}
);
_table.Columns.Add (abbreviatedDayName);
}
// TODO: Get rid of the +7 which is hackish
_calendar.Width = _calendar.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 7;
}
private string GetBackButtonText () { return Glyphs.LeftArrow + Glyphs.LeftArrow.ToString (); }
private string GetForwardButtonText () { return Glyphs.RightArrow + Glyphs.RightArrow.ToString (); }
private void SelectDayOnCalendar (int day)
{
for (var i = 0; i < _table.Rows.Count; i++)
{
for (var j = 0; j < _table.Columns.Count; j++)
{
if (_table.Rows [i] [j].ToString () == day.ToString ())
{
_calendar.SetSelection (j, i, false);
return;
}
}
}
}
private void SetInitialProperties (DateTime date)
{
_date = date;
BorderStyle = LineStyle.Single;
Date = date;
_dateLabel = new Label { X = 0, Y = 0, Text = "Date: " };
CanFocus = true;
_calendar = new TableView
{
Id = "_calendar",
X = 0,
Y = Pos.Bottom (_dateLabel),
Height = 11,
Style = new TableStyle
{
ShowHeaders = true,
ShowHorizontalBottomline = true,
ShowVerticalCellLines = true,
ExpandLastColumn = true,
},
MultiSelect = false
};
_dateField = new DateField (DateTime.Now)
{
Id = "_dateField",
X = Pos.Right (_dateLabel),
Y = 0,
Width = Dim.Width (_calendar) - Dim.Width (_dateLabel),
Height = 1,
Culture = Culture
};
_previousMonthButton = new Button
{
Id = "_previousMonthButton",
X = Pos.Center () - 2,
Y = Pos.Bottom (_calendar) - 1,
Width = 2,
Text = GetBackButtonText (),
WantContinuousButtonPressed = true,
NoPadding = true,
NoDecorations = true,
ShadowStyle = ShadowStyle.None
};
_previousMonthButton.Accepting += (sender, e) =>
{
Date = _date.AddMonths (-1);
CreateCalendar ();
_dateField.Date = Date;
};
_nextMonthButton = new Button
{
Id = "_nextMonthButton",
X = Pos.Right (_previousMonthButton) + 2,
Y = Pos.Bottom (_calendar) - 1,
Width = 2,
Text = GetForwardButtonText (),
WantContinuousButtonPressed = true,
NoPadding = true,
NoDecorations = true,
ShadowStyle = ShadowStyle.None
};
_nextMonthButton.Accepting += (sender, e) =>
{
Date = _date.AddMonths (1);
CreateCalendar ();
_dateField.Date = Date;
};
CreateCalendar ();
SelectDayOnCalendar (_date.Day);
_calendar.CellActivated += (sender, e) =>
{
object dayValue = _table.Rows [e.Row] [e.Col];
if (dayValue is null)
{
return;
}
bool isDay = int.TryParse (dayValue.ToString (), out int day);
if (!isDay)
{
return;
}
ChangeDayDate (day);
SelectDayOnCalendar (day);
Text = _date.ToString (Format);
};
Width = Dim.Auto (DimAutoStyle.Content);
Height = Dim.Auto (DimAutoStyle.Content);
_dateField.DateChanged += DateField_DateChanged;
Add (_dateLabel, _dateField, _calendar, _previousMonthButton, _nextMonthButton);
}
///
protected override bool OnDrawingText (Rectangle viewport) { return true; }
private static string StandardizeDateFormat (string format)
{
return 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"
};
}
}