www.pudn.com > jworkbook-0.3.0.zip > SerialDate.java


/* ================================================= 
 * JWorkbook : data export from Java to spreadsheets 
 * ================================================= 
 * 
 * Project Info:  http://www.jfree.org/jworkbook/index.html; 
 * Project Lead:  David Gilbert (david.gilbert@object-refinery.com); 
 * 
 * (C) Copyright 2001-2003, by Object Refinery Limited. 
 * 
 * This library is free software; you can redistribute it and/or modify it under the terms 
 * of the GNU Lesser General Public License as published by the Free Software Foundation; 
 * either version 2.1 of the License, or (at your option) any later version. 
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 * See the GNU Lesser General Public License for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public License along with this 
 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
 * Boston, MA 02111-1307, USA. 
 * 
 * --------------- 
 * SerialDate.java 
 * --------------- 
 * (C) Copyright 2001-2003, by Object Refinery Limited. 
 * 
 * Original Author:  David Gilbert (for Object Refinery Limited); 
 * Contributor(s):   -; 
 * 
 * $Id: SerialDate.java,v 1.2 2003/05/29 09:46:19 mungady Exp $ 
 * 
 * Changes 
 * ------- 
 * 08-Jul-2003 : Version 1 copied from JCommon (DG); 
 * 
 */ 
 
package org.jfree.workbook.date; 
 
import java.io.Serializable; 
import java.text.DateFormatSymbols; 
import java.text.SimpleDateFormat; 
import java.util.Calendar; 
import java.util.GregorianCalendar; 
 
