// This file contains portions of code released by Microsoft under the MIT license as part
// of an open-sourcing initiative in 2014 of the C# core libraries.
// The original source was submitted to https://github.com/Microsoft/referencesource

namespace System.Globalization {
    using System;
    using System.Globalization;
    using System.Diagnostics.Contracts;

    // This abstract class represents a calendar. A calendar reckons time in
    // divisions such as weeks, months and years. The number, length and start of
    // the divisions vary in each calendar.
    //
    // Any instant in time can be represented as an n-tuple of numeric values using
    // a particular calendar. For example, the next vernal equinox occurs at (0.0, 0
    // , 46, 8, 20, 3, 1999) in the Gregorian calendar. An  implementation of
    // Calendar can map any DateTime value to such an n-tuple and vice versa. The
    // DateTimeFormat class can map between such n-tuples and a textual
    // representation such as "8:46 AM [....] 20th 1999 AD".
    //
    // Most calendars identify a year which begins the current era. There may be any
    // number of previous eras. The Calendar class identifies the eras as enumerated
    // integers where the current era (CurrentEra) has the value zero.
    //
    // For consistency, the first unit in each interval, e.g. the first month, is
    // assigned the value one.
    // The calculation of hour/minute/second is moved to Calendar from GregorianCalendar,
    // since most of the calendars (or all?) have the same way of calcuating hour/minute/second.

    public abstract class Calendar //: ICloneable
    {

        // Number of 100ns (10E-7 second) ticks per time unit
        protected const int64 TicksPerMillisecond   = 10000;
        protected const int64 TicksPerSecond        = TicksPerMillisecond * 1000;
        protected const int64 TicksPerMinute        = TicksPerSecond * 60;
        protected const int64 TicksPerHour          = TicksPerMinute * 60;
        protected const int64 TicksPerDay           = TicksPerHour * 24;

        // Number of milliseconds per time unit
        protected const int MillisPerSecond        = 1000;
        protected const int MillisPerMinute        = MillisPerSecond * 60;
        protected const int MillisPerHour          = MillisPerMinute * 60;
        protected const int MillisPerDay           = MillisPerHour * 24;

        // Number of days in a non-leap year
        protected const int DaysPerYear            = 365;
        // Number of days in 4 years
        protected const int DaysPer4Years          = DaysPerYear * 4 + 1;
        // Number of days in 100 years
        protected const int DaysPer100Years        = DaysPer4Years * 25 - 1;
        // Number of days in 400 years
        protected const int DaysPer400Years        = DaysPer100Years * 4 + 1;

        // Number of days from 1/1/0001 to 1/1/10000
        protected const int DaysTo10000            = DaysPer400Years * 25 - 366;

        protected const int64 MaxMillis             = (int64)DaysTo10000 * MillisPerDay;

        //
        //  Calendar ID Values.  This is used to get data from calendar.nlp.
        //  The order of calendar ID means the order of data items in the table.
        //

        const int CAL_GREGORIAN                  = 1 ;     // Gregorian (localized) calendar
        const int CAL_GREGORIAN_US               = 2 ;     // Gregorian (U.S.) calendar
        const int CAL_JAPAN                      = 3 ;     // Japanese Emperor Era calendar
        const int CAL_TAIWAN                     = 4 ;     // Taiwan Era calendar
        const int CAL_KOREA                      = 5 ;     // Korean Tangun Era calendar
        const int CAL_HIJRI                      = 6 ;     // Hijri (Arabic Lunar) calendar
        const int CAL_THAI                       = 7 ;     // Thai calendar
        const int CAL_HEBREW                     = 8 ;     // Hebrew (Lunar) calendar
        const int CAL_GREGORIAN_ME_FRENCH        = 9 ;     // Gregorian Middle East French calendar
        const int CAL_GREGORIAN_ARABIC           = 10;     // Gregorian Arabic calendar
        const int CAL_GREGORIAN_XLIT_ENGLISH     = 11;     // Gregorian Transliterated English calendar
        const int CAL_GREGORIAN_XLIT_FRENCH      = 12;
        const int CAL_JULIAN                     = 13;
        const int CAL_JAPANESELUNISOLAR          = 14;
        const int CAL_CHINESELUNISOLAR           = 15;
        const int CAL_SAKA                       = 16;     // reserved to match Office but not implemented in our code
        const int CAL_LUNAR_ETO_CHN              = 17;     // reserved to match Office but not implemented in our code
        const int CAL_LUNAR_ETO_KOR              = 18;     // reserved to match Office but not implemented in our code
        const int CAL_LUNAR_ETO_ROKUYOU          = 19;     // reserved to match Office but not implemented in our code
        const int CAL_KOREANLUNISOLAR            = 20;
        const int CAL_TAIWANLUNISOLAR            = 21;
        const int CAL_PERSIAN                    = 22;
        const int CAL_UMALQURA                   = 23;

