// Version: Feb 2020
    //
    // The following C# program illustrates how easy it is to apply NexCalendar:
    //
    // Since software stores dates by its ordinal days from a special date,
    // it is easy to convert any Gregorian date to NexCalendar date.	
    //
    public static class NexCalendar
    {
        // Weekday = Day of the Week (1 = Monday ... 7 = Sunday, 8 = Sunday)
        // Yearday = Ordinal Day of the Year (1 ... 365, 366 = Leap Sunday)
        public const int MaxDaysInYear = 366;  // 366 is equal to the Leap Sunday
        public const int DaysByWeek35 = 35 * 7 + 1;  // It is the annual long week with 8 days
        public static readonly int[] DaysInMonths
            = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
        public static readonly int[] DaysByMonths
            = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };
        private static bool InvalidInt(int x, int max) => (x < 1 || x > max);

        public static int GetYeardayFromMonthDay(int month, int day)
        {
            if (InvalidInt(month, 12) || InvalidInt(day, DaysInMonths[month])) return 0;
            return (DaysByMonths[month - 1] + day);
        }
        public static (int Month, int Day) GetMonthDay(int yearday)
        {
            if (InvalidInt(yearday, MaxDaysInYear)) return (0, 0);
            int month = 2;
            if (yearday > DaysByMonths[4]) month = 6;
            if (yearday > DaysByMonths[8]) month = 10;
            if (yearday > DaysByMonths[month]) month++; else month--;
            if (yearday > DaysByMonths[month]) month++;
            return (month, yearday - DaysByMonths[month - 1]);
        }
        public static int GetYeardayFromWeekDate(int week, int weekday)
        {
            if (week == 35 && weekday == 8) return DaysByWeek35;
            if (week == 52 && weekday == 8) return MaxDaysInYear;
            if (InvalidInt(week, 52) || InvalidInt(weekday, 7)) return 0;
            int yearday = (week - 1) * 7 + weekday;
            if (week > 35) yearday++;
            return yearday;
        }
        public static (int Week, int Weekday) GetWeekDate(int yearday)
        {
            if (InvalidInt(yearday, MaxDaysInYear)) return (0, 0); // Unknown
            if (yearday == MaxDaysInYear) return (52, 8); // The last week
            if (yearday == DaysByWeek35) return (35, 8); // The last week
            if (yearday >= DaysByWeek35) yearday--;
            int week = (1 + (yearday - 1) / 7);
            int weekday = yearday - ((week - 1) * 7);
            return (week, weekday);
        }
        public static (int Week, int Weekday) GetWeekDateFromMonthDay(int month, int day)
            => GetWeekDate(GetYeardayFromMonthDay(month, day));
        public static (int Month, int Day) GetMonthDayFromWeekDate(int week, int weekday)
            => GetMonthDay(GetYeardayFromWeekDate(week, weekday));
        public static (string LongName, string ShortName) GetWeekdayName(int week, int weekday)
        {
            if ((week == 35 || week == 52) && weekday == 8) weekday = 7; // Special Sunday
            if (InvalidInt(week, 52) || InvalidInt(weekday, 7)) return ("", "");
            if (weekday == 7) weekday = 0; // Sunday is 0
            var dateFormat = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat;
            return (dateFormat.DayNames[weekday], dateFormat.AbbreviatedDayNames[weekday]);
        }
        public static DateTime NewDate(int year, int month = 1, int day = 1, int hour = 0, 
            int minute = 0, int second = 0, DateTimeKind kind = DateTimeKind.Unspecified)
        {
            if (!DateTime.IsLeapYear(year))
            {
                if (InvalidInt(month, 12)) throw new Exception("Invalid Month");
                if (InvalidInt(day, DaysInMonths[month])) throw new Exception("Invalid Day");
                if (month == 12 && day == 31) ;  // set the last day of the year
                else if (month > 1 && day == DaysInMonths[month]) { month++; day = 1; }
                else if (month > 2) day++;
            }
            return new DateTime(year, month, day, hour, minute, second, kind);
        }
        public static string ToString(DateTime date, string format)
        {
            var d = "@";
            var diff = 0;
            var day = date.Day;
            var dayOfYear = date.DayOfYear;
            var (week, weekday) = NexCalendar.GetWeekDate(dayOfYear);
            var (longName, shortName) = NexCalendar.GetWeekdayName(week, weekday);
            if (!DateTime.IsLeapYear(date.Year) && date.Month > 2)
            {	// the previous day
                diff = -1; day--;
                if (day == 0) day = NexCalendar.DaysInMonths[(date.Month - 1)];
            }
            var output = date.AddDays(diff).ToString(format.Replace("d", d));
            output = output.Replace(d + d + d + d, longName);
            output = output.Replace(d + d + d, shortName);
            output = output.Replace(d + d, day.ToString("D2"));
            output = output.Replace(d, day.ToString());
            return output;
        }

        //
        // Three extended functions are provided as DateTime extensions. 
        // e.g.
        // var (Year, Month, Day) = DateTime.Today.GetNexDate();
        // Debug.WriteLine("Month/day: " + Month + "/" + Day); 
        // var (Yr, Week, Weekday) = DateTime.Today.GetNexWeekDate();
        // Debug.WriteLine("Week Date: W" + Week + "-" + Weekday); 
        // var(LongName, ShortName) = DateTime.Today.GetNexWeekdayName();
        // Debug.WriteLine("Today is " + LongName + " (" + ShortName + ")");
        // Debug.WriteLine(DateTime.Today.ToString("d/M/yyyy dd ddd dddd"));
        // Debug.WriteLine(NexCalendar.ToString(DateTime.Today, "d/M/yyyy dd ddd dddd"));
        // var date = NexCalendar.NewDate(2021, 2, 29);
        // Debug.WriteLine(NexCalendar.ToString(date, "d/M/yyyy dd ddd dddd"));

        public static (int Year, int Month, int Day) GetNexDate(this DateTime date)
        {
            int year = date.Year; int month = date.Month; int day = date.Day;
            if (DateTime.IsLeapYear(year) || month < 3) return (year, month, day);
            if (day == 1) return (year, month - 1, DaysInMonths[month - 1]);
            else return (year, month, day - 1);
        }
        public static (int Year, int Week, int Weekday) GetNexWeekDate(this DateTime date)
        { 
            var (week, weekday) = NexCalendar.GetWeekDate(date.DayOfYear);
            return (date.Year, week, weekday);
        }
        public static (string LongName, string ShortName) GetNexWeekdayName(this DateTime date)
        {
            var (week, weekday) = NexCalendar.GetWeekDate(date.DayOfYear);
            return NexCalendar.GetWeekdayName(week, weekday);
        }
    }
    // Copyright © 2020 Dr. Donny C.F. Lai & Justin J.S. Lai. All Rights Reserved.