Represents a date using an integer, in a similar fashion to the implementation in Microsoft Excel.
package app.test;
/*
* AFreeChart : a free chart library for Android(tm) platform.
* (based on JFreeChart and JCommon)
*
*
* (C) Copyright 2010, by Icom Systech Co., Ltd.
* (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
*
* Project Info:
* AFreeChart: http://code.google.com/p/afreechart/
* JFreeChart: http://www.jfree.org/jfreechart/index.html
* JCommon : http://www.jfree.org/jcommon/index.html
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* [Android is a trademark of Google Inc.]
*
* ---------------
* SerialDate.java
* ---------------
*
* (C) Copyright 2010, by Icom Systech Co., Ltd.
*
* Original Author: shiraki (for Icom Systech Co., Ltd);
* Contributor(s): Sato Yoshiaki ;
* Niwano Masayoshi;
*
* Changes (from 19-Nov-2010)
* --------------------------
* 19-Nov-2010 : port JCommon 1.0.16 to Android as "AFreeChart"
*
* ------------- JFreeChart ---------------------------------------------
* (C) Copyright 2001-2006, by Object Refinery Limited.
*
* Original Author: David Gilbert (for Object Refinery Limited);
* Contributor(s): -;
*
*
* Changes (from 11-Oct-2001)
* --------------------------
* 11-Oct-2001 : Re-organised the class and moved it to new package
* com.jrefinery.date (DG);
* 05-Nov-2001 : Added a getDescription() method, and eliminated NotableDate
* class (DG);
* 12-Nov-2001 : IBD requires setDescription() method, now that NotableDate
* class is gone (DG); Changed getPreviousDayOfWeek(),
* getFollowingDayOfWeek() and getNearestDayOfWeek() to correct
* bugs (DG);
* 05-Dec-2001 : Fixed bug in SpreadsheetDate class (DG);
* 29-May-2002 : Moved the month constants into a separate interface
* (MonthConstants) (DG);
* 27-Aug-2002 : Fixed bug in addMonths() method, thanks to N???levka Petr (DG);
* 03-Oct-2002 : Fixed errors reported by Checkstyle (DG);
* 13-Mar-2003 : Implemented Serializable (DG);
* 29-May-2003 : Fixed bug in addMonths method (DG);
* 04-Sep-2003 : Implemented Comparable. Updated the isInRange javadocs (DG);
* 05-Jan-2005 : Fixed bug in addYears() method (1096282) (DG);
*
*/
import java.io.Serializable;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* Represents a date using an integer, in a similar fashion to the
* implementation in Microsoft Excel. The range of dates supported is 1-Jan-1900
* to 31-Dec-9999.
* <P>
* Be aware that there is a deliberate bug in Excel that recognises the year
* 1900 as a leap year when in fact it is not a leap year. You can find more
* information on the Microsoft website in article Q181370:
* <P>
* http://support.microsoft.com/support/kb/articles/Q181/3/70.asp
* <P>
* Excel uses the convention that 1-Jan-1900 = 1. This class uses the convention
* 1-Jan-1900 = 2. The result is that the day number in this class will be
* different to the Excel figure for January and February 1900...but then Excel
* adds in an extra day (29-Feb-1900 which does not actually exist!) and from
* that point forward the day numbers will match.
*
* @author David Gilbert
*/
public class SpreadsheetDate extends SerialDate {
/** For serialization. */
private static final long serialVersionUID = -2039586705374454461L;
/**
* The day number (1-Jan-1900 = 2, 2-Jan-1900 = 3, ..., 31-Dec-9999 =
* 2958465).
*/
private final int serial;
/** The day of the month (1 to 28, 29, 30 or 31 depending on the month). */
private final int day;
/** The month of the year (1 to 12). */
private final int month;
/** The year (1900 to 9999). */
private final int year;
/**
* Creates a new date instance.
*
* @param day
* the day (in the range 1 to 28/29/30/31).
* @param month
* the month (in the range 1 to 12).
* @param year
* the year (in the range 1900 to 9999).
*/
public SpreadsheetDate(final int day, final int month, final int year) {
if ((year >= 1900) && (year <= 9999)) {
this.year = year;
} else {
throw new IllegalArgumentException(
"The 'year' argument must be in range 1900 to 9999.");
}
if ((month >= MonthConstants.JANUARY)
&& (month <= MonthConstants.DECEMBER)) {
this.month = month;
} else {
throw new IllegalArgumentException(
"The 'month' argument must be in the range 1 to 12.");
}
if ((day >= 1) && (day <= SerialDate.lastDayOfMonth(month, year))) {
this.day = day;
} else {
throw new IllegalArgumentException("Invalid 'day' argument.");
}
// the serial number needs to be synchronised with the day-month-year...
this.serial = calcSerial(day, month, year);
}
/**
* Standard constructor - creates a new date object representing the
* specified day number (which should be in the range 2 to 2958465.
*
* @param serial
* the serial number for the day (range: 2 to 2958465).
*/
public SpreadsheetDate(final int serial) {
if ((serial >= SERIAL_LOWER_BOUND) && (serial <= SERIAL_UPPER_BOUND)) {
this.serial = serial;
} else {
throw new IllegalArgumentException(
"SpreadsheetDate: Serial must be in range 2 to 2958465.");
}
// the day-month-year needs to be synchronised with the serial number...
// get the year from the serial date
final int days = this.serial - SERIAL_LOWER_BOUND;
// overestimated because we ignored leap days
final int overestimatedYYYY = 1900 + (days / 365);
final int leaps = SerialDate.leapYearCount(overestimatedYYYY);
final int nonleapdays = days - leaps;
// underestimated because we overestimated years
int underestimatedYYYY = 1900 + (nonleapdays / 365);
if (underestimatedYYYY == overestimatedYYYY) {
this.year = underestimatedYYYY;
} else {
int ss1 = calcSerial(1, 1, underestimatedYYYY);
while (ss1 <= this.serial) {
underestimatedYYYY = underestimatedYYYY + 1;
ss1 = calcSerial(1, 1, underestimatedYYYY);
}
this.year = underestimatedYYYY - 1;
}
final int ss2 = calcSerial(1, 1, this.year);
int[] daysToEndOfPrecedingMonth = AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH;
if (isLeapYear(this.year)) {
daysToEndOfPrecedingMonth = LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH;
}
// get the month from the serial date
int mm = 1;
int sss = ss2 + daysToEndOfPrecedingMonth[mm] - 1;
while (sss < this.serial) {
mm = mm + 1;
sss = ss2 + daysToEndOfPrecedingMonth[mm] - 1;
}
this.month = mm - 1;
// what's left is d(+1);
this.day = this.serial - ss2 - daysToEndOfPrecedingMonth[this.month]
+ 1;
}
/**
* 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 of this date.
*/
public int toSerial() {
return this.serial;
}
/**
* Returns a <code>java.util.Date</code> equivalent to this date.
*
* @return The date.
*/
public Date toDate() {
final Calendar calendar = Calendar.getInstance();
calendar.set(getYYYY(), getMonth() - 1, getDayOfMonth(), 0, 0, 0);
return calendar.getTime();
}
/**
* Returns the year (assume a valid range of 1900 to 9999).
*
* @return The year.
*/
public int getYYYY() {
return this.year;
}
/**
* Returns the month (January = 1, February = 2, March = 3).
*
* @return The month of the year.
*/
public int getMonth() {
return this.month;
}
/**
* Returns the day of the month.
*
* @return The day of the month.
*/
public int getDayOfMonth() {
return this.day;
}
/**
* Returns a code representing the day of the week.
* <P>
* The codes are defined in the {@link SerialDate} class as:
* <code>SUNDAY</code>, <code>MONDAY</code>, <code>TUESDAY</code>,
* <code>WEDNESDAY</code>, <code>THURSDAY</code>, <code>FRIDAY</code>, and
* <code>SATURDAY</code>.
*
* @return A code representing the day of the week.
*/
public int getDayOfWeek() {
return (this.serial + 6) % 7 + 1;
}
/**
* Tests the equality of this date with an arbitrary object.
* <P>
* This method will return true ONLY if the object is an instance of the
* {@link SerialDate} base class, and it represents the same day as this
* {@link SpreadsheetDate}.
*
* @param object
* the object to compare (<code>null</code> permitted).
*
* @return A boolean.
*/
public boolean equals(final Object object) {
if (object instanceof SerialDate) {
final SerialDate s = (SerialDate) object;
return (s.toSerial() == this.toSerial());
} else {
return false;
}
}
/**
* Returns a hash code for this object instance.
*
* @return A hash code.
*/
public int hashCode() {
return toSerial();
}
/**
* Returns the difference (in days) between this date and the specified
* 'other' date.
*
* @param other
* the date being compared to.
*
* @return The difference (in days) between this date and the specified
* 'other' date.
*/
public int compare(final SerialDate other) {
return this.serial - other.toSerial();
}
/**
* Implements the method required by the Comparable interface.
*
* @param other
* the other object (usually another SerialDate).
*
* @return A negative integer, zero, or a positive integer as this object is
* less than, equal to, or greater than the specified object.
*/
public int compareTo(final Object other) {
return compare((SerialDate) other);
}
/**
* Returns true if this SerialDate represents the same date as the specified
* SerialDate.
*
* @param other
* the date being compared to.
*
* @return <code>true</code> if this SerialDate represents the same date as
* the specified SerialDate.
*/
public boolean isOn(final SerialDate other) {
return (this.serial == other.toSerial());
}
/**
* Returns true if this SerialDate represents an earlier date compared to
* the specified SerialDate.
*
* @param other
* the date being compared to.
*
* @return <code>true</code> if this SerialDate represents an earlier date
* compared to the specified SerialDate.
*/
public boolean isBefore(final SerialDate other) {
return (this.serial < other.toSerial());
}
/**
* Returns true if this SerialDate represents the same date as the specified
* SerialDate.
*
* @param other
* the date being compared to.
*
* @return <code>true</code> if this SerialDate represents the same date as
* the specified SerialDate.
*/
public boolean isOnOrBefore(final SerialDate other) {
return (this.serial <= other.toSerial());
}
/**
* Returns true if this SerialDate represents the same date as the specified
* SerialDate.
*
* @param other
* the date being compared to.
*
* @return <code>true</code> if this SerialDate represents the same date as
* the specified SerialDate.
*/
public boolean isAfter(final SerialDate other) {
return (this.serial > other.toSerial());
}
/**
* Returns true if this SerialDate represents the same date as the specified
* SerialDate.
*
* @param other
* the date being compared to.
*
* @return <code>true</code> if this SerialDate represents the same date as
* the specified SerialDate.
*/
public boolean isOnOrAfter(final SerialDate other) {
return (this.serial >= other.toSerial());
}
/**
* Returns <code>true</code> if this {@link SerialDate} is within the
* specified range (INCLUSIVE). The date order of d1 and d2 is not
* important.
*
* @param d1
* a boundary date for the range.
* @param d2
* the other boundary date for the range.
*
* @return A boolean.
*/
public boolean isInRange(final SerialDate d1, final SerialDate d2) {
return isInRange(d1, d2, SerialDate.INCLUDE_BOTH);
}
/**
* 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
* a code that controls whether or not the start and end dates
* are included in the range.
*
* @return <code>true</code> if this SerialDate is within the specified
* range.
*/
public boolean isInRange(final SerialDate d1, final SerialDate d2,
final int include) {
final int s1 = d1.toSerial();
final int s2 = d2.toSerial();
final int start = Math.min(s1, s2);
final int end = Math.max(s1, s2);
final int s = toSerial();
if (include == SerialDate.INCLUDE_BOTH) {
return (s >= start && s <= end);
} else if (include == SerialDate.INCLUDE_FIRST) {
return (s >= start && s < end);
} else if (include == SerialDate.INCLUDE_SECOND) {
return (s > start && s <= end);
} else {
return (s > start && s < end);
}
}
/**
* Calculate the serial number from the day, month and year.
* <P>
* 1-Jan-1900 = 2.
*
* @param d
* the day.
* @param m
* the month.
* @param y
* the year.
*
* @return the serial number from the day, month and year.
*/
private int calcSerial(final int d, final int m, final int y) {
final int yy = ((y - 1900) * 365) + SerialDate.leapYearCount(y - 1);
int mm = SerialDate.AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH[m];
if (m > MonthConstants.FEBRUARY) {
if (SerialDate.isLeapYear(y)) {
mm = mm + 1;
}
}
final int dd = d;
return yy + mm + dd + 1;
}
}
/**
* An abstract class that defines our requirements for manipulating dates,
* without tying down a particular implementation.
* <P>
* Requirement 1 : match at least what Excel does for dates; Requirement 2 : the
* date represented by the class is immutable;
* <P>
* 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.
* <P>
* You can call getInstance() to get a concrete subclass of SerialDate, without
* worrying about the exact implementation.
*
* @author David Gilbert
*/
abstract class SerialDate implements Comparable, Serializable, MonthConstants {
/** For serialization. */
private static final long serialVersionUID = -293716040467423637L;
/** 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. */
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. */
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. */
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. */
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.
*/
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;
/**
* Default constructor.
*/
protected SerialDate() {
}
/**
* Returns <code>true</code> if the supplied integer code represents a valid
* day-of-the-week, and <code>false</code> otherwise.
*
* @param code
* the code being checked for validity.
*
* @return <code>true</code> if the supplied integer code represents a valid
* day-of-the-week, and <code>false</code> otherwise.
*/
public static boolean isValidWeekdayCode(final int code) {
switch (code) {
case SUNDAY:
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
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 <code>-1</code> if the string is not convertable, the day of the
* week otherwise.
*/
public static int stringToWeekdayCode(String s) {
final String[] shortWeekdayNames = DATE_FORMAT_SYMBOLS
.getShortWeekdays();
final 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.
* <P>
* 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(final int weekday) {
final 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(final 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 <code>true</code> if the supplied integer code represents a valid
* month.
*/
public static boolean isValidMonthCode(final int code) {
switch (code) {
case JANUARY:
case FEBRUARY:
case MARCH:
case APRIL:
case MAY:
case JUNE:
case JULY:
case AUGUST:
case SEPTEMBER:
case OCTOBER:
case NOVEMBER:
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(final int code) {
switch (code) {
case JANUARY:
case FEBRUARY:
case MARCH:
return 1;
case APRIL:
case MAY:
case JUNE:
return 2;
case JULY:
case AUGUST:
case SEPTEMBER:
return 3;
case OCTOBER:
case NOVEMBER:
case DECEMBER:
return 4;
default:
throw new IllegalArgumentException(
"SerialDate.monthCodeToQuarter: invalid month code.");
}
}
/**
* Returns a string representing the supplied month.
* <P>
* 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(final int month) {
return monthCodeToString(month, false);
}
/**
* Returns a string representing the supplied month.
* <P>
* 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 <code>true</code> return the abbreviation of the month.
*
* @return a string representing the supplied month.
*/
public static String monthCodeToString(final int month,
final boolean shortened) {
// check arguments...
if (!isValidMonthCode(month)) {
throw new IllegalArgumentException(
"SerialDate.monthCodeToString: month outside valid range.");
}
final 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.
* <P>
* 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 <code>-1</code> if the string is not parseable, the month of the
* year otherwise.
*/
public static int stringToMonthCode(String s) {
final String[] shortMonthNames = DATE_FORMAT_SYMBOLS.getShortMonths();
final 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 <code>true</code> if the supplied integer code represents a valid
* week-in-the-month.
*/
public static boolean isValidWeekInMonthCode(final int code) {
switch (code) {
case FIRST_WEEK_IN_MONTH:
case SECOND_WEEK_IN_MONTH:
case THIRD_WEEK_IN_MONTH:
case FOURTH_WEEK_IN_MONTH:
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 <code>true</code> if the specified year is a leap year.
*/
public static boolean isLeapYear(final 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.
* <P>
* 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(final int yyyy) {
final int leap4 = (yyyy - 1896) / 4;
final int leap100 = (yyyy - 1800) / 100;
final 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(final int month, final int yyyy) {
final 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(final int days, final SerialDate base) {
final int serialDayNumber = base.toSerial() + days;
return SerialDate.createInstance(serialDayNumber);
}
/**
* Creates a new date by adding the specified number of months to the base
* date.
* <P>
* 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(final int months, final SerialDate base) {
final int yy = (12 * base.getYYYY() + base.getMonth() + months - 1) / 12;
final int mm = (12 * base.getYYYY() + base.getMonth() + months - 1) % 12 + 1;
final 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(final int years, final SerialDate base) {
final int baseY = base.getYYYY();
final int baseM = base.getMonth();
final int baseD = base.getDayOfMonth();
final int targetY = baseY + years;
final int targetD = Math.min(baseD,
SerialDate.lastDayOfMonth(baseM, targetY));
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(final int targetWeekday,
final SerialDate base) {
// check arguments...
if (!SerialDate.isValidWeekdayCode(targetWeekday)) {
throw new IllegalArgumentException("Invalid day-of-the-week code.");
}
// find the date...
final int adjust;
final 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(final int targetWeekday,
final SerialDate base) {
// check arguments...
if (!SerialDate.isValidWeekdayCode(targetWeekday)) {
throw new IllegalArgumentException("Invalid day-of-the-week code.");
}
// find the date...
final int adjust;
final 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(final int targetDOW,
final SerialDate base) {
// check arguments...
if (!SerialDate.isValidWeekdayCode(targetDOW)) {
throw new IllegalArgumentException("Invalid day-of-the-week code.");
}
// find the date...
final 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(final SerialDate base) {
final 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.
* <P>
* 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(final 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'.
* <P>
* Need to find a better approach.
*
* @param relative
* a constant representing the 'relative'.
*
* @return a string representing the supplied 'relative'.
*/
public static String relativeToString(final 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 An instance of {@link SerialDate}.
*/
public static SerialDate createInstance(final int day, final int month,
final 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(final 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(final java.util.Date date) {
final 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 <code>java.util.Date</code>.
*/
public abstract java.util.Date toDate();
/**
* Returns the description that is attached to the date. It is not required
* that a date have a description, but for some applications it is useful.
*
* @return The description (possibly <code>null</code>).
*/
public String getDescription() {
return this.description;
}
/**
* Sets the description for the date.
*
* @param description
* the description for this date (<code>null</code> permitted).
*/
public void setDescription(final 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.
* <P>
* 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 <code>true</code> 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 <code>true</code> 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 <code>true<code> 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 <code>true</code> 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 <code>true</code> if this SerialDate represents the same date as
* the specified SerialDate.
*/
public abstract boolean isOnOrAfter(SerialDate other);
/**
* Returns <code>true</code> if this {@link SerialDate} is within the
* specified range (INCLUSIVE). The date order of d1 and d2 is not
* important.
*
* @param d1
* a boundary date for the range.
* @param d2
* the other boundary date for the range.
*
* @return A boolean.
*/
public abstract boolean isInRange(SerialDate d1, SerialDate d2);
/**
* Returns <code>true</code> if this {@link SerialDate} is within the
* specified range (caller specifies whether or not the end-points are
* included). The date order of d1 and d2 is not important.
*
* @param d1
* a boundary date for the range.
* @param d2
* the other boundary date for the range.
* @param include
* a code that controls whether or not the start and end dates
* are included in the range.
*
* @return A boolean.
*/
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(final 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(final 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(final int targetDOW) {
return getNearestDayOfWeek(targetDOW, this);
}
}
/**
* Useful constants for months. Note that these are NOT equivalent to the
* constants defined by java.util.Calendar (where JANUARY=0 and DECEMBER=11).
* <P>
* Used by the SerialDate and RegularTimePeriod classes.
*
* @author David Gilbert
*/
interface MonthConstants {
/** Constant for January. */
public static final int JANUARY = 1;
/** Constant for February. */
public static final int FEBRUARY = 2;
/** Constant for March. */
public static final int MARCH = 3;
/** Constant for April. */
public static final int APRIL = 4;
/** Constant for May. */
public static final int MAY = 5;
/** Constant for June. */
public static final int JUNE = 6;
/** Constant for July. */
public static final int JULY = 7;
/** Constant for August. */
public static final int AUGUST = 8;
/** Constant for September. */
public static final int SEPTEMBER = 9;
/** Constant for October. */
public static final int OCTOBER = 10;
/** Constant for November. */
public static final int NOVEMBER = 11;
/** Constant for December. */
public static final int DECEMBER = 12;
}
Related examples in the same category
1. | Write Date to AudioTrack | | |
2. | A formatter that formats dates to show the elapsed time relative to some base date. | | |
3. | Iso Date | | |
4. | Get the string of the date using format "yyyy-MM-dd HH:mm:ss" | | |
5. | Parse a string that contains date, return a date object using format "yyyy-MM-dd HH:mm:ss" | | |
6. | Create Date from timestamp | | |
7. | Convert date in RFC2822 and UTC strings, and to build Date from string of dates in RFC2822 and UTC format | | |
8. | Gets a "HH:mm:ss.SSS EEE dd MMM yyyy" representation of a Date | | |
9. | Gets a "yyyy MMM dd, HH:mm:ss.SSS" representation of a Date | | |
10. | Gets a "EEE, dd MMM yyyy hh:mm:ss 'GMT'" representation of a Date | | |
11. | Parses a String for a "EEE, dd MMM yyyy hh:mm:ss 'GMT'" formatted Date | | |
12. | Formatting and parsing the various date formats we expect to encounter. | | |
13. | ISO8601, ISO8601, RFC822 Date format | | |
14. | Easter Date | | |
15. | Parse Date | | |
16. | compare Two dates with Day value | | |
17. | compare Two dates with Second value | | |
18. | Get start of a date | | |
19. | Get start date of a Month | | |
20. | Get start date of a year | | |
21. | Create date from year, month and day value | | |
22. | Create Date from year, month, day, hour, minute, second | | |
23. | Get year value from Date | | |
24. | Get Month value from Date | | |
25. | Get Day value from Date | | |
26. | Get now in Date and Millis-seconds | | |
27. | Add day, Month and year to a Date | | |
28. | Parse date in format of yyyyMMddHHmmss or yyyyMMdd | | |
29. | String to Date | | |
30. | Convert date value in long to YYYYMMDDHHMMSS format | | |
31. | Convert Date to Number | | |
32. | Get short and long date String | | |
33. | Convert Java Date To Xml Time | | |
34. | Convert dates to Julian dates. | | |
35. | Parse an RFC 822 date string. | | |
36. | Format a date into a format suitable for SQLite | | |
37. | Dot String to Date | | |
38. | Parses an RFC822 formatted date string. | | |
39. | Formats a Date according to RFC822. | | |
40. | DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(date); | | |
41. | yyyy-MM-dd HH:mm:ss date format | | |
42. | Utility class for formatting and parsing the various date formats we expect to encounter. | | |
43. | Get a string representation of a date as it relates to the current time. | | |
44. | get Date String from milliseconds | | |
45. | Generate a ISO 8601 date | | |
46. | Generate a Calendar from ISO 8601 date | | |
47. | parse Date for list of possible formats | | |
48. | date To String | | |
49. | Get date string for Locale tr | | |
50. | RFC3339 Date | | |
51. | Date formats | | |
52. | build Date Format day-of-week Short | | |
53. | Get end of each day | | |
54. | Get the end of each Month | | |
55. | Get end of a year | | |
56. | calculate Month Distance | | |
57. | calculate Day Distance | | |
58. | The month, and just the month, is zero-based. Add 1 for display. | | |
59. | Get hour different | | |
60. | format Millis Into Human Readable | | |