/** 
 *  An abstract class that defines our requirements for manipulating dates, 
 *  without tying down a particular implementation. 
 *  

* Requirement 1 : match at least what Excel does for dates; * Requirement 2 : class is immutable; *

* Why not just use java.util.Date? We will, when it makes sense. At times, * java.util.Date can be *too* precise - it represents an instant in time, * accurate to 1/1000th of a second (with the date itself depending on the * time-zone). Sometimes we just want to represent a particular day (e.g. 21 * January 2015) without concerning ourselves about the time of day, or the * time-zone, or anything else. That's what we've defined SerialDate for. *

* You can call getInstance() to get a concrete subclass of SerialDate, * without worrying about the exact implementation. * * @author David Gilbert */ public abstract class SerialDate implements Serializable, MonthConstants { /** Date format symbols. */ public static final DateFormatSymbols DATE_FORMAT_SYMBOLS = new SimpleDateFormat().getDateFormatSymbols(); /** The serial number for 1 January 1900. */ public static final int SERIAL_LOWER_BOUND = 2; /** The serial number for 31 December 9999. */ public static final int SERIAL_UPPER_BOUND = 2958465; /** The lowest year value supported by this date format. */ public static final int MINIMUM_YEAR_SUPPORTED = 1900; /** The highest year value supported by this date format. */ public static final int MAXIMUM_YEAR_SUPPORTED = 9999; /** Useful constant for Monday. Equivalent to java.util.Calendar.MONDAY. */ public static final int MONDAY = Calendar.MONDAY; /** Useful constant for Tuesday. Equivalent to java.util.Calendar.TUESDAY. */ public static final int TUESDAY = Calendar.TUESDAY; /** Useful constant for Wednesday. Equivalent to java.util.Calendar.WEDNESDAY. */ public static final int WEDNESDAY = Calendar.WEDNESDAY; /** Useful constant for Thrusday. Equivalent to java.util.Calendar.THURSDAY. */ public static final int THURSDAY = Calendar.THURSDAY; /** Useful constant for Friday. Equivalent to java.util.Calendar.FRIDAY. */ public static final int FRIDAY = Calendar.FRIDAY; /** Useful constant for Saturday. Equivalent to java.util.Calendar.SATURDAY. */ public static final int SATURDAY = Calendar.SATURDAY; /** Useful constant for Sunday. Equivalent to java.util.Calendar.SUNDAY. */ public static final int SUNDAY = Calendar.SUNDAY; /** The number of days in each month in non leap years. */ public static final int[] LAST_DAY_OF_MONTH = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; /** The number of days in a (non-leap) year up to the end of each month. */ public static final int[] AGGREGATE_DAYS_TO_END_OF_MONTH = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; /** The number of days in a year up to the end of the preceding month. */ public static final int[] AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH = {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; /** The number of days in a leap year up to the end of each month. */ public static final int[] LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_MONTH = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}; /** The number of days in a leap year up to the end of the preceding month. */ public static final int[] LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH = {0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}; /** A useful constant for referring to the first week in a month. */ public static final int FIRST_WEEK_IN_MONTH = 1; /** A useful constant for referring to the second week in a month. */ public static final int SECOND_WEEK_IN_MONTH = 2; /** A useful constant for referring to the third week in a month. */ public static final int THIRD_WEEK_IN_MONTH = 3; /** A useful constant for referring to the fourth week in a month. */ public static final int FOURTH_WEEK_IN_MONTH = 4; /** A useful constant for referring to the last week in a month. */ public static final int LAST_WEEK_IN_MONTH = 0; /** Useful range constant. */ public static final int INCLUDE_NONE = 0; /** Useful range constant. */ public static final int INCLUDE_FIRST = 1; /** Useful range constant. */ public static final int INCLUDE_SECOND = 2; /** Useful range constant. */ public static final int INCLUDE_BOTH = 3; /** Useful constant for specifying a day of the week relative to a fixed date. */ public static final int PRECEDING = -1; /** Useful constant for specifying a day of the week relative to a fixed date. */ public static final int NEAREST = 0; /** Useful constant for specifying a day of the week relative to a fixed date. */ public static final int FOLLOWING = 1; /** A description for the date. */ private String description; /** * Returns true if the supplied integer code represents a valid day-of-the-week, and * false otherwise. * * @param code the code being checked for validity. * * @return true if the supplied integer code represents a valid day-of-the-week, and false * otherwise. */ public static boolean isValidWeekdayCode(int code) { switch(code) { case SUNDAY: return true; case MONDAY: return true; case TUESDAY: return true; case WEDNESDAY: return true; case THURSDAY: return true; case FRIDAY: return true; case SATURDAY: return true; default: return false; } } /** * Converts the supplied string to a day of the week. * * @param s a string representing the day of the week. * * @return -1 if the string is not convertable, the day of the week otherwise. */ public static int stringToWeekdayCode(String s) { String[] shortWeekdayNames = DATE_FORMAT_SYMBOLS.getShortWeekdays(); String[] weekDayNames = DATE_FORMAT_SYMBOLS.getWeekdays(); int result = -1; s = s.trim(); for (int i = 0; i < weekDayNames.length; i++) { if (s.equals(shortWeekdayNames[i])) { result = i; break; } if (s.equals(weekDayNames[i])) { result = i; break; } } return result; } /** * Returns a string representing the supplied day-of-the-week. *

* Need to find a better approach. * * @param weekday the day of the week. * * @return a string representing the supplied day-of-the-week. */ public static String weekdayCodeToString(int weekday) { String[] weekdays = DATE_FORMAT_SYMBOLS.getWeekdays(); return weekdays[weekday]; } /** * Returns an array of month names. * * @return an array of month names. */ public static String[] getMonths() { return getMonths(false); } /** * Returns an array of month names. * * @param shortened a flag indicating that shortened month names should be returned. * * @return an array of month names. */ public static String[] getMonths(boolean shortened) { if (shortened) { return DATE_FORMAT_SYMBOLS.getShortMonths(); } else { return DATE_FORMAT_SYMBOLS.getMonths(); } } /** * Returns true if the supplied integer code represents a valid month. * * @param code the code being checked for validity. * * @return true if the supplied integer code represents a valid month. */ public static boolean isValidMonthCode(int code) { switch(code) { case JANUARY: return true; case FEBRUARY: return true; case MARCH: return true; case APRIL: return true; case MAY: return true; case JUNE: return true; case JULY: return true; case AUGUST: return true; case SEPTEMBER: return true; case OCTOBER: return true; case NOVEMBER: return true; case DECEMBER: return true; default: return false; } } /** * Returns the quarter for the specified month. * * @param code the month code (1-12). * * @return the quarter that the month belongs to. */ public static int monthCodeToQuarter(int code) { switch(code) { case JANUARY: return 1; case FEBRUARY: return 1; case MARCH: return 1; case APRIL: return 2; case MAY: return 2; case JUNE: return 2; case JULY: return 3; case AUGUST: return 3; case SEPTEMBER: return 3; case OCTOBER: return 4; case NOVEMBER: return 4; case DECEMBER: return 4; default: throw new IllegalArgumentException( "SerialDate.monthCodeToQuarter: invalid month code."); } } /** * Returns a string representing the supplied month. *

* The string returned is the long form of the month name taken from the default locale. * * @param month the month. * * @return a string representing the supplied month. */ public static String monthCodeToString(int month) { return monthCodeToString(month, false); } /** * Returns a string representing the supplied month. *

* The string returned is the long or short form of the month name taken from the default * locale. * * @param month the month. * @param shortened if true return the abbreviation of the month. * * @return a string representing the supplied month. */ public static String monthCodeToString(int month, boolean shortened) { // check arguments... if (!isValidMonthCode(month)) { throw new IllegalArgumentException( "SerialDate.monthCodeToString: month outside valid range."); } String[] months; if (shortened) { months = DATE_FORMAT_SYMBOLS.getShortMonths(); } else { months = DATE_FORMAT_SYMBOLS.getMonths(); } return months[month - 1]; } /** * Converts a string to a month code. *

* This method will return one of the constants JANUARY, FEBRUARY, ..., DECEMBER that * corresponds to the string. If the string is not recognised, this method returns -1. * * @param s the string to parse. * * @return -1 if the string is not parseable, the month of the year otherwise. */ public static int stringToMonthCode(String s) { String[] shortMonthNames = DATE_FORMAT_SYMBOLS.getShortMonths(); String[] monthNames = DATE_FORMAT_SYMBOLS.getMonths(); int result = -1; s = s.trim(); // first try parsing the string as an integer (1-12)... try { result = Integer.parseInt(s); } catch (NumberFormatException e) { // suppress } // now search through the month names... if ((result < 1) || (result > 12)) { for (int i = 0; i < monthNames.length; i++) { if (s.equals(shortMonthNames[i])) { result = i + 1; break; } if (s.equals(monthNames[i])) { result = i + 1; break; } } } return result; } /** * Returns true if the supplied integer code represents a valid week-in-the-month, and * false otherwise. * * @param code the code being checked for validity. * @return true if the supplied integer code represents a valid week-in-the-month. */ public static boolean isValidWeekInMonthCode(int code) { switch(code) { case FIRST_WEEK_IN_MONTH: return true; case SECOND_WEEK_IN_MONTH: return true; case THIRD_WEEK_IN_MONTH: return true; case FOURTH_WEEK_IN_MONTH: return true; case LAST_WEEK_IN_MONTH: return true; default: return false; } } /** * Determines whether or not the specified year is a leap year. * * @param yyyy the year (in the range 1900 to 9999). * * @return true if the specified year is a leap year. */ public static boolean isLeapYear(int yyyy) { if ((yyyy % 4) != 0) { return false; } else if ((yyyy % 400) == 0) { return true; } else if ((yyyy % 100) == 0) { return false; } else { return true; } } /** * Returns the number of leap years from 1900 to the specified year INCLUSIVE. *

* Note that 1900 is not a leap year. * * @param yyyy the year (in the range 1900 to 9999). * * @return the number of leap years from 1900 to the specified year. */ public static int leapYearCount(int yyyy) { int leap4 = (yyyy - 1896) / 4; int leap100 = (yyyy - 1800) / 100; int leap400 = (yyyy - 1600) / 400; return leap4 - leap100 + leap400; } /** * Returns the number of the last day of the month, taking into account leap years. * * @param month the month. * @param yyyy the year (in the range 1900 to 9999). * * @return the number of the last day of the month. */ public static int lastDayOfMonth(int month, int yyyy) { int result = LAST_DAY_OF_MONTH[month]; if (month != FEBRUARY) { return result; } else if (isLeapYear(yyyy)) { return result + 1; } else { return result; } } /** * Creates a new date by adding the specified number of days to the base date. * * @param days the number of days to add (can be negative). * @param base the base date. * * @return a new date. */ public static SerialDate addDays(int days, SerialDate base) { int serialDayNumber = base.toSerial() + days; return SerialDate.createInstance(serialDayNumber); } /** * Creates a new date by adding the specified number of months to the base date. *

* If the base date is close to the end of the month, the day on the result * may be adjusted slightly: 31 May + 1 month = 30 June. * * @param months the number of months to add (can be negative). * @param base the base date. * * @return a new date. */ public static SerialDate addMonths(int months, SerialDate base) { int yy = (12 * base.getYYYY() + base.getMonth() + months - 1) / 12; int mm = (12 * base.getYYYY() + base.getMonth() + months - 1) % 12 + 1; int dd = Math.min(base.getDayOfMonth(), SerialDate.lastDayOfMonth(mm, yy)); return SerialDate.createInstance(dd, mm, yy); } /** * Creates a new date by adding the specified number of years to the base date. * * @param years the number of years to add (can be negative). * @param base the base date. * * @return a new date. */ public static SerialDate addYears(int years, SerialDate base) { int baseY = base.getYYYY(); int baseM = base.getMonth(); int baseD = base.getDayOfMonth(); int targetY = baseY + years; int targetD = Math.min(baseD, SerialDate.lastDayOfMonth(baseM, baseY)); return SerialDate.createInstance(targetD, baseM, targetY); } /** * Returns the latest date that falls on the specified day-of-the-week and is BEFORE the * base date. * * @param targetWeekday a code for the target day-of-the-week. * @param base the base date. * * @return the latest date that falls on the specified day-of-the-week and is BEFORE the * base date. */ public static SerialDate getPreviousDayOfWeek(int targetWeekday, SerialDate base) { // check arguments... if (!SerialDate.isValidWeekdayCode(targetWeekday)) { throw new IllegalArgumentException( "SerialDate.getPreviousDayOfWeek(...): invalid day-of-the-week code."); } // find the date... int adjust; int baseDOW = base.getDayOfWeek(); if (baseDOW > targetWeekday) { adjust = Math.min(0, targetWeekday - baseDOW); } else { adjust = -7 + Math.max(0, targetWeekday - baseDOW); } return SerialDate.addDays(adjust, base); } /** * Returns the earliest date that falls on the specified day-of-the-week * and is AFTER the base date. * * @param targetWeekday a code for the target day-of-the-week. * @param base the base date. * * @return the earliest date that falls on the specified day-of-the-week and is AFTER the * base date. */ public static SerialDate getFollowingDayOfWeek(int targetWeekday, SerialDate base) { // check arguments... if (!SerialDate.isValidWeekdayCode(targetWeekday)) { throw new IllegalArgumentException( "SerialDate.getFollowingDayOfWeek(...): invalid day-of-the-week code."); } // find the date... int adjust; int baseDOW = base.getDayOfWeek(); if (baseDOW > targetWeekday) { adjust = 7 + Math.min(0, targetWeekday - baseDOW); } else { adjust = Math.max(0, targetWeekday - baseDOW); } return SerialDate.addDays(adjust, base); } /** * Returns the date that falls on the specified day-of-the-week and is * CLOSEST to the base date. * * @param targetDOW a code for the target day-of-the-week. * @param base the base date. * * @return the date that falls on the specified day-of-the-week and is CLOSEST to the * base date. */ public static SerialDate getNearestDayOfWeek(int targetDOW, SerialDate base) { // check arguments... if (!SerialDate.isValidWeekdayCode(targetDOW)) { throw new IllegalArgumentException( "SerialDate.getNearestDayOfWeek(...): invalid day-of-the-week code."); } // find the date... int baseDOW = base.getDayOfWeek(); int adjust = -Math.abs(targetDOW - baseDOW); if (adjust >= 4) { adjust = 7 - adjust; } if (adjust <= -4) { adjust = 7 + adjust; } return SerialDate.addDays(adjust, base); } /** * Rolls the date forward to the last day of the month. * * @param base the base date. * * @return a new serial date. */ public SerialDate getEndOfCurrentMonth(SerialDate base) { int last = SerialDate.lastDayOfMonth(base.getMonth(), base.getYYYY()); return SerialDate.createInstance(last, base.getMonth(), base.getYYYY()); } /** * Returns a string corresponding to the week-in-the-month code. *

* Need to find a better approach. * * @param count an integer code representing the week-in-the-month. * * @return a string corresponding to the week-in-the-month code. */ public static String weekInMonthToString(int count) { switch (count) { case SerialDate.FIRST_WEEK_IN_MONTH : return "First"; case SerialDate.SECOND_WEEK_IN_MONTH : return "Second"; case SerialDate.THIRD_WEEK_IN_MONTH : return "Third"; case SerialDate.FOURTH_WEEK_IN_MONTH : return "Fourth"; case SerialDate.LAST_WEEK_IN_MONTH : return "Last"; default : return "SerialDate.weekInMonthToString(...): invalid code."; } } /** * Returns a string representing the supplied 'relative'. *

* Need to find a better approach. * * @param relative a constant representing the 'relative'. * * @return a string representing the supplied 'relative'. */ public static String relativeToString(int relative) { switch (relative) { case SerialDate.PRECEDING : return "Preceding"; case SerialDate.NEAREST : return "Nearest"; case SerialDate.FOLLOWING : return "Following"; default : return "ERROR : Relative To String"; } } /** * Factory method that returns an instance of some concrete subclass of {@link SerialDate}. * * @param day the day (1-31). * @param month the month (1-12). * @param yyyy the year (in the range 1900 to 9999). * * @return a instance of SerialDate. */ public static SerialDate createInstance(int day, int month, int yyyy) { return new SpreadsheetDate(day, month, yyyy); } /** * Factory method that returns an instance of some concrete subclass of {@link SerialDate}. * * @param serial the serial number for the day (1 January 1900 = 2). * * @return a instance of SerialDate. */ public static SerialDate createInstance(int serial) { return new SpreadsheetDate(serial); } /** * Factory method that returns an instance of a subclass of SerialDate. * * @param date A Java date object. * * @return a instance of SerialDate. */ public static SerialDate createInstance(java.util.Date date) { GregorianCalendar calendar = new GregorianCalendar(); calendar.setTime(date); return new SpreadsheetDate(calendar.get(Calendar.DATE), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.YEAR)); } /** * Returns the serial number for the date, where 1 January 1900 = 2 (this * corresponds, almost, to the numbering system used in Microsoft Excel for * Windows and Lotus 1-2-3). * * @return the serial number for the date. */ public abstract int toSerial(); /** * Returns a java.util.Date. Since java.util.Date has more precision than * SerialDate, we need to define a convention for the 'time of day'. * * @return this as java.util.Date. */ public abstract java.util.Date toDate(); /** * Returns a description of the date. * * @return a description of the date. */ public String getDescription() { return this.description; } /** * Sets the description for the date. * * @param description the new description for the date. */ public void setDescription(String description) { this.description = description; } /** * Converts the date to a string. * * @return a string representation of the date. */ public String toString() { return getDayOfMonth() + "-" + SerialDate.monthCodeToString(getMonth()) + "-" + getYYYY(); } /** * Returns the year (assume a valid range of 1900 to 9999). * * @return the year. */ public abstract int getYYYY(); /** * Returns the month (January = 1, February = 2, March = 3). * * @return the month of the year. */ public abstract int getMonth(); /** * Returns the day of the month. * * @return the day of the month. */ public abstract int getDayOfMonth(); /** * Returns the day of the week. * * @return the day of the week. */ public abstract int getDayOfWeek(); /** * Returns the difference (in days) between this date and the specified 'other' date. *

* The result is positive if this date is after the 'other' date and * negative if it is before the 'other' date. * * @param other the date being compared to. * * @return the difference between this and the other date. */ public abstract int compare(SerialDate other); /** * Returns true if this SerialDate represents the same date as the specified SerialDate. * * @param other the date being compared to. * * @return true if this SerialDate represents the same date as the specified * SerialDate. */ public abstract boolean isOn(SerialDate other); /** * Returns true if this SerialDate represents an earlier date compared to * the specified SerialDate. * * @param other The date being compared to. * * @return true if this SerialDate represents an earlier date compared to the * specified SerialDate. */ public abstract boolean isBefore(SerialDate other); /** * Returns true if this SerialDate represents the same date as the specified SerialDate. * * @param other the date being compared to. * * @return true if this SerialDate represents the same date * as the specified SerialDate. */ public abstract boolean isOnOrBefore(SerialDate other); /** * Returns true if this SerialDate represents the same date as the specified SerialDate. * * @param other the date being compared to. * * @return true if this SerialDate represents the same date * as the specified SerialDate. */ public abstract boolean isAfter(SerialDate other); /** * Returns true if this SerialDate represents the same date as the specified SerialDate. * * @param other the date being compared to. * * @return true if this SerialDate represents the same date * as the specified SerialDate. */ public abstract boolean isOnOrAfter(SerialDate other); /** * Returns true if this SerialDate is within the specified range (INCLUSIVE). The order of * d1 d2 is not important. * * @param d1 one boundary date for the range. * @param d2 a second boundary date for the range. * * @return true if this SerialDate is within the specified range. */ public abstract boolean isInRange(SerialDate d1, SerialDate d2); /** * Returns true if this SerialDate is within the specified range (caller * specifies whether or not the end-points are included). The order of d1 * and d2 is not important. * * @param d1 one boundary date for the range. * @param d2 a second boundary date for the range. * @param include if true include d2 in the search. * * @return true if this SerialDate is within the specified range. */ public abstract boolean isInRange(SerialDate d1, SerialDate d2, int include); /** * Returns the latest date that falls on the specified day-of-the-week and * is BEFORE this date. * * @param targetDOW a code for the target day-of-the-week. * * @return the latest date that falls on the specified day-of-the-week and * is BEFORE this date. */ public SerialDate getPreviousDayOfWeek(int targetDOW) { return getPreviousDayOfWeek(targetDOW, this); } /** * Returns the earliest date that falls on the specified day-of-the-week * and is AFTER this date. * * @param targetDOW a code for the target day-of-the-week. * * @return the earliest date that falls on the specified day-of-the-week * and is AFTER this date. */ public SerialDate getFollowingDayOfWeek(int targetDOW) { return getFollowingDayOfWeek(targetDOW, this); } /** * Returns the nearest date that falls on the specified day-of-the-week. * * @param targetDOW a code for the target day-of-the-week. * * @return the nearest date that falls on the specified day-of-the-week. */ public SerialDate getNearestDayOfWeek(int targetDOW) { return getNearestDayOfWeek(targetDOW, this); } }