view tango/tango/time/chrono/Gregorian.d @ 132:1700239cab2e trunk

[svn r136] MAJOR UNSTABLE UPDATE!!! Initial commit after moving to Tango instead of Phobos. Lots of bugfixes... This build is not suitable for most things.
author lindquist
date Fri, 11 Jan 2008 17:57:40 +0100
parents
children
line wrap: on
line source

/*******************************************************************************

        copyright:      Copyright (c) 2005 John Chapman. All rights reserved

        license:        BSD style: $(LICENSE)

        version:        Mid 2005: Initial release
                        Apr 2007: reshaped                        

        author:         John Chapman, Kris, schveiguy

******************************************************************************/

module tango.time.chrono.Gregorian;

private import tango.time.chrono.Calendar;

/**
 * $(ANCHOR _Gregorian)
 * Represents the Gregorian calendar.
 *
 * Note that this is the Proleptic Gregorian calendar.  Most calendars assume
 * that dates before 9/14/1752 were Julian Dates.  Julian differs from
 * Gregorian in that leap years occur every 4 years, even on 100 year
 * increments.  The Proleptic Gregorian calendar applies the Gregorian leap
 * year rules to dates before 9/14/1752, making the calculation of dates much
 * easier.
 */
class Gregorian : Calendar 
{
        // import baseclass toTime()
        alias Calendar.toTime toTime;

        /// static shared instance
        public static Gregorian generic;

        enum Type 
        {
                Localized = 1,               /// Refers to the localized version of the Gregorian calendar.
                USEnglish = 2,               /// Refers to the US English version of the Gregorian calendar.
                MiddleEastFrench = 9,        /// Refers to the Middle East French version of the Gregorian calendar.
                Arabic = 10,                 /// Refers to the _Arabic version of the Gregorian calendar.
                TransliteratedEnglish = 11,  /// Refers to the transliterated English version of the Gregorian calendar.
                TransliteratedFrench = 12    /// Refers to the transliterated French version of the Gregorian calendar.
        }

        private Type type_;                 

        /**
        * Represents the current era.
        */
        enum {AD_ERA = 1, BC_ERA = 2, MAX_YEAR = 9999};

