// Version: Feb 2020
    //
    // The following Swift 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 Foundation
	
    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)
        static let maxDaysInYear = 366  // 366 is equal to the Leap Sunday
        static let daysByWeek35 = 35 * 7 + 1  // it is the annual long week with 8 days
        static let daysInMonths
            = [ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
        static let daysByMonths
            = [ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 ]
        static private func invalidInt(_ x : Int, _ max: Int) 
            -> Bool { return (x < 1 || x > max) }	

        static func getYeardayFromMonthDay (month : Int, day : Int) -> Int
        {
            if invalidInt(month, 12) || invalidInt(day, daysInMonths[month]) { return 0 }
            else { return (daysByMonths[month - 1] + day) }
        }
        static func getMonthDay (yearday : Int) -> (month : Int, day : Int)
        {
            if invalidInt(yearday, maxDaysInYear) { return (0,0) }
            var month = 2
            if yearday > daysByMonths[4] { month = 6 }
            if yearday > daysByMonths[8] { month = 10 }
            if yearday > daysByMonths[month] { month+=1 } else { month-=1 }
            if yearday > daysByMonths[month] { month+=1 }
            return (month, yearday - daysByMonths[month - 1])
        }
        static func getYeardayFromWeekDate (week : Int, weekday : Int) -> Int
        {
            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+=1 }
            return yearday
        }
        static func getWeekDate (yearday : Int) -> (week : Int, weekday : Int) 
        {
            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
            var yearday = yearday;
            if yearday >= daysByWeek35 { yearday-=1 }
            var week =  (1 + (yearday - 1) / 7)
            var weekday = yearday - ((week - 1) * 7)
            return (week, weekday)
        }
        static func getWeekDateFromMonthDay (month : Int, day : Int) -> (week : Int, weekday : Int)
        {	return getWeekDate(yearday: getYeardayFromMonthDay(month: month, day: day)) }
        static func getMonthDayFromWeekDate (week : Int, weekday : Int) -> (month : Int, day : Int)
        {	return getMonthDay(yearday: getYeardayFromWeekDate(week: week, weekday: weekday)) }
        static func getWeekdayName (week : Int, weekday : Int) -> (longName : String, shortName : String)
        {   
            var weekday = weekday;
            if week == 35 && weekday == 7 { weekday = 6 } // Special Saturday
            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 current = Calendar.autoupdatingCurrent
            return (current.weekdaySymbols[weekday], current.shortWeekdaySymbols[weekday])
        }
        static func isLeapYear(_ year: Int) -> Bool 
        {
            return (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0)
        }
        static func newDate (year: Int, month: Int=1, day: Int=1, hour: Int=0, minute: Int=0, second: Int=0, 
			timeZone: TimeZone=TimeZone.current ) -> Date?
        {
            var month = month;
            var day = day;
            if (!isLeapYear(year))
            {
                if invalidInt(month, 12) { return nil }
                if invalidInt(day, daysInMonths[month]) { return nil }
                if month == 12 && day == 31 {}  // set the last day of the year
                else if month > 1 && day == daysInMonths[month] { month+=1;  day=1 }
                else if month > 2 { day+=1 }
            }
            let calendar = Calendar(identifier: .gregorian)
            var dateComponents = DateComponents()
            dateComponents.year = year
            dateComponents.month = month
            dateComponents.day = day
            dateComponents.hour = hour
            dateComponents.minute = minute
            dateComponents.second = second
            dateComponents.timeZone = timeZone
            return calendar.date(from: dateComponents) ?? nil
        }
        static func toString (date : Date, format: String) -> String
        {
            let calendar = Calendar(identifier: .gregorian)
            let ymd = calendar.dateComponents([.year, .month, .day], from: date)
            var d = "@"
            var diff = 0
            var day = ymd.day!
            var month = ymd.month!
            var year = ymd.year!
            if (!isLeapYear(year) && month > 2)
            {	// the previous day
                diff = -1; day-=1; 
                if day == 0 { month-=1; day = NexCalendar.daysInMonths[month]}
            }
            var date = Calendar.current.date(byAdding: .day, value: diff, to: date)!
            var format = format.replacingOccurrences(of: "d", with: d)
            let formatter = DateFormatter()
            formatter.dateFormat = format
            var output = formatter.string(from: date)
            var (week, weekday) = NexCalendar.getWeekDateFromMonthDay(month: month, day: day)
            var (longName, shortName) = NexCalendar.getWeekdayName(week: week, weekday: weekday)
            output = output.replacingOccurrences(of: d + d + d + d, with: longName)
            output = output.replacingOccurrences(of: d + d + d, with: shortName)
            output = output.replacingOccurrences(of: d + d, with: String(format: "%02d", day))
            output = output.replacingOccurrences(of: d, with: String(day))
            return output
        }
    }

    extension Date
    {
        // Three extended functions are provided as DateTime extensions. 
        func getNexDate () -> (year : Int, month : Int, day : Int) 
        {
            let calendar = Calendar(identifier: .gregorian)
            var year = calendar.component(.year, from: self)
            var month = calendar.component(.month, from: self)
            var day = calendar.component(.day, from: self)
            if NexCalendar.isLeapYear(year) || month < 3 { return (year, month, day) }
            if day == 1 { return (year, month - 1, NexCalendar.daysInMonths[month - 1]) }
            else { return (year, month, day - 1) }
        }
        func getNexWeekDate () -> (year : Int, week : Int, weekday : Int) 
        { 
            let calendar = Calendar(identifier: .gregorian)
            let year = calendar.component(.year, from: self)
            let yearday = calendar.ordinality(of: .day, in: .year, for: self)!
            let (week, weekday) = NexCalendar.getWeekDate(yearday: yearday)
            return (year, week, weekday)
        }
        func getNexWeekdayName () -> (longName : String, shortName : String)
        {
            let calendar = Calendar(identifier: .gregorian)
            let yearday = calendar.ordinality(of: .day, in: .year, for: self)!
            let (week, weekday) = NexCalendar.getWeekDate(yearday: yearday)
            return NexCalendar.getWeekdayName(week: week, weekday: weekday)
        }
    }
    // e.g.
    var (year, month, day) = Date().getNexDate()
    print("Month/day: " + String(year) + "/" + String(month) + "/" + String(day)) 
    var (yr, week, weekday) = Date().getNexWeekDate()
    print("Week Date: " + String(yr)+"-W" + String(week) + "-" + String(weekday)) 
    var (longName, shortName) = Date().getNexWeekdayName()
    print("Today is " + longName + " (" + shortName + ")")
    print(NexCalendar.toString(date: Date(), format:"d/M/yyyy dd ddd dddd"))
    var date = NexCalendar.newDate(year:2021, month: 2, day: 29) 
    print(NexCalendar.toString(date: date!, format:"d/M/yyyy dd ddd dddd"))

    // Copyright © 2020 Dr. Donny C.F. Lai & Justin J.S. Lai. All Rights Reserved.