        int m_currentEraValue = -1;

        private bool m_isReadOnly = false;

        // The minimum supported DateTime range for the calendar.

        public virtual DateTime MinSupportedDateTime
        {
            get
            {
                return (DateTime.MinValue);
            }
        }

        // The maximum supported DateTime range for the calendar.
        public virtual DateTime MaxSupportedDateTime
        {
            get
            {
                return (DateTime.MaxValue);
            }
        }

        protected this() {
            //Do-nothing constructor.
        }

        ///
        // This can not be abstract, otherwise no one can create a subclass of Calendar.
        //
        protected virtual int ID {
            get {
                return (-1);
            }
        }

        ///
        // Return the Base calendar ID for calendars that didn't have defined data in calendarData
        //

        protected virtual int BaseCalendarID
        {
            get { return ID; }
        }

        // Returns  the type of the calendar.
        //
        public virtual CalendarAlgorithmType AlgorithmType
        {
            get
            {
                return CalendarAlgorithmType.Unknown;
            }
        }

        ////////////////////////////////////////////////////////////////////////
        //
        //  IsReadOnly
        //
        //  Detect if the object is readonly.
        //
        ////////////////////////////////////////////////////////////////////////
        
        public bool IsReadOnly
        {
            get { return (m_isReadOnly); }
        }

        ////////////////////////////////////////////////////////////////////////
        //
        //  Clone
        //
        //  Is the implementation of IColnable.
        //
        ////////////////////////////////////////////////////////////////////////
        /*public virtual Object Clone()
        {
            object o = MemberwiseClone();
            ((Calendar) o).SetReadOnlyState(false);
            return (o);
        }*/
        
        ////////////////////////////////////////////////////////////////////////
        //
        //  ReadOnly
        //
        //  Create a cloned readonly instance or return the input one if it is 
        //  readonly.
        //
        ////////////////////////////////////////////////////////////////////////
        public static Calendar ReadOnly(Calendar calendar) 
        {
            /*if (calendar == null)       { throw new ArgumentNullException("calendar"); }
            Contract.EndContractBlock();
            if (calendar.IsReadOnly)    { return (calendar); }
            
            Calendar clonedCalendar = (Calendar)(calendar.MemberwiseClone());
            clonedCalendar.SetReadOnlyState(true);
            
            return (clonedCalendar);*/
			Runtime.NotImplemented();
        }

        Result<void> VerifyWritable()
        {
            if (m_isReadOnly)
            {
                //throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ReadOnly"));
				return .Err;
            }
			return .Ok;
        }

        void SetReadOnlyState(bool readOnly)
        {
            m_isReadOnly = readOnly;
        }


        /*=================================CurrentEraValue==========================
        **Action: This is used to convert CurretEra(0) to an appropriate era value.
        **Returns:
        **Arguments:
        **Exceptions:
        **Notes:
        ** The value is from calendar.nlp.
        ============================================================================*/

