Program.cs
The complete calendar program:
using System.Collections.Immutable;
using Funcky.Extensions;
using Funcky;
using Funcky.Monads;
using static System.Globalization.CultureInfo;
using static Funcky.Functional;
namespace Calendar;
internal static class StringExtensions
{
public static string Center(this string text, int width)
=> (width - text.Length) switch
{
0 => text,
1 => $" {text}",
_ => Center($" {text} ", width),
};
}
internal class Program
{
private const int FirstDayOfTheWeek = 0;
private const int MonthsPerRow = 3;
private const int DaysInAWeek = 7;
private const int WidthOfDay = 3;
private const int WidthOfAWeek = WidthOfDay * DaysInAWeek;
private const string MonthNameFormat = "MMMM";
private static void Main(string[] args)
=> Console.WriteLine(CreateCalendarString(ExtractYear(args).GetOrElse(DateTime.Now.Year)));
private static Option<int> ExtractYear(IEnumerable<string> args)
=> args.FirstOrNone()
.AndThen(ParseExtensions.ParseInt32OrNone);
private static string CreateCalendarString(int year)
=> Sequence.Successors(JanuaryFirst(year), NextDay)
.TakeWhile(IsSameYear(year))
.AdjacentGroupBy(day => day.Month)
.Select(LayoutMonth)
.Chunk(MonthsPerRow)
.Select(EnumerableExtensions.Transpose)
.Select(JoinLine)
.SelectMany(Identity)
.JoinToString(Environment.NewLine);
private static DateOnly NextDay(DateOnly day)
=> day.AddDays(1);
private static DateOnly JanuaryFirst(int year)
=> new(year: year, month: 1, day: 1);
private static Func<DateOnly, bool> IsSameYear(int year)
=> day
=> day.Year == year;
private static IEnumerable<string> JoinLine(IEnumerable<IEnumerable<string>> sequence)
=> sequence.Select(t => t.JoinToString(" "));
private static IEnumerable<string> LayoutMonth(IEnumerable<DateOnly> month)
=> ImmutableList<string>.Empty
.Add(CenteredMonthName(month))
.AddRange(FormatWeeks(month))
.Add(new string(' ', WidthOfAWeek));
private static IEnumerable<string> FormatWeeks(IEnumerable<DateOnly> month)
=> month
.AdjacentGroupBy(GetWeekOfYear)
.Select(FormatWeek);
private static string FormatWeek(IGrouping<int, DateOnly> week)
=> PadWeek(week.Select(FormatDay).ConcatToString(), week);
private static string FormatDay(DateOnly day)
=> $"{day.Day,WidthOfDay}";
private static string PadWeek(string formattedWeek, IEnumerable<DateOnly> week)
=> StartsOnFirstDayOfWeek(week)
? $"{formattedWeek,-WidthOfAWeek}"
: $"{formattedWeek,WidthOfAWeek}";
private static bool StartsOnFirstDayOfWeek(IEnumerable<DateOnly> week)
=> NthDayOfWeek(week.First().DayOfWeek) is FirstDayOfTheWeek;
private static int NthDayOfWeek(DayOfWeek dayOfWeek)
=> (dayOfWeek + DaysInAWeek - CurrentCulture.DateTimeFormat.FirstDayOfWeek) % DaysInAWeek;
private static int GetWeekOfYear(DateOnly dateTime)
=> CurrentCulture
.Calendar
.GetWeekOfYear(
dateTime.ToDateTime(default),
CurrentCulture.DateTimeFormat.CalendarWeekRule,
CurrentCulture.DateTimeFormat.FirstDayOfWeek);
private static string CenteredMonthName(IEnumerable<DateOnly> month)
=> month
.First()
.ToString(MonthNameFormat)
.Center(WidthOfAWeek);
}