1 /******************************************************************************* 2 3 copyright: Copyright (c) 2005 John Chapman. All rights reserved 4 5 license: BSD style: $(LICENSE) 6 7 version: Mid 2005: Initial release 8 Apr 2007: reshaped 9 10 author: John Chapman, Kris, schveiguy 11 12 ******************************************************************************/ 13 14 module tango.time.chrono.Gregorian; 15 16 private import tango.time.chrono.Calendar; 17 18 private import tango.core.Exception; 19 20 /** 21 * $(ANCHOR _Gregorian) 22 * Represents the Gregorian calendar. 23 * 24 * Note that this is the Proleptic Gregorian calendar. Most calendars assume 25 * that dates before 9/14/1752 were Julian Dates. Julian differs from 26 * Gregorian in that leap years occur every 4 years, even on 100 year 27 * increments. The Proleptic Gregorian calendar applies the Gregorian leap 28 * year rules to dates before 9/14/1752, making the calculation of dates much 29 * easier. 30 */ 31 class Gregorian : Calendar 32 { 33 // import baseclass toTime() 34 alias Calendar.toTime toTime; 35 36 /// static shared instance 37 public static __gshared Gregorian generic; 38 39 enum Type 40 { 41 Localized = 1, /// Refers to the localized version of the Gregorian calendar. 42 USEnglish = 2, /// Refers to the US English version of the Gregorian calendar. 43 MiddleEastFrench = 9, /// Refers to the Middle East French version of the Gregorian calendar. 44 Arabic = 10, /// Refers to the _Arabic version of the Gregorian calendar. 45 TransliteratedEnglish = 11, /// Refers to the transliterated English version of the Gregorian calendar. 46 TransliteratedFrench = 12 /// Refers to the transliterated French version of the Gregorian calendar. 47 } 48 49 private Type type_; 50 51 /** 52 * Represents the current era. 53 */ 54 enum {AD_ERA = 1, BC_ERA = 2, MAX_YEAR = 9999}; 55 56 private __gshared immutable uint[] DaysToMonthCommon = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; 57 58 private __gshared immutable uint[] DaysToMonthLeap = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; 59 60 /** 61 * create a generic instance of this calendar 62 */ 63 shared static this() 64 { 65 generic = new Gregorian; 66 } 67 68 /** 69 * Initializes an instance of the Gregorian class using the specified GregorianTypes value. If no value is 70 * specified, the default is Gregorian.Types.Localized. 71 */ 72 this (Type type = Type.Localized) 73 { 74 type_ = type; 75 } 76 77 /** 78 * Overridden. Returns a Time value set to the specified date and time in the specified _era. 79 * Params: 80 * year = An integer representing the _year. 81 * month = An integer representing the _month. 82 * day = An integer representing the _day. 83 * hour = An integer representing the _hour. 84 * minute = An integer representing the _minute. 85 * second = An integer representing the _second. 86 * millisecond = An integer representing the _millisecond. 87 * era = An integer representing the _era. 88 * Returns: A Time set to the specified date and time. 89 */ 90 override const Time toTime (uint year, uint month, uint day, uint hour, uint minute, uint second, uint millisecond, uint era) 91 { 92 return Time (getDateTicks(year, month, day, era) + getTimeTicks(hour, minute, second)) + TimeSpan.fromMillis(millisecond); 93 } 94 95 /** 96 * Overridden. Returns the day of the week in the specified Time. 97 * Params: time = A Time value. 98 * Returns: A DayOfWeek value representing the day of the week of time. 99 */ 100 override const DayOfWeek getDayOfWeek(const(Time) time) 101 { 102 auto ticks = time.ticks; 103 int offset = 1; 104 if (ticks < 0) 105 { 106 ++ticks; 107 offset = 0; 108 } 109 110 auto dow = cast(int) ((ticks / TimeSpan.TicksPerDay + offset) % 7); 111 if (dow < 0) 112 dow += 7; 113 return cast(DayOfWeek) dow; 114 } 115 116 /** 117 * Overridden. Returns the day of the month in the specified Time. 118 * Params: time = A Time value. 119 * Returns: An integer representing the day of the month of time. 120 */ 121 override const uint getDayOfMonth(const(Time) time) 122 { 123 return extractPart(time.ticks, DatePart.Day); 124 } 125 126 /** 127 * Overridden. Returns the day of the year in the specified Time. 128 * Params: time = A Time value. 129 * Returns: An integer representing the day of the year of time. 130 */ 131 override const uint getDayOfYear(const(Time) time) 132 { 133 return extractPart(time.ticks, DatePart.DayOfYear); 134 } 135 136 /** 137 * Overridden. Returns the month in the specified Time. 138 * Params: time = A Time value. 139 * Returns: An integer representing the month in time. 140 */ 141 override const uint getMonth(const(Time) time) 142 { 143 return extractPart(time.ticks, DatePart.Month); 144 } 145 146 /** 147 * Overridden. Returns the year in the specified Time. 148 * Params: time = A Time value. 149 * Returns: An integer representing the year in time. 150 */ 151 override const uint getYear(const(Time) time) 152 { 153 return extractPart(time.ticks, DatePart.Year); 154 } 155 156 /** 157 * Overridden. Returns the era in the specified Time. 158 * Params: time = A Time value. 159 * Returns: An integer representing the era in time. 160 */ 161 override const uint getEra(const(Time) time) 162 { 163 if(time < time.epoch) 164 return BC_ERA; 165 else 166 return AD_ERA; 167 } 168 169 /** 170 * Overridden. Returns the number of days in the specified _year and _month of the specified _era. 171 * Params: 172 * year = An integer representing the _year. 173 * month = An integer representing the _month. 174 * era = An integer representing the _era. 175 * Returns: The number of days in the specified _year and _month of the specified _era. 176 */ 177 override const uint getDaysInMonth(uint year, uint month, uint era) 178 { 179 // 180 // verify args. isLeapYear verifies the year is valid. 181 // 182 if(month < 1 || month > 12) 183 argumentError("months out of range"); 184 auto monthDays = isLeapYear(year, era) ? DaysToMonthLeap : DaysToMonthCommon; 185 return monthDays[month] - monthDays[month - 1]; 186 } 187 188 /** 189 * Overridden. Returns the number of days in the specified _year of the specified _era. 190 * Params: 191 * year = An integer representing the _year. 192 * era = An integer representing the _era. 193 * Returns: The number of days in the specified _year in the specified _era. 194 */ 195 override const uint getDaysInYear(uint year, uint era) 196 { 197 return isLeapYear(year, era) ? 366 : 365; 198 } 199 200 /** 201 * Overridden. Returns the number of months in the specified _year of the specified _era. 202 * Params: 203 * year = An integer representing the _year. 204 * era = An integer representing the _era. 205 * Returns: The number of months in the specified _year in the specified _era. 206 */ 207 override const uint getMonthsInYear(uint year, uint era) 208 { 209 return 12; 210 } 211 212 /** 213 * Overridden. Indicates whether the specified _year in the specified _era is a leap _year. 214 * Params: year = An integer representing the _year. 215 * Params: era = An integer representing the _era. 216 * Returns: true is the specified _year is a leap _year; otherwise, false. 217 */ 218 override const bool isLeapYear(uint year, uint era) 219 { 220 return staticIsLeapYear(year, era); 221 } 222 223 /** 224 * $(I Property.) Retrieves the GregorianTypes value indicating the language version of the Gregorian. 225 * Returns: The Gregorian.Type value indicating the language version of the Gregorian. 226 */ 227 @property const Type calendarType() 228 { 229 return type_; 230 } 231 232 /** 233 * $(I Property.) Overridden. Retrieves the list of eras in the current calendar. 234 * Returns: An integer array representing the eras in the current calendar. 235 */ 236 @property override const uint[] eras() 237 { 238 uint[] tmp = [AD_ERA, BC_ERA]; 239 return tmp.dup; 240 } 241 242 /** 243 * $(I Property.) Overridden. Retrieves the identifier associated with the current calendar. 244 * Returns: An integer representing the identifier of the current calendar. 245 */ 246 @property override const uint id() 247 { 248 return cast(int) type_; 249 } 250 251 /** 252 * Overridden. Get the components of a Time structure using the rules 253 * of the calendar. This is useful if you want more than one of the 254 * given components. Note that this doesn't handle the time of day, 255 * as that is calculated directly from the Time struct. 256 */ 257 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) 258 { 259 splitDate(time.ticks, year, month, day, doy, era); 260 dow = getDayOfWeek(time); 261 } 262 263 /** 264 * Overridden. Returns a new Time with the specified number of months 265 * added. If the months are negative, the months are subtracted. 266 * 267 * If the target month does not support the day component of the input 268 * time, then an error will be thrown, unless truncateDay is set to 269 * true. If truncateDay is set to true, then the day is reduced to 270 * the maximum day of that month. 271 * 272 * For example, adding one month to 1/31/2000 with truncateDay set to 273 * true results in 2/28/2000. 274 * 275 * Params: t = A time to add the months to 276 * Params: nMonths = The number of months to add. This can be 277 * negative. 278 * Params: truncateDay = Round the day down to the maximum day of the 279 * target month if necessary. 280 * 281 * Returns: A Time that represents the provided time with the number 282 * of months added. 283 */ 284 override const Time addMonths(const(Time) t, int nMonths, bool truncateDay=false) 285 { 286 // 287 // We know all years are 12 months, so use the to/from date 288 // methods to make the calculation an O(1) operation 289 // 290 auto date = toDate(t); 291 nMonths += date.month - 1; 292 int nYears = nMonths / 12; 293 nMonths %= 12; 294 if(nMonths < 0) 295 { 296 nYears--; 297 nMonths += 12; 298 } 299 int realYear = date.year; 300 if(date.era == BC_ERA) 301 realYear = -realYear + 1; 302 realYear += nYears; 303 if(realYear < 1) 304 { 305 date.year = -realYear + 1; 306 date.era = BC_ERA; 307 } 308 else 309 { 310 date.year = realYear; 311 date.era = AD_ERA; 312 } 313 date.month = nMonths + 1; 314 // 315 // truncate the day if necessary 316 // 317 if(truncateDay) 318 { 319 uint maxday = getDaysInMonth(date.year, date.month, date.era); 320 if(date.day > maxday) 321 date.day = maxday; 322 } 323 auto tod = t.ticks % TimeSpan.TicksPerDay; 324 if(tod < 0) 325 tod += TimeSpan.TicksPerDay; 326 return toTime(date) + TimeSpan(tod); 327 } 328 329 /** 330 * Overridden. Add the specified number of years to the given Time. 331 * 332 * Note that the Gregorian calendar takes into account that BC time 333 * is negative, and supports crossing from BC to AD. 334 * 335 * Params: t = A time to add the years to 336 * Params: nYears = The number of years to add. This can be negative. 337 * 338 * Returns: A Time that represents the provided time with the number 339 * of years added. 340 */ 341 override const Time addYears(const(Time) t, int nYears) 342 { 343 return addMonths(t, nYears * 12); 344 } 345 346 package static void splitDate (long ticks, ref uint year, ref uint month, ref uint day, ref uint dayOfYear, ref uint era) 347 { 348 int numDays; 349 350 void calculateYear() 351 { 352 auto whole400Years = numDays / cast(int) TimeSpan.DaysPer400Years; 353 numDays -= whole400Years * cast(int) TimeSpan.DaysPer400Years; 354 auto whole100Years = numDays / cast(int) TimeSpan.DaysPer100Years; 355 if (whole100Years == 4) 356 whole100Years = 3; 357 358 numDays -= whole100Years * cast(int) TimeSpan.DaysPer100Years; 359 auto whole4Years = numDays / cast(int) TimeSpan.DaysPer4Years; 360 numDays -= whole4Years * cast(int) TimeSpan.DaysPer4Years; 361 auto wholeYears = numDays / cast(int) TimeSpan.DaysPerYear; 362 if (wholeYears == 4) 363 wholeYears = 3; 364 365 year = whole400Years * 400 + whole100Years * 100 + whole4Years * 4 + wholeYears + era; 366 numDays -= wholeYears * TimeSpan.DaysPerYear; 367 } 368 369 if(ticks < 0) 370 { 371 // in the BC era 372 era = BC_ERA; 373 // 374 // set up numDays to be like AD. AD days start at 375 // year 1. However, in BC, year 1 is like AD year 0, 376 // so we must subtract one year. 377 // 378 numDays = cast(int)((-ticks - 1) / TimeSpan.TicksPerDay); 379 if(numDays < 366) 380 { 381 // in the year 1 B.C. This is a special case 382 // leap year 383 year = 1; 384 } 385 else 386 { 387 numDays -= 366; 388 calculateYear(); 389 } 390 // 391 // numDays is the number of days back from the end of 392 // the year, because the original ticks were negative 393 // 394 numDays = (staticIsLeapYear(year, era) ? 366 : 365) - numDays - 1; 395 } 396 else 397 { 398 era = AD_ERA; 399 numDays = cast(int)(ticks / TimeSpan.TicksPerDay); 400 calculateYear(); 401 } 402 dayOfYear = numDays + 1; 403 404 auto monthDays = staticIsLeapYear(year, era) ? DaysToMonthLeap : DaysToMonthCommon; 405 month = numDays >> 5 + 1; 406 while (numDays >= monthDays[month]) 407 month++; 408 409 day = numDays - monthDays[month - 1] + 1; 410 } 411 412 package static uint extractPart (long ticks, DatePart part) 413 { 414 uint year, month, day, dayOfYear, era; 415 416 splitDate(ticks, year, month, day, dayOfYear, era); 417 418 if (part is DatePart.Year) 419 return year; 420 421 if (part is DatePart.Month) 422 return month; 423 424 if (part is DatePart.DayOfYear) 425 return dayOfYear; 426 427 return day; 428 } 429 430 package static long getDateTicks (uint year, uint month, uint day, uint era) 431 { 432 // 433 // verify arguments, getDaysInMonth verifies the year and 434 // month is valid. 435 // 436 if(day < 1 || day > generic.getDaysInMonth(year, month, era)) 437 argumentError("days out of range"); 438 439 auto monthDays = staticIsLeapYear(year, era) ? DaysToMonthLeap : DaysToMonthCommon; 440 if(era == BC_ERA) 441 { 442 year += 2; 443 return -cast(long)( (year - 3) * 365 + year / 4 - year / 100 + year / 400 + monthDays[12] - (monthDays[month - 1] + day - 1)) * TimeSpan.TicksPerDay; 444 } 445 else 446 { 447 year--; 448 return (year * 365 + year / 4 - year / 100 + year / 400 + monthDays[month - 1] + day - 1) * TimeSpan.TicksPerDay; 449 } 450 } 451 452 package static bool staticIsLeapYear(uint year, uint era) 453 { 454 if(year < 1) 455 argumentError("year cannot be 0"); 456 if(era == BC_ERA) 457 { 458 if(year == 1) 459 return true; 460 return staticIsLeapYear(year - 1, AD_ERA); 461 } 462 if(era == AD_ERA || era == CURRENT_ERA) 463 return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); 464 return false; 465 } 466 467 package static void argumentError(const(char[]) str) 468 { 469 throw new IllegalArgumentException(str.idup); 470 } 471 } 472 473 debug(Gregorian) 474 { 475 import tango.io.Stdout; 476 477 void output(Time t) 478 { 479 Date d = Gregorian.generic.toDate(t); 480 TimeOfDay tod = t.time; 481 Stdout.format("{}/{}/{:d4} {} {}:{:d2}:{:d2}.{:d3} dow:{}", 482 d.month, d.day, d.year, d.era == Gregorian.AD_ERA ? "AD" : "BC", 483 tod.hours, tod.minutes, tod.seconds, tod.millis, d.dow).newline; 484 } 485 486 void main() 487 { 488 Time t = Time(365 * TimeSpan.TicksPerDay); 489 output(t); 490 for(int i = 0; i < 366 + 365; i++) 491 { 492 t -= TimeSpan.fromDays(1); 493 output(t); 494 } 495 } 496 } 497 498 debug(UnitTest) 499 { 500 unittest 501 { 502 // 503 // check Gregorian date handles positive time. 504 // 505 Time t = Time.epoch + TimeSpan.fromDays(365); 506 Date d = Gregorian.generic.toDate(t); 507 assert(d.year == 2); 508 assert(d.month == 1); 509 assert(d.day == 1); 510 assert(d.era == Gregorian.AD_ERA); 511 assert(d.doy == 1); 512 // 513 // note that this is in disagreement with the Julian Calendar 514 // 515 assert(d.dow == Gregorian.DayOfWeek.Tuesday); 516 517 // 518 // check that it handles negative time 519 // 520 t = Time.epoch - TimeSpan.fromDays(366); 521 d = Gregorian.generic.toDate(t); 522 assert(d.year == 1); 523 assert(d.month == 1); 524 assert(d.day == 1); 525 assert(d.era == Gregorian.BC_ERA); 526 assert(d.doy == 1); 527 assert(d.dow == Gregorian.DayOfWeek.Saturday); 528 529 // 530 // check that addMonths works properly, add 15 months to 531 // 2/3/2004, 04:05:06.007008, then subtract 15 months again. 532 // 533 t = Gregorian.generic.toTime(2004, 2, 3, 4, 5, 6, 7) + TimeSpan.fromMicros(8); 534 d = Gregorian.generic.toDate(t); 535 assert(d.year == 2004); 536 assert(d.month == 2); 537 assert(d.day == 3); 538 assert(d.era == Gregorian.AD_ERA); 539 assert(d.doy == 34); 540 assert(d.dow == Gregorian.DayOfWeek.Tuesday); 541 542 auto t2 = Gregorian.generic.addMonths(t, 15); 543 d = Gregorian.generic.toDate(t2); 544 assert(d.year == 2005); 545 assert(d.month == 5); 546 assert(d.day == 3); 547 assert(d.era == Gregorian.AD_ERA); 548 assert(d.doy == 123); 549 assert(d.dow == Gregorian.DayOfWeek.Tuesday); 550 551 t2 = Gregorian.generic.addMonths(t2, -15); 552 d = Gregorian.generic.toDate(t2); 553 assert(d.year == 2004); 554 assert(d.month == 2); 555 assert(d.day == 3); 556 assert(d.era == Gregorian.AD_ERA); 557 assert(d.doy == 34); 558 assert(d.dow == Gregorian.DayOfWeek.Tuesday); 559 560 assert(t == t2); 561 562 // 563 // verify that illegal argument exceptions occur 564 // 565 try 566 { 567 t = Gregorian.generic.toTime (0, 1, 1, 0, 0, 0, 0, Gregorian.AD_ERA); 568 assert(false, "Did not throw illegal argument exception"); 569 } 570 catch(Exception iae) 571 { 572 } 573 try 574 { 575 t = Gregorian.generic.toTime (1, 0, 1, 0, 0, 0, 0, Gregorian.AD_ERA); 576 assert(false, "Did not throw illegal argument exception"); 577 } 578 catch(IllegalArgumentException iae) 579 { 580 } 581 try 582 { 583 t = Gregorian.generic.toTime (1, 1, 0, 0, 0, 0, 0, Gregorian.BC_ERA); 584 assert(false, "Did not throw illegal argument exception"); 585 } 586 catch(IllegalArgumentException iae) 587 { 588 } 589 590 try 591 { 592 t = Gregorian.generic.toTime(2000, 1, 31, 0, 0, 0, 0); 593 t = Gregorian.generic.addMonths(t, 1); 594 assert(false, "Did not throw illegal argument exception"); 595 } 596 catch(IllegalArgumentException iae) 597 { 598 } 599 600 try 601 { 602 t = Gregorian.generic.toTime(2000, 1, 31, 0, 0, 0, 0); 603 t = Gregorian.generic.addMonths(t, 1, true); 604 assert(Gregorian.generic.getDayOfMonth(t) == 29); 605 } 606 catch(IllegalArgumentException iae) 607 { 608 assert(false, "Should not throw illegal argument exception"); 609 } 610 611 612 613 } 614 }