        protected virtual int CurrentEraValue
		{
            get
			{
                // The following code assumes that the current era value can not be -1.
                if (m_currentEraValue == -1) {
                    Contract.Assert(BaseCalendarID > 0, "[Calendar.CurrentEraValue] Expected ID > 0");
                    m_currentEraValue = CalendarData.[Friend]GetCalendarData(BaseCalendarID).[Friend]iCurrentEra;
                }
                return (m_currentEraValue);
            }
        }

        // The current era for a calendar.

        public const int CurrentEra = 0;

        protected int twoDigitYearMax = -1;

        static Result<void> CheckAddResult(int64 ticks, DateTime minValue, DateTime maxValue) {
            if (ticks < minValue.Ticks || ticks > maxValue.Ticks) {
				return .Err;
                /*throw new ArgumentException(
                    String.Format(CultureInfo.InvariantCulture, Environment.GetResourceString("Argument_ResultCalendarRange"),
                        minValue, maxValue));*/
            }
            //Contract.EndContractBlock();
			return .Ok;
        }

        Result<DateTime> Add(DateTime time, double value, int scale) {
            // From ECMA CLI spec, Partition III, section 3.27:
            //
            // If overflow occurs converting a floating-point type to an integer, or if the floating-point value 
            // being converted to an integer is a NaN, the value returned is unspecified. 
            //
            // Based upon this, this method should be performing the comparison against the double
            // before attempting a cast. Otherwise, the result is undefined.
            double tempMillis = (value * scale + (value >= 0 ? 0.5 : -0.5));
            if (!((tempMillis > -(double)MaxMillis) && (tempMillis < (double)MaxMillis)))
            {
                //throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_AddValue"));
				return .Err;
            }

            int64 millis = (int64)tempMillis;
            int64 ticks = time.Ticks + millis * TicksPerMillisecond;
            CheckAddResult(ticks, MinSupportedDateTime, MaxSupportedDateTime);
            return (DateTime(ticks));
        }

        // Returns the DateTime resulting from adding the given number of
        // milliseconds to the specified DateTime. The result is computed by rounding
        // the number of milliseconds given by value to the nearest integer,
        // and adding that interval to the specified DateTime. The value
        // argument is permitted to be negative.
        //

        public virtual Result<DateTime> AddMilliseconds(DateTime time, double milliseconds) {
            return (Add(time, milliseconds, 1));
        }


        // Returns the DateTime resulting from adding a fractional number of
        // days to the specified DateTime. The result is computed by rounding the
        // fractional number of days given by value to the nearest
        // millisecond, and adding that interval to the specified DateTime. The
        // value argument is permitted to be negative.
        //

        public virtual Result<DateTime> AddDays(DateTime time, int days) {
            return (Add(time, days, MillisPerDay));
        }

        // Returns the DateTime resulting from adding a fractional number of
        // hours to the specified DateTime. The result is computed by rounding the
        // fractional number of hours given by value to the nearest
        // millisecond, and adding that interval to the specified DateTime. The
        // value argument is permitted to be negative.
        //

        public virtual Result<DateTime> AddHours(DateTime time, int hours) {
            return (Add(time, hours, MillisPerHour));
        }


        // Returns the DateTime resulting from adding a fractional number of
        // minutes to the specified DateTime. The result is computed by rounding the
        // fractional number of minutes given by value to the nearest
        // millisecond, and adding that interval to the specified DateTime. The
        // value argument is permitted to be negative.
        //

        public virtual Result<DateTime> AddMinutes(DateTime time, int minutes) {
            return (Add(time, minutes, MillisPerMinute));
        }


        // Returns the DateTime resulting from adding the given number of
        // months to the specified DateTime. The result is computed by incrementing
        // (or decrementing) the year and month parts of the specified DateTime by
        // value months, and, if required, adjusting the day part of the
        // resulting date downwards to the last day of the resulting month in the
        // resulting year. The time-of-day part of the result is the same as the
        // time-of-day part of the specified DateTime.
        //
        // In more precise terms, considering the specified DateTime to be of the
        // form y / m / d + t, where y is the
        // year, m is the month, d is the day, and t is the
        // time-of-day, the result is y1 / m1 / d1 + t,
        // where y1 and m1 are computed by adding value months
        // to y and m, and d1 is the largest value less than
        // or equal to d that denotes a valid day in month m1 of year
        // y1.
        //

