// Version: Feb 2020
    //
    // The following Dart 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.	
    //
    class NexCalendar
    {
        // Weekday = Day of the Week (1 = Monday ... 7 = Sunday, 8 = Sunday)
        // Yearday = Ordinal Day of the Year (1 ... 365, 366 = Leap Sunday)
        static var maxDaysInYear = 366; // 366 is equal to the Leap Sunday
        static var daysByWeek35 = 35 * 7 + 1;  // Week 35 is the annual long week with 8 days
        static var daysInMonths = [ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
        static var daysByMonths =[ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 ];
        static bool invalidInt(int x, int max) {
           return (x < 1 || x > max);
        }

        static int getYeardayFromMonthDay(month, day)  // month = 1 to 12, day = 1 to 31
        {
            if (invalidInt(month, 12) || invalidInt(day, daysInMonths[month])) return 0;
            return (daysByMonths[month - 1] + day);
        }
        static Map getMonthDay(int yearday)
        {
            if (invalidInt(yearday, maxDaysInYear)) return {"month": 0, "day": 0};
            var 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++;
            var day = yearday - daysByMonths[month - 1];
            return { "month": month, "day": day };
        }
        static int getYeardayFromWeekDate(int week, int weekday) // week = 1 to 52, weekday = 1 to 7 or 8 for week 35 or 52.
        {
            if (week == 35 && weekday == 8) return daysByWeek35;
            if (week == 52 && weekday == 8) return maxDaysInYear;
            if (invalidInt(week, 52) || invalidInt(weekday, 7)) return 0;
            var yearday = (week - 1) * 7 + weekday;
            if (week > 35) yearday++;
            return yearday;
        }
        static Map getWeekDate(yearday)
        {
            if (invalidInt(yearday, maxDaysInYear)) return {"week": 0, "weekday": 0}; // Unknown
            if (yearday == maxDaysInYear) return {"week": 52, "weekday": 8}; // The last week
            if (yearday == daysByWeek35) return {"week": 35, "weekday": 8}; // The last week
            if (yearday >= daysByWeek35) yearday--;
            var week =  (yearday / 7).ceil();
            var weekday = yearday - ((week - 1) * 7);
            return { "week": week, "weekday": weekday };
        }
        static Map getWeekDateFromMonthDay(int month, int day) 
        { return getWeekDate(getYeardayFromMonthDay(month, day));}
        static Map getMonthDayFromWeekDate(int week, int weekday) 
        { return getMonthDay(getYeardayFromWeekDate(week, weekday));}

        static Map getWeekdayName(week, weekday)
        {   
            if ((week == 35 || week == 52) && weekday == 8) weekday = 7; // Special Sunday
            if (invalidInt(week, 52) || invalidInt(weekday, 7)) return null;
            // if no 'package:intl/intl.dart':
            var longNames = ["","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"];
            var shortNames = ["","Mon","Tue","Wed","Thu","Fri","Sat","Sun"];
            var longName = longNames[weekday];
            var shortName = shortNames[weekday];
            // if the package:intl is available:
            // import 'package:intl/intl.dart';
            // var date = new DateTime(2001,1,weekday); // 1 to 7 = Monday to Sunday
            // var longName = new DateFormat("EEEE").format(date);
            // var shortName = new DateFormat("E").format(date);
            return { "longName": longName, "shortName": shortName };
        }      
        static bool isLeapYear(int year) 
        {
            if( invalidInt(year,9999)) return false; 
            return ((year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0));
        }
        static int getYeardayFromDate(DateTime date) {
            int year = date.year;
            int month = date.month;
            int day = date.day;
            int yearday = getYeardayFromMonthDay(month, day);
            if( !isLeapYear(year) && month > 2) yearday--;
            return yearday;
        }
        static DateTime newDate(int year, [ int month = 1, int day = 1, int hour = 0, int minute = 0, 
		    int second = 0, int millisecond = 0, int microsecond = 0, bool utc = false ])
        {
            if (!isLeapYear(year))
            {
                if (invalidInt(month, 12)) return null;
                if (invalidInt(day, daysInMonths[month])) return null;
                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++;
            }
            if( utc ) 
              return new DateTime.utc(year, month, day, hour, minute, second, millisecond, microsecond); 
            else 
              return new DateTime(year, month, day, hour, minute, second, millisecond, microsecond);
        }
        static String toDateString(DateTime date)  
        {
            if( date == null ) return "";
            int year = date.year;
            int month = date.month;
            int day = date.day;
            if (!isLeapYear(year) && month > 2)
            {    // the previous day
                day--;
                if (day == 0) day = daysInMonths[--month];
            }
            var week = getWeekDateFromMonthDay(month, day);
            var weekdayName = getWeekdayName(week["week"], week["weekday"]);
            String long = weekdayName["longName"];
            String short = weekdayName["shortName"];
            String y = year.toString();
            String m = month.toString();
            String d = day.toString();
            String h = date.hour.toString();
            String min = date.minute.toString();
            String sec = date.second.toString();
            String ms = date.millisecond.toString();
            if (date.isUtc) {
              return "$y-$m-$d $long $short $h:$min:$sec.$ms Z";
            } else {
              return "$y-$m-$d $long $short $h:$min:$sec.$ms";
            }
        }
    }
    // e.g.
    void main() {
      // e.g.
      var date = NexCalendar.getMonthDay(246);
      print("Month: ${date["month"]}  day: ${date["day"]}");
      var weekdate = NexCalendar.getWeekDate(246);
      print("Week: ${weekdate["week"]}  Weekday: ${weekdate["weekday"]}");
      var weekdayName = NexCalendar.getWeekdayName(35, 7);
      print("Long name: ${weekdayName["longName"]}  Short name: ${weekdayName["shortName"]}");
      print(NexCalendar.toDateString(DateTime.now()));
      var feb29 = NexCalendar.newDate(2021,2,29);
      print(feb29.toString());
      print(NexCalendar.toDateString(feb29)); 
      var lastdate = DateTime(2021,12,31);
      print("Yearday is " + NexCalendar.getYeardayFromDate(lastdate).toString());
    }
    // Copyright © 2020 Dr. Donny C.F. Lai & Justin J.S. Lai. All Rights Reserved.