        private static final uint[] DaysToMonthCommon = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];

        private static final uint[] DaysToMonthLeap   = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];

        /**
        * create a generic instance of this calendar
        */
        static this()
        {       
                generic = new Gregorian;
        }

        /**
        * Initializes an instance of the Gregorian class using the specified GregorianTypes value. If no value is 
        * specified, the default is Gregorian.Types.Localized.
        */
        this (Type type = Type.Localized) 
        {
                type_ = type;
        }

        /**
        * Overridden. Returns a Time value set to the specified date and time in the specified _era.
        * Params:
        *   year = An integer representing the _year.
        *   month = An integer representing the _month.
        *   day = An integer representing the _day.
        *   hour = An integer representing the _hour.
        *   minute = An integer representing the _minute.
        *   second = An integer representing the _second.
        *   millisecond = An integer representing the _millisecond.
        *   era = An integer representing the _era.
        * Returns: A Time set to the specified date and time.
        */
        override Time toTime (uint year, uint month, uint day, uint hour, uint minute, uint second, uint millisecond, uint era)
        {
                return Time (getDateTicks(year, month, day, era) + getTimeTicks(hour, minute, second)) + TimeSpan.millis(millisecond);
        }

        /**
        * Overridden. Returns the day of the week in the specified Time.
        * Params: time = A Time value.
        * Returns: A DayOfWeek value representing the day of the week of time.
        */
        override DayOfWeek getDayOfWeek(Time time) 
        {
                int dow;
                if(time.ticks < 0)
                {
                        dow = ((time.ticks + 1) / TimeSpan.TicksPerDay) % 7;
                        if(dow < 0)
                                dow += 7;
                }
                else
                        dow = (time.ticks / TimeSpan.TicksPerDay + 1) % 7;
                return cast(DayOfWeek)dow;
        }

        /**
        * Overridden. Returns the day of the month in the specified Time.
        * Params: time = A Time value.
        * Returns: An integer representing the day of the month of time.
        */
        override uint getDayOfMonth(Time time) 
        {
                return extractPart(time.ticks, DatePart.Day);
        }

        /**
        * Overridden. Returns the day of the year in the specified Time.
        * Params: time = A Time value.
        * Returns: An integer representing the day of the year of time.
        */
        override uint getDayOfYear(Time time) 
        {
                return extractPart(time.ticks, DatePart.DayOfYear);
        }

        /**
        * Overridden. Returns the month in the specified Time.
        * Params: time = A Time value.
        * Returns: An integer representing the month in time.
        */
        override uint getMonth(Time time) 
        {
                return extractPart(time.ticks, DatePart.Month);
        }

        /**
        * Overridden. Returns the year in the specified Time.
        * Params: time = A Time value.
        * Returns: An integer representing the year in time.
        */
        override uint getYear(Time time) 
        {
                return extractPart(time.ticks, DatePart.Year);
        }

        /**
        * Overridden. Returns the era in the specified Time.
        * Params: time = A Time value.
        * Returns: An integer representing the era in time.
        */
        override uint getEra(Time time) 
        {
                if(time < time.epoch)
                        return BC_ERA;
                else
                        return AD_ERA;
        }

        /**
        * Overridden. Returns the number of days in the specified _year and _month of the specified _era.
        * Params:
        *   year = An integer representing the _year.
        *   month = An integer representing the _month.
        *   era = An integer representing the _era.
        * Returns: The number of days in the specified _year and _month of the specified _era.
        */
        override uint getDaysInMonth(uint year, uint month, uint era) 
        {
                auto monthDays = isLeapYear(year, era) ? DaysToMonthLeap : DaysToMonthCommon;
                return monthDays[month] - monthDays[month - 1];
        }

        /**
        * Overridden. Returns the number of days in the specified _year of the specified _era.
        * Params:
        *   year = An integer representing the _year.
        *   era = An integer representing the _era.
        * Returns: The number of days in the specified _year in the specified _era.
        */
        override uint getDaysInYear(uint year, uint era) 
        {
                return isLeapYear(year, era) ? 366 : 365;
        }

        /**
        * Overridden. Returns the number of months in the specified _year of the specified _era.
        * Params:
        *   year = An integer representing the _year.
        *   era = An integer representing the _era.
        * Returns: The number of months in the specified _year in the specified _era.
        */
        override uint getMonthsInYear(uint year, uint era) 
        {
                return 12;
        }

        /**
        * Overridden. Indicates whether the specified _year in the specified _era is a leap _year.
        * Params: year = An integer representing the _year.
        * Params: era = An integer representing the _era.
        * Returns: true is the specified _year is a leap _year; otherwise, false.
        */
        override bool isLeapYear(uint year, uint era) 
        {
                return staticIsLeapYear(year, era);
        }

        /**
        * $(I Property.) Retrieves the GregorianTypes value indicating the language version of the Gregorian.
        * Returns: The Gregorian.Type value indicating the language version of the Gregorian.
        */
        Type calendarType() 
        {
                return type_;
        }

        /**
        * $(I Property.) Overridden. Retrieves the list of eras in the current calendar.
        * Returns: An integer array representing the eras in the current calendar.
        */
        override uint[] eras() 
        {       
                uint[] tmp = [AD_ERA, BC_ERA];
                return tmp.dup;
        }

        /**
        * $(I Property.) Overridden. Retrieves the identifier associated with the current calendar.
        * Returns: An integer representing the identifier of the current calendar.
        */
        override uint id() 
        {
                return cast(int) type_;
        }

        override void split(Time time, ref uint year, ref uint month, ref uint day, ref uint doy, ref uint dow, ref uint era)
        {
            splitDate(time.ticks, year, month, day, doy, era);
            dow = getDayOfWeek(time);
        }

        package static void splitDate (long ticks, ref uint year, ref uint month, ref uint day, ref uint dayOfYear, ref uint era) 
        {
                int numDays;

                void calculateYear()
                {
                        auto whole400Years = numDays / cast(int) TimeSpan.DaysPer400Years;
                        numDays -= whole400Years * cast(int) TimeSpan.DaysPer400Years;
                        auto whole100Years = numDays / cast(int) TimeSpan.DaysPer100Years;
                        if (whole100Years == 4)
                                whole100Years = 3;

                        numDays -= whole100Years * cast(int) TimeSpan.DaysPer100Years;
                        auto whole4Years = numDays / cast(int) TimeSpan.DaysPer4Years;
                        numDays -= whole4Years * cast(int) TimeSpan.DaysPer4Years;
                        auto wholeYears = numDays / cast(int) TimeSpan.DaysPerYear;
                        if (wholeYears == 4)
                                wholeYears = 3;

                        year = whole400Years * 400 + whole100Years * 100 + whole4Years * 4 + wholeYears + era;
                        numDays -= wholeYears * TimeSpan.DaysPerYear;
                }

                if(ticks < 0)
                {
                        // in the BC era
                        era = BC_ERA;
                        //
                        // set up numDays to be like AD.  AD days start at
                        // year 1.  However, in BC, year 1 is like AD year 0,
                        // so we must subtract one year.
                        //
                        numDays = cast(int)((-ticks - 1) / TimeSpan.TicksPerDay);
                        if(numDays < 366)
                        {
                                // in the year 1 B.C.  This is a special case
                                // leap year
                                year = 1;
                        }
                        else
                        {
                                numDays -= 366;
                                calculateYear;
                        }
                        //
                        // numDays is the number of days back from the end of
                        // the year, because the original ticks were negative
                        //
                        numDays = (staticIsLeapYear(year, era) ? 366 : 365) - numDays - 1;
                }
                else
                {
                        era = AD_ERA;
                        numDays = cast(int)(ticks / TimeSpan.TicksPerDay);
                        calculateYear;
                }
                dayOfYear = numDays + 1;

                auto monthDays = staticIsLeapYear(year, era) ? DaysToMonthLeap : DaysToMonthCommon;
                month = numDays >> 5 + 1;
                while (numDays >= monthDays[month])
                       month++;

                day = numDays - monthDays[month - 1] + 1;
        }

        package static uint extractPart (long ticks, DatePart part) 
        {
                uint year, month, day, dayOfYear, era;

                splitDate(ticks, year, month, day, dayOfYear, era);

                if (part is DatePart.Year)
                    return year;

                if (part is DatePart.Month)
                    return month;

                if (part is DatePart.DayOfYear)
                    return dayOfYear;

                return day;
        }

        package static long getDateTicks (uint year, uint month, uint day, uint era) 
        {
                auto monthDays = staticIsLeapYear(year, era) ? DaysToMonthLeap : DaysToMonthCommon;
                if(era == BC_ERA)
                {
                        year += 2;
                        return -cast(long)( (year - 3) * 365 + year / 4 - year / 100 + year / 400 + monthDays[12] - (monthDays[month - 1] + day - 1)) * TimeSpan.TicksPerDay;
                }
                else
                {
                        year--;
                        return (year * 365 + year / 4 - year / 100 + year / 400 + monthDays[month - 1] + day - 1) * TimeSpan.TicksPerDay;
                }
        }

        package static bool staticIsLeapYear(uint year, uint era)
        {
                if(era == BC_ERA)
                        return staticIsLeapYear(year - 1, AD_ERA);
                if(era == AD_ERA)
                        return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
                return false;
        }
}