        public abstract Result<DateTime> AddMonths(DateTime time, int months);

        // Returns the DateTime resulting from adding a number of
        // seconds to the specified DateTime. The result is computed by rounding the
        // fractional number of seconds given by value to the nearest
        // millisecond, and adding that interval to the specified DateTime. The
        // value argument is permitted to be negative.
        //

        public virtual Result<DateTime> AddSeconds(DateTime time, int seconds) {
            return Add(time, seconds, MillisPerSecond);
        }

        // Returns the DateTime resulting from adding a number of
        // weeks to the specified DateTime. The
        // value argument is permitted to be negative.
        //

        public virtual Result<DateTime> AddWeeks(DateTime time, int weeks) {
            return (AddDays(time, weeks * 7));
        }


        // Returns the DateTime resulting from adding the given number of
        // years to the specified DateTime. The result is computed by incrementing
        // (or decrementing) the year part of the specified DateTime by value
        // years. If the month and day of the specified DateTime is 2/29, and if the
        // resulting year is not a leap year, the month and day of the resulting
        // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day
        // parts of the result are the same as those of the specified DateTime.
        //

        public abstract Result<DateTime> AddYears(DateTime time, int years);

        // Returns the day-of-month part of the specified DateTime. The returned
        // value is an integer between 1 and 31.
        //

        public abstract Result<int> GetDayOfMonth(DateTime time);

        // Returns the day-of-week part of the specified DateTime. The returned value
        // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
        // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
        // Thursday, 5 indicates Friday, and 6 indicates Saturday.
        //

        public abstract Result<DayOfWeek> GetDayOfWeek(DateTime time);

        // Returns the day-of-year part of the specified DateTime. The returned value
        // is an integer between 1 and 366.
        //

        public abstract Result<int> GetDayOfYear(DateTime time);

        // Returns the number of days in the month given by the year and
        // month arguments.
        //

        public virtual Result<int> GetDaysInMonth(int year, int month)
        {
            return (GetDaysInMonth(year, month, CurrentEra));
        }

        // Returns the number of days in the month given by the year and
        // month arguments for the specified era.
        //

        public abstract Result<int> GetDaysInMonth(int year, int month, int era);

        // Returns the number of days in the year given by the year argument for the current era.
        //

        public virtual Result<int> GetDaysInYear(int year)
        {
            return (GetDaysInYear(year, CurrentEra));
        }

        // Returns the number of days in the year given by the year argument for the current era.
        //

        public abstract Result<int> GetDaysInYear(int year, int era);

        // Returns the era for the specified DateTime value.

        public abstract Result<int> GetEra(DateTime time);

        /*=================================Eras==========================
        **Action: Get the list of era values.
        **Returns: The int array of the era names supported in this calendar.
        **      null if era is not used.
        **Arguments: None.
        **Exceptions: None.
        ============================================================================*/


        public abstract int[] Eras {
            get;
        }


        // Returns the hour part of the specified DateTime. The returned value is an
        // integer between 0 and 23.
        //

        public virtual Result<int> GetHour(DateTime time) {
            return ((int)((time.Ticks / TicksPerHour) % 24));
        }

        // Returns the millisecond part of the specified DateTime. The returned value
        // is an integer between 0 and 999.
        //

        public virtual Result<double> GetMilliseconds(DateTime time) {
            return (double)((time.Ticks / TicksPerMillisecond) % 1000);
        }

        // Returns the minute part of the specified DateTime. The returned value is
        // an integer between 0 and 59.
        //

        public virtual Result<int> GetMinute(DateTime time) {
            return ((int)((time.Ticks / TicksPerMinute) % 60));
        }

