using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Spectre.Console.Internal; using Spectre.Console.Rendering; namespace Spectre.Console { /// /// A renderable calendar. /// public sealed class Calendar : JustInTimeRenderable, IHasCulture, IHasTableBorder, IAlignable { private const int NumberOfWeekDays = 7; private const int ExpectedRowCount = 6; private readonly ListWithCallback _calendarEvents; private int _year; private int _month; private int _day; private TableBorder _border; private bool _useSafeBorder; private Style? _borderStyle; private CultureInfo _culture; private Style _highlightStyle; private bool _showHeader; private Style? _headerStyle; private Justify? _alignment; /// /// Gets or sets the calendar year. /// public int Year { get => _year; set => MarkAsDirty(() => _year = value); } /// /// Gets or sets the calendar month. /// public int Month { get => _month; set => MarkAsDirty(() => _month = value); } /// /// Gets or sets the calendar day. /// public int Day { get => _day; set => MarkAsDirty(() => _day = value); } /// public TableBorder Border { get => _border; set => MarkAsDirty(() => _border = value); } /// public bool UseSafeBorder { get => _useSafeBorder; set => MarkAsDirty(() => _useSafeBorder = value); } /// public Style? BorderStyle { get => _borderStyle; set => MarkAsDirty(() => _borderStyle = value); } /// /// Gets or sets the calendar's . /// public CultureInfo Culture { get => _culture; set => MarkAsDirty(() => _culture = value); } /// /// Gets or sets the calendar's highlight . /// public Style HightlightStyle { get => _highlightStyle; set => MarkAsDirty(() => _highlightStyle = value); } /// /// Gets or sets a value indicating whether or not the calendar header should be shown. /// public bool ShowHeader { get => _showHeader; set => MarkAsDirty(() => _showHeader = value); } /// /// Gets or sets the header style. /// public Style? HeaderStyle { get => _headerStyle; set => MarkAsDirty(() => _headerStyle = value); } /// public Justify? Alignment { get => _alignment; set => MarkAsDirty(() => _alignment = value); } /// /// Gets a list containing all calendar events. /// public IList CalendarEvents => _calendarEvents; /// /// Initializes a new instance of the class. /// /// The calendar date. public Calendar(DateTime date) : this(date.Year, date.Month, date.Day) { } /// /// Initializes a new instance of the class. /// /// The calendar year. /// The calendar month. public Calendar(int year, int month) : this(year, month, 1) { } /// /// Initializes a new instance of the class. /// /// The calendar year. /// The calendar month. /// The calendar day. public Calendar(int year, int month, int day) { _year = year; _month = month; _day = day; _border = TableBorder.Square; _useSafeBorder = true; _borderStyle = null; _culture = CultureInfo.InvariantCulture; _highlightStyle = new Style(foreground: Color.Blue); _showHeader = true; _calendarEvents = new ListWithCallback(() => MarkAsDirty()); } /// protected override IRenderable Build() { var culture = Culture ?? CultureInfo.InvariantCulture; var table = new Table { Border = _border, UseSafeBorder = _useSafeBorder, BorderStyle = _borderStyle, Alignment = _alignment, }; if (ShowHeader) { var heading = new DateTime(Year, Month, Day).ToString("Y", culture).EscapeMarkup(); table.Title = new TableTitle(heading, HeaderStyle); } // Add columns foreach (var order in GetWeekdays()) { table.AddColumn(new TableColumn(order.GetAbbreviatedDayName(culture))); } var row = new List(); var currentDay = 1; var weekday = culture.DateTimeFormat.FirstDayOfWeek; var weekdays = BuildWeekDayTable(); var daysInMonth = DateTime.DaysInMonth(Year, Month); while (currentDay <= daysInMonth) { if (weekdays[currentDay - 1] == weekday) { if (_calendarEvents.Any(e => e.Month == Month && e.Day == currentDay)) { row.Add(new Markup(currentDay.ToString(CultureInfo.InvariantCulture) + "*", _highlightStyle)); } else { row.Add(new Text(currentDay.ToString(CultureInfo.InvariantCulture))); } currentDay++; } else { // Add empty cell row.Add(Text.Empty); } if (row.Count == NumberOfWeekDays) { // Flush row table.AddRow(row.ToArray()); row.Clear(); } weekday = weekday.GetNextWeekDay(); } if (row.Count > 0) { // Flush row table.AddRow(row.ToArray()); row.Clear(); } // We want all calendars to have the same height. if (table.Rows.Count < ExpectedRowCount) { var diff = Math.Max(0, ExpectedRowCount - table.Rows.Count); for (var i = 0; i < diff; i++) { table.AddEmptyRow(); } } return table; } private DayOfWeek[] GetWeekdays() { var culture = Culture ?? CultureInfo.InvariantCulture; var days = new DayOfWeek[7]; days[0] = culture.DateTimeFormat.FirstDayOfWeek; for (var i = 1; i < 7; i++) { days[i] = days[i - 1].GetNextWeekDay(); } return days; } private DayOfWeek[] BuildWeekDayTable() { var result = new List(); for (var day = 0; day < DateTime.DaysInMonth(Year, Month); day++) { result.Add(new DateTime(Year, Month, day + 1).DayOfWeek); } return result.ToArray(); } } }