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");
}
}
}
|