        // Returns the month part of the specified DateTime. The returned value is an
        // integer between 1 and 12.
        //

        public abstract Result<int> GetMonth(DateTime time);

        // Returns the number of months in the specified year in the current era.

        public virtual Result<int> GetMonthsInYear(int year)
        {
            return (GetMonthsInYear(year, CurrentEra));
        }

        // Returns the number of months in the specified year and era.

        public abstract Result<int> GetMonthsInYear(int year, int era);

        // Returns the second part of the specified DateTime. The returned value is
        // an integer between 0 and 59.
        //

        public virtual Result<int> GetSecond(DateTime time) {
            return ((int)((time.Ticks / TicksPerSecond) % 60));
        }

        /*=================================GetFirstDayWeekOfYear==========================
        **Action: Get the week of year using the FirstDay rule.
        **Returns:  the week of year.
        **Arguments:
        **  time
        **  firstDayOfWeek  the first day of week (0=Sunday, 1=Monday, ... 6=Saturday)
        **Notes:
        **  The CalendarWeekRule.FirstDay rule: Week 1 begins on the first day of the year.
        **  Assume f is the specifed firstDayOfWeek,
        **  and n is the day of week for January 1 of the specified year.
        **  Assign offset = n - f;
        **  Case 1: offset = 0
        **      E.g.
        **                     f=1
        **          weekday 0  1  2  3  4  5  6  0  1
        **          date       1/1
        **          week#      1                    2
        **      then week of year = (GetDayOfYear(time) - 1) / 7 + 1
        **
        **  Case 2: offset < 0
        **      e.g.
        **                     n=1   f=3
        **          weekday 0  1  2  3  4  5  6  0
        **          date       1/1
        **          week#      1     2
        **      This means that the first week actually starts 5 days before 1/1.
        **      So week of year = (GetDayOfYear(time) + (7 + offset) - 1) / 7 + 1
        **  Case 3: offset > 0
        **      e.g.
        **                  f=0   n=2
        **          weekday 0  1  2  3  4  5  6  0  1  2
        **          date          1/1
        **          week#         1                    2
        **      This means that the first week actually starts 2 days before 1/1.
        **      So Week of year = (GetDayOfYear(time) + offset - 1) / 7 + 1
        ============================================================================*/

        Result<int> GetFirstDayWeekOfYear(DateTime time, int firstDayOfWeek) {
            int dayOfYear = Try!(GetDayOfYear(time)) - 1;   // Make the day of year to be 0-based, so that 1/1 is day 0.
            // Calculate the day of week for the first day of the year.
            // dayOfWeek - (dayOfYear % 7) is the day of week for the first day of this year.  Note that
            // this value can be less than 0.  It's fine since we are making it positive again in calculating offset.
            int dayForJan1 = (int)Try!(GetDayOfWeek(time)) - (dayOfYear % 7);
            int offset = (dayForJan1 - firstDayOfWeek + 14) % 7;
            Contract.Assert(offset >= 0, "Calendar.GetFirstDayWeekOfYear(): offset >= 0");
            return ((dayOfYear + offset) / 7 + 1);
        }

