// Version: Feb 2020
    //
    // The following Java 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.	
    //
    import java.util.*; 
    import java.time.*;
    import java.time.format.*;
    import java.text.DateFormatSymbols;
    import java.io.*;

    public 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 static final int maxdaysInYear = 366;  // 366 is equal to the Leap Sunday
        public static final int daysByWeek35 = 35 * 7 + 1;  // It is the annual long week with 8 days
        public static final int[] daysInMonths
            = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
        public static final int[] daysByMonths
            = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };
        private static boolean invalidInt(int x, int max) { return (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[] getMonthDay(int yearday)  // [0] = month, [1] = day
        {
            if (invalidInt(yearday, maxdaysInYear)) return new int[] {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 new int[] {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[] getWeekDate(int yearday) // [0] = week, [1] = weekday
        {
            if (invalidInt(yearday, maxdaysInYear)) return new int[] {0, 0}; // Unknown
            if (yearday == maxdaysInYear) return new int[] {52, 8}; // The last week
            if (yearday == daysByWeek35) return new int[] {35, 8}; // The last week
            if (yearday >= daysByWeek35) yearday--;
            int week =  (1 + (yearday - 1) / 7);
            int weekday = yearday - ((week - 1) * 7);
            return new int[] {week, weekday};
        }
        public static int[] getWeekDateFromMonthDay(int month, int day) // [0] = week, [1] = weekday
        {	
          	return getWeekDate(getYeardayFromMonthDay(month, day));
        }
        public static int[] getMonthDayFromWeekDate(int week, int weekday) // [0] = month, [1] = day
        {
          	return getMonthDay(getYeardayFromWeekDate(week, weekday));
        }
        public static String[] getWeekdayName(int week, int weekday) // [0] = LongName, [1] = ShortName 
        {
            if ((week == 35 || week == 52) && weekday == 8) weekday = 7; // Special Sunday
            if (invalidInt(week, 52) || invalidInt(weekday, 7)) return new String[] {"", ""};
            if (weekday == 7) weekday = 0; // Sunday is 0
            weekday++; // Sunday = 1, Saturday = 7
            String longName = DateFormatSymbols.getInstance().getWeekdays()[weekday];
            String shortName = DateFormatSymbols.getInstance().getShortWeekdays()[weekday];
            return new String[] {longName, shortName};
        }
        public static boolean isLeapYear(int year) 
        {
            return (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0);
        }
 
        public static LocalDate newLocalDate(int year, int month, int day) 
        {
            if (!isLeapYear(year))
            {
                if (invalidInt(month, 12)) 
                    throw new UncheckedIOException(new IOException("invalid Month"));
                if (invalidInt(day, daysInMonths[month])) 
                    throw new UncheckedIOException(new IOException("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 LocalDate.of(year, month, day);
        }
        public static Date newDate(int year, int month, int day) 
        {
            LocalDate localDate = newLocalDate(year, month, day);
            return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
        }
        public static String toString(LocalDate date, String format)
        {
            String d = "@";
            int diff = 0;
            int day = date.getDayOfMonth();
            int month = date.getMonthValue();
            int year = date.getYear();
            int dayOfYear = date.getDayOfYear();
            int[] weekDate = NexCalendar.getWeekDate(dayOfYear);
            String[] weekdayName = NexCalendar.getWeekdayName(weekDate[0], weekDate[1]);
            if (!isLeapYear(year) && month > 2)
            {	// the previous day
                diff = -1; day--;
                if (day == 0) day = NexCalendar.daysInMonths[(month - 1)];
            }
            date = date.plusDays(diff);
            format = format.replaceAll("d",d);
            format = format.replaceAll("EEEE", d+d+d+d);
            format = format.replaceAll("E+", d+d+d);
            String output = date.format(DateTimeFormatter.ofPattern(format));
            output = output.replaceAll(d + d + d + d, weekdayName[0]);  // longName
            output = output.replaceAll(d + d + d, weekdayName[1]);  // shortName
            output = output.replaceAll(d + d, (day < 10? "0"+day : ""+day));
            output = output.replaceAll(d, ""+day);
            return output;
        }
        public static String toString(Date date, String format)
        {
            LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            return toString(localDate, format);
        }
		// e.g.
        public static void main(String[] args)
        {
       	  LocalDate date = NexCalendar.newLocalDate(2021,2,29);
          int[] monthDay = NexCalendar.getMonthDay(date.getDayOfYear());
    	  int[] weekDate = NexCalendar.getWeekDate(date.getDayOfYear());
    	  String[] weekdayName = NexCalendar.getWeekdayName(weekDate[0], weekDate[1]);
    	  System.out.println("Month: " + monthDay[0] + " Day: " + monthDay[1]);
          System.out.println("Week: " + weekDate[0] + " Weekday: " + weekDate[1]);
          System.out.println("It is " + weekdayName[0] + " (" + weekdayName[1] + ")");
          System.out.println(NexCalendar.toString(date,"d-MMM-yyyy d dd E EEEE"));
        }
    }
    // Copyright © 2020 Dr. Donny C.F. Lai & Justin J.S. Lai. All Rights Reserved.