| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615 | /******************************************************************************* 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; private import tango.core.Exception; /** * $(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 __gshared 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 enum uint[] DaysToMonthCommon = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; private static enum uint[] DaysToMonthLeap = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; /** * create a generic instance of this calendar */ shared 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 const 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.fromMillis(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 const DayOfWeek getDayOfWeek(const(Time) time) { auto ticks = time.ticks; int offset = 1; if (ticks < 0) { ++ticks; offset = 0; } auto dow = cast(int) ((ticks / TimeSpan.TicksPerDay + offset) % 7); if (dow < 0) dow += 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 const uint getDayOfMonth(const(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 const uint getDayOfYear(const(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 const uint getMonth(const(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 const uint getYear(const(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 const uint getEra(const(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 const uint getDaysInMonth(uint year, uint month, uint era) { // // verify args. isLeapYear verifies the year is valid. // if(month < 1 || month > 12) argumentError("months out of range"); 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 const 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 const 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 const 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. */ @property const 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. */ @property override const 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. */ @property override const uint id() { return cast(int) type_; } /** * Overridden. Get the components of a Time structure using the rules * of the calendar. This is useful if you want more than one of the * given components. Note that this doesn't handle the time of day, * as that is calculated directly from the Time struct. */ override const void split(const(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); } /** * Overridden. Returns a new Time with the specified number of months * added. If the months are negative, the months are subtracted. * * If the target month does not support the day component of the input * time, then an error will be thrown, unless truncateDay is set to * true. If truncateDay is set to true, then the day is reduced to * the maximum day of that month. * * For example, adding one month to 1/31/2000 with truncateDay set to * true results in 2/28/2000. * * Params: t = A time to add the months to * Params: nMonths = The number of months to add. This can be * negative. * Params: truncateDay = Round the day down to the maximum day of the * target month if necessary. * * Returns: A Time that represents the provided time with the number * of months added. */ override const Time addMonths(const(Time) t, int nMonths, bool truncateDay=false) { // // We know all years are 12 months, so use the to/from date // methods to make the calculation an O(1) operation // auto date = toDate(t); nMonths += date.month - 1; int nYears = nMonths / 12; nMonths %= 12; if(nMonths < 0) { nYears--; nMonths += 12; } int realYear = date.year; if(date.era == BC_ERA) realYear = -realYear + 1; realYear += nYears; if(realYear < 1) { date.year = -realYear + 1; date.era = BC_ERA; } else { date.year = realYear; date.era = AD_ERA; } date.month = nMonths + 1; // // truncate the day if necessary // if(truncateDay) { uint maxday = getDaysInMonth(date.year, date.month, date.era); if(date.day > maxday) date.day = maxday; } auto tod = t.ticks % TimeSpan.TicksPerDay; if(tod < 0) tod += TimeSpan.TicksPerDay; return toTime(date) + TimeSpan(tod); } /** * Overridden. Add the specified number of years to the given Time. * * Note that the Gregorian calendar takes into account that BC time * is negative, and supports crossing from BC to AD. * * Params: t = A time to add the years to * Params: nYears = The number of years to add. This can be negative. * * Returns: A Time that represents the provided time with the number * of years added. */ override const Time addYears(const(Time) t, int nYears) { return addMonths(t, nYears * 12); } 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) { // // verify arguments, getDaysInMonth verifies the year and // month is valid. // if(day < 1 || day > generic.getDaysInMonth(year, month, era)) argumentError("days out of range"); 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(year < 1) argumentError("year cannot be 0"); if(era == BC_ERA) { if(year == 1) return true; return staticIsLeapYear(year - 1, AD_ERA); } if(era == AD_ERA || era == CURRENT_ERA) return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); return false; } package static void argumentError(const(char[]) str) { throw new IllegalArgumentException(str.idup); } } 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.fromDays(1); output(t); } } } debug(UnitTest) { unittest { // // check Gregorian date handles positive time. // Time t = Time.epoch + TimeSpan.fromDays(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.fromDays(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); // // check that addMonths works properly, add 15 months to // 2/3/2004, 04:05:06.007008, then subtract 15 months again. // t = Gregorian.generic.toTime(2004, 2, 3, 4, 5, 6, 7) + TimeSpan.fromMicros(8); d = Gregorian.generic.toDate(t); assert(d.year == 2004); assert(d.month == 2); assert(d.day == 3); assert(d.era == Gregorian.AD_ERA); assert(d.doy == 34); assert(d.dow == Gregorian.DayOfWeek.Tuesday); auto t2 = Gregorian.generic.addMonths(t, 15); d = Gregorian.generic.toDate(t2); assert(d.year == 2005); assert(d.month == 5); assert(d.day == 3); assert(d.era == Gregorian.AD_ERA); assert(d.doy == 123); assert(d.dow == Gregorian.DayOfWeek.Tuesday); t2 = Gregorian.generic.addMonths(t2, -15); d = Gregorian.generic.toDate(t2); assert(d.year == 2004); assert(d.month == 2); assert(d.day == 3); assert(d.era == Gregorian.AD_ERA); assert(d.doy == 34); assert(d.dow == Gregorian.DayOfWeek.Tuesday); assert(t == t2); // // verify that illegal argument exceptions occur // try { t = Gregorian.generic.toTime (0, 1, 1, 0, 0, 0, 0, Gregorian.AD_ERA); assert(false, "Did not throw illegal argument exception"); } catch(Exception iae) { } try { t = Gregorian.generic.toTime (1, 0, 1, 0, 0, 0, 0, Gregorian.AD_ERA); assert(false, "Did not throw illegal argument exception"); } catch(IllegalArgumentException iae) { } try { t = Gregorian.generic.toTime (1, 1, 0, 0, 0, 0, 0, Gregorian.BC_ERA); assert(false, "Did not throw illegal argument exception"); } catch(IllegalArgumentException iae) { } try { t = Gregorian.generic.toTime(2000, 1, 31, 0, 0, 0, 0); t = Gregorian.generic.addMonths(t, 1); assert(false, "Did not throw illegal argument exception"); } catch(IllegalArgumentException iae) { } try { t = Gregorian.generic.toTime(2000, 1, 31, 0, 0, 0, 0); t = Gregorian.generic.addMonths(t, 1, true); assert(Gregorian.generic.getDayOfMonth(t) == 29); } catch(IllegalArgumentException iae) { assert(false, "Should not throw illegal argument exception"); } } } |