        private Result<int> GetWeekOfYearFullDays(DateTime time, int firstDayOfWeek, int fullDays) {
            int dayForJan1;
            int offset;
            int day;

            int dayOfYear = Try!(GetDayOfYear(time)) - 1; // Make the day of year to be 0-based, so that 1/1 is day 0.
            //
            // Calculate the number of days between the first day of year (1/1) and the first day of the week.
            // This value will be a positive value from 0 ~ 6.  We call this value as "offset".
            //
            // If offset is 0, it means that the 1/1 is the start of the first week.
            //     Assume the first day of the week is Monday, it will look like this:
            //     Sun      Mon     Tue     Wed     Thu     Fri     Sat
            //     12/31    1/1     1/2     1/3     1/4     1/5     1/6
            //              +--> First week starts here.
            //
            // If offset is 1, it means that the first day of the week is 1 day ahead of 1/1.
            //     Assume the first day of the week is Monday, it will look like this:
            //     Sun      Mon     Tue     Wed     Thu     Fri     Sat
            //     1/1      1/2     1/3     1/4     1/5     1/6     1/7
            //              +--> First week starts here.
            //
            // If offset is 2, it means that the first day of the week is 2 days ahead of 1/1.
            //     Assume the first day of the week is Monday, it will look like this:
            //     Sat      Sun     Mon     Tue     Wed     Thu     Fri     Sat
            //     1/1      1/2     1/3     1/4     1/5     1/6     1/7     1/8
            //                      +--> First week starts here.



            // Day of week is 0-based.
            // Get the day of week for 1/1.  This can be derived from the day of week of the target day.
            // Note that we can get a negative value.  It's ok since we are going to make it a positive value when calculating the offset.
            dayForJan1 = (int)Try!(GetDayOfWeek(time)) - (dayOfYear % 7);

            // Now, calculate the offset.  Subtract the first day of week from the dayForJan1.  And make it a positive value.
            offset = (firstDayOfWeek - dayForJan1 + 14) % 7;
            if (offset != 0 && offset >= fullDays)
            {
                //
                // If the offset is greater than the value of fullDays, it means that
                // the first week of the year starts on the week where Jan/1 falls on.
                //
                offset -= 7;
            }
            //
            // Calculate the day of year for specified time by taking offset into account.
            //
            day = dayOfYear - offset;
            if (day >= 0) {
                //
                // If the day of year value is greater than zero, get the week of year.
                //
                return (day/7 + 1);
            }
            //
            // Otherwise, the specified time falls on the week of previous year.
            // Call this method again by passing the last day of previous year.
            //
            // the last day of the previous year may "underflow" to no longer be a valid date time for
            // this calendar if we just subtract so we need the subclass to provide us with 
            // that information
            if (time <= MinSupportedDateTime.AddDays(dayOfYear))
            {
                return GetWeekOfYearOfMinSupportedDateTime(firstDayOfWeek, fullDays);
            }
            return (GetWeekOfYearFullDays(time.AddDays(-(dayOfYear + 1)), firstDayOfWeek, fullDays));
        }

        private Result<int> GetWeekOfYearOfMinSupportedDateTime(int firstDayOfWeek, int minimumDaysInFirstWeek)
        {
            int dayOfYear = Try!(GetDayOfYear(MinSupportedDateTime)) - 1;  // Make the day of year to be 0-based, so that 1/1 is day 0.
            int dayOfWeekOfFirstOfYear = (int)Try!(GetDayOfWeek(MinSupportedDateTime)) - dayOfYear % 7;

            // Calculate the offset (how many days from the start of the year to the start of the week)
            int offset = (firstDayOfWeek + 7 - dayOfWeekOfFirstOfYear) % 7;
            if (offset == 0 || offset >= minimumDaysInFirstWeek)
            {
                // First of year falls in the first week of the year
                return 1;
            }

            int daysInYearBeforeMinSupportedYear = DaysInYearBeforeMinSupportedYear - 1; // Make the day of year to be 0-based, so that 1/1 is day 0.
            int dayOfWeekOfFirstOfPreviousYear = dayOfWeekOfFirstOfYear - 1 - (daysInYearBeforeMinSupportedYear % 7);

            // starting from first day of the year, how many days do you have to go forward
            // before getting to the first day of the week?
            int daysInInitialPartialWeek = (firstDayOfWeek - dayOfWeekOfFirstOfPreviousYear + 14) % 7;
            int day = daysInYearBeforeMinSupportedYear - daysInInitialPartialWeek;
            if (daysInInitialPartialWeek >= minimumDaysInFirstWeek)
            {
                // If the offset is greater than the minimum Days in the first week, it means that
                // First of year is part of the first week of the year even though it is only a partial week
                // add another week
                day += 7;
            }

            return (day / 7 + 1);
        }