debug(Gregorian)
{
        import tango.io.Stdout;

        void output(Time t)
        {
                Date d = Gregorian.generic.toDate(t);
                TimeOfDay tod = t.time;
                Stdout.format("{}/{}/{:d4} {} {}:{:d2}:{:d2}.{:d3} dow:{}",
                                d.month, d.day, d.year, d.era == Gregorian.AD_ERA ? "AD" : "BC",
                                tod.hours, tod.minutes, tod.seconds, tod.millis, d.dow).newline;
        }

        void main()
        {
                Time t = Time(365 * TimeSpan.TicksPerDay);
                output(t);
                for(int i = 0; i < 366 + 365; i++)
                {
                        t -= TimeSpan.days(1);
                        output(t);
                }
        }
}

debug(UnitTest)
{
        unittest
        {
                //
                // check Gregorian date handles positive time.
                //
                Time t = Time.epoch + TimeSpan.days(365);
                Date d = Gregorian.generic.toDate(t);
                assert(d.year == 2);
                assert(d.month == 1);
                assert(d.day == 1);
                assert(d.era == Gregorian.AD_ERA);
                assert(d.doy == 1);
                //
                // note that this is in disagreement with the Julian Calendar
                //
                assert(d.dow == Gregorian.DayOfWeek.Tuesday);

                //
                // check that it handles negative time
                //
                t = Time.epoch - TimeSpan.days(366);
                d = Gregorian.generic.toDate(t);
                assert(d.year == 1);
                assert(d.month == 1);
                assert(d.day == 1);
                assert(d.era == Gregorian.BC_ERA);
                assert(d.doy == 1);
                assert(d.dow == Gregorian.DayOfWeek.Saturday);

        }
}