// // DatePicker.cs: DatePicker control // // Author: Maciej Winnik // using System.Data; using System.Globalization; namespace Terminal.Gui.Views; /// Lets the user pick a date from a visual calendar. 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 (_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 (); GenerateCalendarLabels (); int amountOfDaysInMonth = DateTime.DaysInMonth (year, month); DateTime 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, EventArgs e) { Date = e.Value; if (e.Value.Date.Day != _date.Day) { SelectDayOnCalendar (e.Value.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() { 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 static string GetBackButtonText () { return Glyphs.LeftArrow + Glyphs.LeftArrow.ToString (); } private static 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() { X = 0, Y = 0, Text = "Date: " }; CanFocus = true; _calendar = new() { Id = "_calendar", X = 0, Y = Pos.Bottom (_dateLabel), Height = 11, Style = new() { ShowHeaders = true, ShowHorizontalBottomline = true, ShowVerticalCellLines = true, ExpandLastColumn = true }, MultiSelect = false }; _dateField = new (DateTime.Now) { Id = "_dateField", X = Pos.Right (_dateLabel), Y = 0, Width = Dim.Width (_calendar) - Dim.Width (_dateLabel), Height = 1, Culture = Culture }; _previousMonthButton = new() { 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 += (_, _) => AdjustMonth (-1); _nextMonthButton = new() { 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 += (_, _) => AdjustMonth (1); CreateCalendar (); SelectDayOnCalendar (_date.Day); _calendar.CellActivated += (_, e) => { object dayValue = _table!.Rows [e.Row] [e.Col]; 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); } private void AdjustMonth (int offset) { Date = _date.AddMonths (offset); CreateCalendar (); _dateField!.Date = Date; } /// protected override bool OnDrawingText () { 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" }; } }