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