        // it would be nice to make this abstract but we can't since that would break previous implementations
        protected virtual int DaysInYearBeforeMinSupportedYear
        {
            get
            {
                return 365;
            }
        }


        // Returns the week of year for the specified DateTime. The returned value is an
        // integer between 1 and 53.
        //

        public virtual Result<int> GetWeekOfYear(DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
        {
            if ((int)firstDayOfWeek < 0 || (int)firstDayOfWeek > 6) {
                /*throw new ArgumentOutOfRangeException(
                    "firstDayOfWeek", Environment.GetResourceString("ArgumentOutOfRange_Range",
                    DayOfWeek.Sunday, DayOfWeek.Saturday));*/
				return .Err;
            }
            //Contract.EndContractBlock();
            switch (rule) {
                case CalendarWeekRule.FirstDay:
                    return (GetFirstDayWeekOfYear(time, (int)firstDayOfWeek));
                case CalendarWeekRule.FirstFullWeek:
                    return (GetWeekOfYearFullDays(time, (int)firstDayOfWeek, 7));
                case CalendarWeekRule.FirstFourDayWeek:
                    return (GetWeekOfYearFullDays(time, (int)firstDayOfWeek, 4));
            }
            /*throw new ArgumentOutOfRangeException(
                "rule", Environment.GetResourceString("ArgumentOutOfRange_Range",
                CalendarWeekRule.FirstDay, CalendarWeekRule.FirstFourDayWeek));*/
			//return .Err;

        }

        // Returns the year part of the specified DateTime. The returned value is an
        // integer between 1 and 9999.
        //

        public abstract Result<int> GetYear(DateTime time);

        // Checks whether a given day in the current era is a leap day. This method returns true if
        // the date is a leap day, or false if not.
        //

        public virtual Result<bool> IsLeapDay(int year, int month, int day)
        {
            return (IsLeapDay(year, month, day, CurrentEra));
        }

        // Checks whether a given day in the specified era is a leap day. This method returns true if
        // the date is a leap day, or false if not.
        //

        public abstract Result<bool> IsLeapDay(int year, int month, int day, int era);

        // Checks whether a given month in the current era is a leap month. This method returns true if
        // month is a leap month, or false if not.
        //

        public virtual Result<bool> IsLeapMonth(int year, int month) {
            return (IsLeapMonth(year, month, CurrentEra));
        }

        // Checks whether a given month in the specified era is a leap month. This method returns true if
        // month is a leap month, or false if not.
        //

        public abstract Result<bool> IsLeapMonth(int year, int month, int era);

        // Returns  the leap month in a calendar year of the current era. This method returns 0
        // if this calendar does not have leap month, or this year is not a leap year.
        //

        public virtual Result<int> GetLeapMonth(int year)
        {
            return (GetLeapMonth(year, CurrentEra));
        }

        // Returns  the leap month in a calendar year of the specified era. This method returns 0
        // if this calendar does not have leap month, or this year is not a leap year.
        //
        public virtual Result<int> GetLeapMonth(int year, int era)
        {
            if (!IsLeapYear(year, era))
                return 0;

            int monthsCount = GetMonthsInYear(year, era);
            for (int month=1; month<=monthsCount; month++)
            {
                if (IsLeapMonth(year, month, era))
                    return month;
            }

            return 0;
        }

        // Checks whether a given year in the current era is a leap year. This method returns true if
        // year is a leap year, or false if not.
        //

        public virtual Result<bool> IsLeapYear(int year)
        {
            return (IsLeapYear(year, CurrentEra));
        }

        // Checks whether a given year in the specified era is a leap year. This method returns true if
        // year is a leap year, or false if not.
        //

        public abstract Result<bool> IsLeapYear(int year, int era);

        // Returns the date and time converted to a DateTime value.  Throws an exception if the n-tuple is invalid.
        //

        public virtual DateTime ToDateTime(int year, int month,  int day, int hour, int minute, int second, int millisecond)
        {
            return (ToDateTime(year, month, day, hour, minute, second, millisecond, CurrentEra));
        }

        // Returns the date and time converted to a DateTime value.  Throws an exception if the n-tuple is invalid.
        //

        public abstract Result<DateTime> ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era);

