001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2022, by David Gilbert and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * --------------- 028 * SerialDate.java 029 * --------------- 030 * (C) Copyright 2006-2022, by David Gilbert and Contributors. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): -; 034 * 035 */ 036 037package org.jfree.chart.date; 038 039import java.io.Serializable; 040import java.text.DateFormatSymbols; 041import java.text.SimpleDateFormat; 042import java.util.Calendar; 043import java.util.GregorianCalendar; 044 045/** 046 * An abstract class that defines our requirements for manipulating dates, 047 * without tying down a particular implementation. 048 * <P> 049 * Requirement 1 : match at least what Excel does for dates; 050 * Requirement 2 : the date represented by the class is immutable; 051 * <P> 052 * Why not just use java.util.Date? We will, when it makes sense. At times, 053 * java.util.Date can be *too* precise - it represents an instant in time, 054 * accurate to 1/1000th of a second (with the date itself depending on the 055 * time-zone). Sometimes we just want to represent a particular day (e.g. 21 056 * January 2015) without concerning ourselves about the time of day, or the 057 * time-zone, or anything else. That's what we've defined SerialDate for. 058 * <P> 059 * You can call getInstance() to get a concrete subclass of SerialDate, 060 * without worrying about the exact implementation. 061 */ 062public abstract class SerialDate implements Comparable, Serializable, 063 MonthConstants { 064 065 /** For serialization. */ 066 private static final long serialVersionUID = -293716040467423637L; 067 068 /** Date format symbols. */ 069 public static final DateFormatSymbols 070 DATE_FORMAT_SYMBOLS = new SimpleDateFormat().getDateFormatSymbols(); 071 072 /** The serial number for 1 January 1900. */ 073 public static final int SERIAL_LOWER_BOUND = 2; 074 075 /** The serial number for 31 December 9999. */ 076 public static final int SERIAL_UPPER_BOUND = 2958465; 077 078 /** The lowest year value supported by this date format. */ 079 public static final int MINIMUM_YEAR_SUPPORTED = 1900; 080 081 /** The highest year value supported by this date format. */ 082 public static final int MAXIMUM_YEAR_SUPPORTED = 9999; 083 084 /** Useful constant for Monday. Equivalent to java.util.Calendar.MONDAY. */ 085 public static final int MONDAY = Calendar.MONDAY; 086 087 /** 088 * Useful constant for Tuesday. Equivalent to java.util.Calendar.TUESDAY. 089 */ 090 public static final int TUESDAY = Calendar.TUESDAY; 091 092 /** 093 * Useful constant for Wednesday. Equivalent to 094 * java.util.Calendar.WEDNESDAY. 095 */ 096 public static final int WEDNESDAY = Calendar.WEDNESDAY; 097 098 /** 099 * Useful constant for Thrusday. Equivalent to java.util.Calendar.THURSDAY. 100 */ 101 public static final int THURSDAY = Calendar.THURSDAY; 102 103 /** Useful constant for Friday. Equivalent to java.util.Calendar.FRIDAY. */ 104 public static final int FRIDAY = Calendar.FRIDAY; 105 106 /** 107 * Useful constant for Saturday. Equivalent to java.util.Calendar.SATURDAY. 108 */ 109 public static final int SATURDAY = Calendar.SATURDAY; 110 111 /** Useful constant for Sunday. Equivalent to java.util.Calendar.SUNDAY. */ 112 public static final int SUNDAY = Calendar.SUNDAY; 113 114 /** The number of days in each month in non leap years. */ 115 static final int[] LAST_DAY_OF_MONTH = 116 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 117 118 /** The number of days in a (non-leap) year up to the end of each month. */ 119 static final int[] AGGREGATE_DAYS_TO_END_OF_MONTH = 120 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; 121 122 /** The number of days in a year up to the end of the preceding month. */ 123 static final int[] AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH = 124 {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; 125 126 /** The number of days in a leap year up to the end of each month. */ 127 static final int[] LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_MONTH = 128 {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}; 129 130 /** 131 * The number of days in a leap year up to the end of the preceding month. 132 */ 133 static final int[] 134 LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH = 135 {0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}; 136 137 /** A useful constant for referring to the first week in a month. */ 138 public static final int FIRST_WEEK_IN_MONTH = 1; 139 140 /** A useful constant for referring to the second week in a month. */ 141 public static final int SECOND_WEEK_IN_MONTH = 2; 142 143 /** A useful constant for referring to the third week in a month. */ 144 public static final int THIRD_WEEK_IN_MONTH = 3; 145 146 /** A useful constant for referring to the fourth week in a month. */ 147 public static final int FOURTH_WEEK_IN_MONTH = 4; 148 149 /** A useful constant for referring to the last week in a month. */ 150 public static final int LAST_WEEK_IN_MONTH = 0; 151 152 /** Useful range constant. */ 153 public static final int INCLUDE_NONE = 0; 154 155 /** Useful range constant. */ 156 public static final int INCLUDE_FIRST = 1; 157 158 /** Useful range constant. */ 159 public static final int INCLUDE_SECOND = 2; 160 161 /** Useful range constant. */ 162 public static final int INCLUDE_BOTH = 3; 163 164 /** 165 * Useful constant for specifying a day of the week relative to a fixed 166 * date. 167 */ 168 public static final int PRECEDING = -1; 169 170 /** 171 * Useful constant for specifying a day of the week relative to a fixed 172 * date. 173 */ 174 public static final int NEAREST = 0; 175 176 /** 177 * Useful constant for specifying a day of the week relative to a fixed 178 * date. 179 */ 180 public static final int FOLLOWING = 1; 181 182 /** A description for the date. */ 183 private String description; 184 185 /** 186 * Default constructor. 187 */ 188 protected SerialDate() { 189 } 190 191 /** 192 * Returns {@code true} if the supplied integer code represents a 193 * valid day-of-the-week, and {@code false} otherwise. 194 * 195 * @param code the code being checked for validity. 196 * 197 * @return {@code true} if the supplied integer code represents a 198 * valid day-of-the-week, and {@code false} otherwise. 199 */ 200 public static boolean isValidWeekdayCode(int code) { 201 202 switch(code) { 203 case SUNDAY: 204 case MONDAY: 205 case TUESDAY: 206 case WEDNESDAY: 207 case THURSDAY: 208 case FRIDAY: 209 case SATURDAY: 210 return true; 211 default: 212 return false; 213 } 214 215 } 216 217 /** 218 * Converts the supplied string to a day of the week. 219 * 220 * @param s a string representing the day of the week. 221 * 222 * @return {@code -1} if the string is not convertable, the day of 223 * the week otherwise. 224 */ 225 public static int stringToWeekdayCode(String s) { 226 227 final String[] shortWeekdayNames 228 = DATE_FORMAT_SYMBOLS.getShortWeekdays(); 229 final String[] weekDayNames = DATE_FORMAT_SYMBOLS.getWeekdays(); 230 231 int result = -1; 232 s = s.trim(); 233 for (int i = 0; i < weekDayNames.length; i++) { 234 if (s.equals(shortWeekdayNames[i])) { 235 result = i; 236 break; 237 } 238 if (s.equals(weekDayNames[i])) { 239 result = i; 240 break; 241 } 242 } 243 return result; 244 245 } 246 247 /** 248 * Returns a string representing the supplied day-of-the-week. 249 * <P> 250 * Need to find a better approach. 251 * 252 * @param weekday the day of the week. 253 * 254 * @return a string representing the supplied day-of-the-week. 255 */ 256 public static String weekdayCodeToString(int weekday) { 257 final String[] weekdays = DATE_FORMAT_SYMBOLS.getWeekdays(); 258 return weekdays[weekday]; 259 } 260 261 /** 262 * Returns an array of month names. 263 * 264 * @return an array of month names. 265 */ 266 public static String[] getMonths() { 267 268 return getMonths(false); 269 270 } 271 272 /** 273 * Returns an array of month names. 274 * 275 * @param shortened a flag indicating that shortened month names should 276 * be returned. 277 * 278 * @return an array of month names. 279 */ 280 public static String[] getMonths(boolean shortened) { 281 if (shortened) { 282 return DATE_FORMAT_SYMBOLS.getShortMonths(); 283 } 284 else { 285 return DATE_FORMAT_SYMBOLS.getMonths(); 286 } 287 } 288 289 /** 290 * Returns true if the supplied integer code represents a valid month. 291 * 292 * @param code the code being checked for validity. 293 * 294 * @return {@code true} if the supplied integer code represents a 295 * valid month. 296 */ 297 public static boolean isValidMonthCode(int code) { 298 299 switch(code) { 300 case JANUARY: 301 case FEBRUARY: 302 case MARCH: 303 case APRIL: 304 case MAY: 305 case JUNE: 306 case JULY: 307 case AUGUST: 308 case SEPTEMBER: 309 case OCTOBER: 310 case NOVEMBER: 311 case DECEMBER: 312 return true; 313 default: 314 return false; 315 } 316 317 } 318 319 /** 320 * Returns the quarter for the specified month. 321 * 322 * @param code the month code (1-12). 323 * 324 * @return the quarter that the month belongs to. 325 */ 326 public static int monthCodeToQuarter(int code) { 327 328 switch(code) { 329 case JANUARY: 330 case FEBRUARY: 331 case MARCH: return 1; 332 case APRIL: 333 case MAY: 334 case JUNE: return 2; 335 case JULY: 336 case AUGUST: 337 case SEPTEMBER: return 3; 338 case OCTOBER: 339 case NOVEMBER: 340 case DECEMBER: return 4; 341 default: throw new IllegalArgumentException( 342 "SerialDate.monthCodeToQuarter: invalid month code."); 343 } 344 345 } 346 347 /** 348 * Returns a string representing the supplied month. 349 * <P> 350 * The string returned is the long form of the month name taken from the 351 * default locale. 352 * 353 * @param month the month. 354 * 355 * @return a string representing the supplied month. 356 */ 357 public static String monthCodeToString(int month) { 358 return monthCodeToString(month, false); 359 } 360 361 /** 362 * Returns a string representing the supplied month. 363 * <P> 364 * The string returned is the long or short form of the month name taken 365 * from the default locale. 366 * 367 * @param month the month. 368 * @param shortened if {@code true} return the abbreviation of the month. 369 * 370 * @return a string representing the supplied month. 371 */ 372 public static String monthCodeToString(int month, boolean shortened) { 373 374 // check arguments... 375 if (!isValidMonthCode(month)) { 376 throw new IllegalArgumentException( 377 "SerialDate.monthCodeToString: month outside valid range."); 378 } 379 380 final String[] months; 381 382 if (shortened) { 383 months = DATE_FORMAT_SYMBOLS.getShortMonths(); 384 } 385 else { 386 months = DATE_FORMAT_SYMBOLS.getMonths(); 387 } 388 389 return months[month - 1]; 390 391 } 392 393 /** 394 * Converts a string to a month code. 395 * <P> 396 * This method will return one of the constants JANUARY, FEBRUARY, ..., 397 * DECEMBER that corresponds to the string. If the string is not 398 * recognised, this method returns -1. 399 * 400 * @param s the string to parse. 401 * 402 * @return {@code -1} if the string is not parseable, the month of the 403 * year otherwise. 404 */ 405 public static int stringToMonthCode(String s) { 406 407 final String[] shortMonthNames = DATE_FORMAT_SYMBOLS.getShortMonths(); 408 final String[] monthNames = DATE_FORMAT_SYMBOLS.getMonths(); 409 410 int result = -1; 411 s = s.trim(); 412 413 // first try parsing the string as an integer (1-12)... 414 try { 415 result = Integer.parseInt(s); 416 } 417 catch (NumberFormatException e) { 418 // suppress 419 } 420 421 // now search through the month names... 422 if ((result < 1) || (result > 12)) { 423 for (int i = 0; i < monthNames.length; i++) { 424 if (s.equals(shortMonthNames[i])) { 425 result = i + 1; 426 break; 427 } 428 if (s.equals(monthNames[i])) { 429 result = i + 1; 430 break; 431 } 432 } 433 } 434 435 return result; 436 437 } 438 439 /** 440 * Returns true if the supplied integer code represents a valid 441 * week-in-the-month, and false otherwise. 442 * 443 * @param code the code being checked for validity. 444 * @return {@code true} if the supplied integer code represents a 445 * valid week-in-the-month. 446 */ 447 public static boolean isValidWeekInMonthCode(int code) { 448 switch(code) { 449 case FIRST_WEEK_IN_MONTH: 450 case SECOND_WEEK_IN_MONTH: 451 case THIRD_WEEK_IN_MONTH: 452 case FOURTH_WEEK_IN_MONTH: 453 case LAST_WEEK_IN_MONTH: return true; 454 default: return false; 455 } 456 } 457 458 /** 459 * Determines whether or not the specified year is a leap year. 460 * 461 * @param yyyy the year (in the range 1900 to 9999). 462 * 463 * @return {@code true} if the specified year is a leap year. 464 */ 465 public static boolean isLeapYear(int yyyy) { 466 467 if ((yyyy % 4) != 0) { 468 return false; 469 } 470 else if ((yyyy % 400) == 0) { 471 return true; 472 } 473 else if ((yyyy % 100) == 0) { 474 return false; 475 } 476 else { 477 return true; 478 } 479 480 } 481 482 /** 483 * Returns the number of leap years from 1900 to the specified year 484 * INCLUSIVE. 485 * <P> 486 * Note that 1900 is not a leap year. 487 * 488 * @param yyyy the year (in the range 1900 to 9999). 489 * 490 * @return the number of leap years from 1900 to the specified year. 491 */ 492 public static int leapYearCount(int yyyy) { 493 int leap4 = (yyyy - 1896) / 4; 494 int leap100 = (yyyy - 1800) / 100; 495 int leap400 = (yyyy - 1600) / 400; 496 return leap4 - leap100 + leap400; 497 } 498 499 /** 500 * Returns the number of the last day of the month, taking into account 501 * leap years. 502 * 503 * @param month the month. 504 * @param yyyy the year (in the range 1900 to 9999). 505 * 506 * @return the number of the last day of the month. 507 */ 508 public static int lastDayOfMonth(int month, int yyyy) { 509 510 final int result = LAST_DAY_OF_MONTH[month]; 511 if (month != FEBRUARY) { 512 return result; 513 } 514 else if (isLeapYear(yyyy)) { 515 return result + 1; 516 } 517 else { 518 return result; 519 } 520 521 } 522 523 /** 524 * Creates a new date by adding the specified number of days to the base 525 * date. 526 * 527 * @param days the number of days to add (can be negative). 528 * @param base the base date. 529 * 530 * @return a new date. 531 */ 532 public static SerialDate addDays(int days, SerialDate base) { 533 int serialDayNumber = base.toSerial() + days; 534 return SerialDate.createInstance(serialDayNumber); 535 } 536 537 /** 538 * Creates a new date by adding the specified number of months to the base 539 * date. 540 * <P> 541 * If the base date is close to the end of the month, the day on the result 542 * may be adjusted slightly: 31 May + 1 month = 30 June. 543 * 544 * @param months the number of months to add (can be negative). 545 * @param base the base date. 546 * 547 * @return a new date. 548 */ 549 public static SerialDate addMonths(int months, SerialDate base) { 550 int yy = (12 * base.getYYYY() + base.getMonth() + months - 1) / 12; 551 if (yy < MINIMUM_YEAR_SUPPORTED || yy > MAXIMUM_YEAR_SUPPORTED) { 552 throw new IllegalArgumentException("Call to addMonths resulted in unsupported year"); 553 } 554 int mm = (12 * base.getYYYY() + base.getMonth() + months - 1) % 12 + 1; 555 int dd = Math.min(base.getDayOfMonth(), 556 SerialDate.lastDayOfMonth(mm, yy)); 557 return SerialDate.createInstance(dd, mm, yy); 558 } 559 560 /** 561 * Creates a new date by adding the specified number of years to the base 562 * date. 563 * 564 * @param years the number of years to add (can be negative). 565 * @param base the base date. 566 * 567 * @return A new date. 568 */ 569 public static SerialDate addYears(int years, SerialDate base) { 570 int baseY = base.getYYYY(); 571 int baseM = base.getMonth(); 572 int baseD = base.getDayOfMonth(); 573 574 int targetY = baseY + years; 575 if (targetY < MINIMUM_YEAR_SUPPORTED || targetY > MAXIMUM_YEAR_SUPPORTED) { 576 throw new IllegalArgumentException("Call to addYears resulted in unsupported year"); 577 } 578 int targetD = Math.min(baseD, SerialDate.lastDayOfMonth(baseM, targetY)); 579 return SerialDate.createInstance(targetD, baseM, targetY); 580 } 581 582 /** 583 * Returns the latest date that falls on the specified day-of-the-week and 584 * is BEFORE the base date. 585 * 586 * @param targetWeekday a code for the target day-of-the-week. 587 * @param base the base date. 588 * 589 * @return the latest date that falls on the specified day-of-the-week and 590 * is BEFORE the base date. 591 */ 592 public static SerialDate getPreviousDayOfWeek(int targetWeekday, 593 SerialDate base) { 594 595 // check arguments... 596 if (!SerialDate.isValidWeekdayCode(targetWeekday)) { 597 throw new IllegalArgumentException("Invalid day-of-the-week code."); 598 } 599 600 // find the date... 601 int adjust; 602 int baseDOW = base.getDayOfWeek(); 603 if (baseDOW > targetWeekday) { 604 adjust = Math.min(0, targetWeekday - baseDOW); 605 } else { 606 adjust = -7 + Math.max(0, targetWeekday - baseDOW); 607 } 608 609 return SerialDate.addDays(adjust, base); 610 611 } 612 613 /** 614 * Returns the earliest date that falls on the specified day-of-the-week 615 * and is AFTER the base date. 616 * 617 * @param targetWeekday a code for the target day-of-the-week. 618 * @param base the base date. 619 * 620 * @return the earliest date that falls on the specified day-of-the-week 621 * and is AFTER the base date. 622 */ 623 public static SerialDate getFollowingDayOfWeek(int targetWeekday, 624 SerialDate base) { 625 626 // check arguments... 627 if (!SerialDate.isValidWeekdayCode(targetWeekday)) { 628 throw new IllegalArgumentException( 629 "Invalid day-of-the-week code." 630 ); 631 } 632 633 // find the date... 634 int adjust; 635 int baseDOW = base.getDayOfWeek(); 636 if (baseDOW > targetWeekday) { 637 adjust = 7 + Math.min(0, targetWeekday - baseDOW); 638 } else { 639 adjust = Math.max(0, targetWeekday - baseDOW); 640 } 641 642 return SerialDate.addDays(adjust, base); 643 } 644 645 /** 646 * Returns the date that falls on the specified day-of-the-week and is 647 * CLOSEST to the base date. 648 * 649 * @param targetDOW a code for the target day-of-the-week. 650 * @param base the base date. 651 * 652 * @return the date that falls on the specified day-of-the-week and is 653 * CLOSEST to the base date. 654 */ 655 public static SerialDate getNearestDayOfWeek(int targetDOW, SerialDate base) { 656 657 // check arguments... 658 if (!SerialDate.isValidWeekdayCode(targetDOW)) { 659 throw new IllegalArgumentException("Invalid day-of-the-week code."); 660 } 661 662 // find the date... 663 final int baseDOW = base.getDayOfWeek(); 664 int adjust = -Math.abs(targetDOW - baseDOW); 665 if (adjust >= 4) { 666 adjust = 7 - adjust; 667 } 668 if (adjust <= -4) { 669 adjust = 7 + adjust; 670 } 671 return SerialDate.addDays(adjust, base); 672 673 } 674 675 /** 676 * Rolls the date forward to the last day of the month. 677 * 678 * @param base the base date. 679 * 680 * @return a new serial date. 681 */ 682 public SerialDate getEndOfCurrentMonth(SerialDate base) { 683 int last = SerialDate.lastDayOfMonth(base.getMonth(), base.getYYYY()); 684 return SerialDate.createInstance(last, base.getMonth(), base.getYYYY()); 685 } 686 687 /** 688 * Returns a string corresponding to the week-in-the-month code. 689 * <P> 690 * Need to find a better approach. 691 * 692 * @param count an integer code representing the week-in-the-month. 693 * 694 * @return a string corresponding to the week-in-the-month code. 695 */ 696 public static String weekInMonthToString(int count) { 697 698 switch (count) { 699 case SerialDate.FIRST_WEEK_IN_MONTH : return "First"; 700 case SerialDate.SECOND_WEEK_IN_MONTH : return "Second"; 701 case SerialDate.THIRD_WEEK_IN_MONTH : return "Third"; 702 case SerialDate.FOURTH_WEEK_IN_MONTH : return "Fourth"; 703 case SerialDate.LAST_WEEK_IN_MONTH : return "Last"; 704 default : 705 return "SerialDate.weekInMonthToString(): invalid code."; 706 } 707 708 } 709 710 /** 711 * Returns a string representing the supplied 'relative'. 712 * <P> 713 * Need to find a better approach. 714 * 715 * @param relative a constant representing the 'relative'. 716 * 717 * @return a string representing the supplied 'relative'. 718 */ 719 public static String relativeToString(int relative) { 720 721 switch (relative) { 722 case SerialDate.PRECEDING : return "Preceding"; 723 case SerialDate.NEAREST : return "Nearest"; 724 case SerialDate.FOLLOWING : return "Following"; 725 default : return "ERROR : Relative To String"; 726 } 727 728 } 729 730 /** 731 * Factory method that returns an instance of some concrete subclass of 732 * {@link SerialDate}. 733 * 734 * @param day the day (1-31). 735 * @param month the month (1-12). 736 * @param yyyy the year (in the range 1900 to 9999). 737 * 738 * @return An instance of {@link SerialDate}. 739 */ 740 public static SerialDate createInstance(int day, int month, int yyyy) { 741 return new SpreadsheetDate(day, month, yyyy); 742 } 743 744 /** 745 * Factory method that returns an instance of some concrete subclass of 746 * {@link SerialDate}. 747 * 748 * @param serial the serial number for the day (1 January 1900 = 2). 749 * 750 * @return a instance of SerialDate. 751 */ 752 public static SerialDate createInstance(int serial) { 753 return new SpreadsheetDate(serial); 754 } 755 756 /** 757 * Factory method that returns an instance of a subclass of SerialDate. 758 * 759 * @param date A Java date object. 760 * 761 * @return a instance of SerialDate. 762 */ 763 public static SerialDate createInstance(java.util.Date date) { 764 765 GregorianCalendar calendar = new GregorianCalendar(); 766 calendar.setTime(date); 767 return new SpreadsheetDate(calendar.get(Calendar.DATE), 768 calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.YEAR)); 769 770 } 771 772 /** 773 * Returns the serial number for the date, where 1 January 1900 = 2 (this 774 * corresponds, almost, to the numbering system used in Microsoft Excel for 775 * Windows and Lotus 1-2-3). 776 * 777 * @return the serial number for the date. 778 */ 779 public abstract int toSerial(); 780 781 /** 782 * Returns a java.util.Date. Since java.util.Date has more precision than 783 * SerialDate, we need to define a convention for the 'time of day'. 784 * 785 * @return this as {@code java.util.Date}. 786 */ 787 public abstract java.util.Date toDate(); 788 789 /** 790 * Returns the description that is attached to the date. It is not 791 * required that a date have a description, but for some applications it 792 * is useful. 793 * 794 * @return The description (possibly {@code null}). 795 */ 796 public String getDescription() { 797 return this.description; 798 } 799 800 /** 801 * Sets the description for the date. 802 * 803 * @param description the description for this date ({@code null} 804 * permitted). 805 */ 806 public void setDescription(String description) { 807 this.description = description; 808 } 809 810 /** 811 * Converts the date to a string. 812 * 813 * @return a string representation of the date. 814 */ 815 @Override 816 public String toString() { 817 return getDayOfMonth() + "-" + SerialDate.monthCodeToString(getMonth()) 818 + "-" + getYYYY(); 819 } 820 821 /** 822 * Returns the year (assume a valid range of 1900 to 9999). 823 * 824 * @return the year. 825 */ 826 public abstract int getYYYY(); 827 828 /** 829 * Returns the month (January = 1, February = 2, March = 3). 830 * 831 * @return the month of the year. 832 */ 833 public abstract int getMonth(); 834 835 /** 836 * Returns the day of the month. 837 * 838 * @return the day of the month. 839 */ 840 public abstract int getDayOfMonth(); 841 842 /** 843 * Returns the day of the week. 844 * 845 * @return the day of the week. 846 */ 847 public abstract int getDayOfWeek(); 848 849 /** 850 * Returns the difference (in days) between this date and the specified 851 * 'other' date. 852 * <P> 853 * The result is positive if this date is after the 'other' date and 854 * negative if it is before the 'other' date. 855 * 856 * @param other the date being compared to. 857 * 858 * @return the difference between this and the other date. 859 */ 860 public abstract int compare(SerialDate other); 861 862 /** 863 * Returns true if this SerialDate represents the same date as the 864 * specified SerialDate. 865 * 866 * @param other the date being compared to. 867 * 868 * @return {@code true} if this SerialDate represents the same date as 869 * the specified SerialDate. 870 */ 871 public abstract boolean isOn(SerialDate other); 872 873 /** 874 * Returns true if this SerialDate represents an earlier date compared to 875 * the specified SerialDate. 876 * 877 * @param other The date being compared to. 878 * 879 * @return {@code true} if this SerialDate represents an earlier date 880 * compared to the specified SerialDate. 881 */ 882 public abstract boolean isBefore(SerialDate other); 883 884 /** 885 * Returns true if this SerialDate represents the same date as the 886 * specified SerialDate. 887 * 888 * @param other the date being compared to. 889 * 890 * @return {@code true} if this SerialDate represents the same date 891 * as the specified SerialDate. 892 */ 893 public abstract boolean isOnOrBefore(SerialDate other); 894 895 /** 896 * Returns true if this SerialDate represents the same date as the 897 * specified SerialDate. 898 * 899 * @param other the date being compared to. 900 * 901 * @return {@code true} if this SerialDate represents the same date 902 * as the specified SerialDate. 903 */ 904 public abstract boolean isAfter(SerialDate other); 905 906 /** 907 * Returns true if this SerialDate represents the same date as the 908 * specified SerialDate. 909 * 910 * @param other the date being compared to. 911 * 912 * @return {@code true} if this SerialDate represents the same date 913 * as the specified SerialDate. 914 */ 915 public abstract boolean isOnOrAfter(SerialDate other); 916 917 /** 918 * Returns {@code true} if this {@link SerialDate} is within the 919 * specified range (INCLUSIVE). The date order of d1 and d2 is not 920 * important. 921 * 922 * @param d1 a boundary date for the range. 923 * @param d2 the other boundary date for the range. 924 * 925 * @return A boolean. 926 */ 927 public abstract boolean isInRange(SerialDate d1, SerialDate d2); 928 929 /** 930 * Returns {@code true} if this {@link SerialDate} is within the 931 * specified range (caller specifies whether or not the end-points are 932 * included). The date order of d1 and d2 is not important. 933 * 934 * @param d1 a boundary date for the range. 935 * @param d2 the other boundary date for the range. 936 * @param include a code that controls whether or not the start and end 937 * dates are included in the range. 938 * 939 * @return A boolean. 940 */ 941 public abstract boolean isInRange(SerialDate d1, SerialDate d2, 942 int include); 943 944 /** 945 * Returns the latest date that falls on the specified day-of-the-week and 946 * is BEFORE this date. 947 * 948 * @param targetDOW a code for the target day-of-the-week. 949 * 950 * @return the latest date that falls on the specified day-of-the-week and 951 * is BEFORE this date. 952 */ 953 public SerialDate getPreviousDayOfWeek(int targetDOW) { 954 return getPreviousDayOfWeek(targetDOW, this); 955 } 956 957 /** 958 * Returns the earliest date that falls on the specified day-of-the-week 959 * and is AFTER this date. 960 * 961 * @param targetDOW a code for the target day-of-the-week. 962 * 963 * @return the earliest date that falls on the specified day-of-the-week 964 * and is AFTER this date. 965 */ 966 public SerialDate getFollowingDayOfWeek(int targetDOW) { 967 return getFollowingDayOfWeek(targetDOW, this); 968 } 969 970 /** 971 * Returns the nearest date that falls on the specified day-of-the-week. 972 * 973 * @param targetDOW a code for the target day-of-the-week. 974 * 975 * @return the nearest date that falls on the specified day-of-the-week. 976 */ 977 public SerialDate getNearestDayOfWeek(int targetDOW) { 978 return getNearestDayOfWeek(targetDOW, this); 979 } 980 981} 982