        protected virtual bool TryToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era, out DateTime result) {
            result = DateTime.MinValue;
            switch (ToDateTime(year, month, day, hour, minute, second, millisecond, era))
			{
			case .Ok(out result):
				return true;
			case .Err:
				return false;
			}
        }
        
        protected virtual bool IsValidYear(int year, int era) {
            return (year >= GetYear(MinSupportedDateTime).Get() && year <= GetYear(MaxSupportedDateTime).Get());
        }
        
        protected virtual bool IsValidMonth(int year, int month, int era) {
            return (IsValidYear(year, era) && month >= 1 && month <= GetMonthsInYear(year, era).Get());
        }
        
        protected virtual bool IsValidDay(int year, int month, int day, int era)
        {
            return (IsValidMonth(year, month, era) && day >= 1 && day <= GetDaysInMonth(year, month, era).Get());
        }

        // Returns and assigns the maximum value to represent a two digit year.  This
        // value is the upper boundary of a 100 year range that allows a two digit year
        // to be properly translated to a four digit year.  For example, if 2029 is the
        // upper boundary, then a two digit value of 30 should be interpreted as 1930
        // while a two digit value of 29 should be interpreted as 2029.  In this example
        // , the 100 year range would be from 1930-2029.  See ToFourDigitYear().

        public virtual int TwoDigitYearMax
        {
            get
            {
                return (twoDigitYearMax);
            }

            set
            {
                VerifyWritable();
                twoDigitYearMax = value;
            }
        }

        // Converts the year value to the appropriate century by using the
        // TwoDigitYearMax property.  For example, if the TwoDigitYearMax value is 2029,
        // then a two digit value of 30 will get converted to 1930 while a two digit
        // value of 29 will get converted to 2029.

        public virtual Result<int> ToFourDigitYear(int year) {
            if (year < 0) {
                /*throw new ArgumentOutOfRangeException("year",
                    Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));*/
				return .Err;
            }
            //Contract.EndContractBlock();
            if (year < 100) {
                return ((TwoDigitYearMax/100 - ( year > TwoDigitYearMax % 100 ? 1 : 0))*100 + year);
            }
            // If the year value is above 100, just return the year value.  Don't have to do
            // the TwoDigitYearMax comparison.
            return (year);
        }

        // Return the tick count corresponding to the given hour, minute, second.
        // Will check the if the parameters are valid.
        static Result<int64> TimeToTicks(int hour, int minute, int second, int millisecond)
        {
            if (hour >= 0 && hour < 24 && minute >= 0 && minute < 60 && second >=0 && second < 60)
            {
                if (millisecond < 0 || millisecond >= MillisPerSecond) {
                    /*throw new ArgumentOutOfRangeException(
                                "millisecond",
                                String.Format(
                                    CultureInfo.InvariantCulture,
                                    Environment.GetResourceString("ArgumentOutOfRange_Range"), 0, MillisPerSecond - 1));*/
					return .Err;
                }
                return Try!(TimeSpan.[Friend]TimeToTicks(hour, minute, second)) + millisecond * TicksPerMillisecond;
            }
            //throw new ArgumentOutOfRangeException(null, Environment.GetResourceString("ArgumentOutOfRange_BadHourMinuteSecond"));
			return .Err;
        }

        protected static int GetSystemTwoDigitYearSetting(int CalID, int defaultYearValue)
        {
            // Call nativeGetTwoDigitYearMax
            int twoDigitYearMax = CalendarData.[Friend]nativeGetTwoDigitYearMax(CalID);
            if (twoDigitYearMax < 0)
            {
                twoDigitYearMax = defaultYearValue;
            }
            return (twoDigitYearMax);
        }

    }
}
