SpreadsheetDate.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. /* ========================================================================
  2. * JCommon : a free general purpose class library for the Java(tm) platform
  3. * ========================================================================
  4. *
  5. * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
  6. *
  7. * Project Info: http://www.jfree.org/jcommon/index.html
  8. *
  9. * This library is free software; you can redistribute it and/or modify it
  10. * under the terms of the GNU Lesser General Public License as published by
  11. * the Free Software Foundation; either version 2.1 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This library is distributed in the hope that it will be useful, but
  15. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  16. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
  17. * License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public
  20. * License along with this library; if not, write to the Free Software
  21. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
  22. * USA.
  23. *
  24. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  25. * in the United States and other countries.]
  26. *
  27. * --------------------
  28. * SpreadsheetDate.java
  29. * --------------------
  30. * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
  31. *
  32. * Original Author: David Gilbert (for Object Refinery Limited);
  33. * Contributor(s): -;
  34. *
  35. * $Id: SpreadsheetDate.java,v 1.10 2006/08/29 13:59:30 mungady Exp $
  36. *
  37. * Changes
  38. * -------
  39. * 11-Oct-2001 : Version 1 (DG);
  40. * 05-Nov-2001 : Added getDescription() and setDescription() methods (DG);
  41. * 12-Nov-2001 : Changed name from ExcelDate.java to SpreadsheetDate.java (DG);
  42. * Fixed a bug in calculating day, month and year from serial
  43. * number (DG);
  44. * 24-Jan-2002 : Fixed a bug in calculating the serial number from the day,
  45. * month and year. Thanks to Trevor Hills for the report (DG);
  46. * 29-May-2002 : Added equals(Object) method (SourceForge ID 558850) (DG);
  47. * 03-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  48. * 13-Mar-2003 : Implemented Serializable (DG);
  49. * 04-Sep-2003 : Completed isInRange() methods (DG);
  50. * 05-Sep-2003 : Implemented Comparable (DG);
  51. * 21-Oct-2003 : Added hashCode() method (DG);
  52. * 29-Aug-2006 : Removed redundant description attribute (DG);
  53. *
  54. */
  55. package org.jfree.date;
  56. import java.util.Calendar;
  57. import java.util.Date;
  58. /**
  59. * Represents a date using an integer, in a similar fashion to the
  60. * implementation in Microsoft Excel. The range of dates supported is
  61. * 1-Jan-1900 to 31-Dec-9999.
  62. * <P>
  63. * Be aware that there is a deliberate bug in Excel that recognises the year
  64. * 1900 as a leap year when in fact it is not a leap year. You can find more
  65. * information on the Microsoft website in article Q181370:
  66. * <P>
  67. * http://support.microsoft.com/support/kb/articles/Q181/3/70.asp
  68. * <P>
  69. * Excel uses the convention that 1-Jan-1900 = 1. This class uses the
  70. * convention 1-Jan-1900 = 2.
  71. * The result is that the day number in this class will be different to the
  72. * Excel figure for January and February 1900...but then Excel adds in an extra
  73. * day (29-Feb-1900 which does not actually exist!) and from that point forward
  74. * the day numbers will match.
  75. *
  76. * @author David Gilbert
  77. */
  78. public class SpreadsheetDate extends SerialDate {
  79. /** For serialization. */
  80. private static final long serialVersionUID = -2039586705374454461L;
  81. /**
  82. * The day number (1-Jan-1900 = 2, 2-Jan-1900 = 3, ..., 31-Dec-9999 =
  83. * 2958465).
  84. */
  85. private final int serial;
  86. /** The day of the month (1 to 28, 29, 30 or 31 depending on the month). */
  87. private final int day;
  88. /** The month of the year (1 to 12). */
  89. private final int month;
  90. /** The year (1900 to 9999). */
  91. private final int year;
  92. /**
  93. * Creates a new date instance.
  94. *
  95. * @param day the day (in the range 1 to 28/29/30/31).
  96. * @param month the month (in the range 1 to 12).
  97. * @param year the year (in the range 1900 to 9999).
  98. */
  99. public SpreadsheetDate(final int day, final int month, final int year) {
  100. if ((year >= 1900) && (year <= 9999)) {
  101. this.year = year;
  102. }
  103. else {
  104. throw new IllegalArgumentException(
  105. "The 'year' argument must be in range 1900 to 9999."
  106. );
  107. }
  108. if ((month >= MonthConstants.JANUARY)
  109. && (month <= MonthConstants.DECEMBER)) {
  110. this.month = month;
  111. }
  112. else {
  113. throw new IllegalArgumentException(
  114. "The 'month' argument must be in the range 1 to 12."
  115. );
  116. }
  117. if ((day >= 1) && (day <= SerialDate.lastDayOfMonth(month, year))) {
  118. this.day = day;
  119. }
  120. else {
  121. throw new IllegalArgumentException("Invalid 'day' argument.");
  122. }
  123. // the serial number needs to be synchronised with the day-month-year...
  124. this.serial = calcSerial(day, month, year);
  125. }
  126. /**
  127. * Standard constructor - creates a new date object representing the
  128. * specified day number (which should be in the range 2 to 2958465.
  129. *
  130. * @param serial the serial number for the day (range: 2 to 2958465).
  131. */
  132. public SpreadsheetDate(final int serial) {
  133. if ((serial >= SERIAL_LOWER_BOUND) && (serial <= SERIAL_UPPER_BOUND)) {
  134. this.serial = serial;
  135. }
  136. else {
  137. throw new IllegalArgumentException(
  138. "SpreadsheetDate: Serial must be in range 2 to 2958465.");
  139. }
  140. // the day-month-year needs to be synchronised with the serial number...
  141. // get the year from the serial date
  142. final int days = this.serial - SERIAL_LOWER_BOUND;
  143. // overestimated because we ignored leap days
  144. final int overestimatedYYYY = 1900 + (days / 365);
  145. final int leaps = SerialDate.leapYearCount(overestimatedYYYY);
  146. final int nonleapdays = days - leaps;
  147. // underestimated because we overestimated years
  148. int underestimatedYYYY = 1900 + (nonleapdays / 365);
  149. if (underestimatedYYYY == overestimatedYYYY) {
  150. this.year = underestimatedYYYY;
  151. }
  152. else {
  153. int ss1 = calcSerial(1, 1, underestimatedYYYY);
  154. while (ss1 <= this.serial) {
  155. underestimatedYYYY = underestimatedYYYY + 1;
  156. ss1 = calcSerial(1, 1, underestimatedYYYY);
  157. }
  158. this.year = underestimatedYYYY - 1;
  159. }
  160. final int ss2 = calcSerial(1, 1, this.year);
  161. int[] daysToEndOfPrecedingMonth
  162. = AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH;
  163. if (isLeapYear(this.year)) {
  164. daysToEndOfPrecedingMonth
  165. = LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH;
  166. }
  167. // get the month from the serial date
  168. int mm = 1;
  169. int sss = ss2 + daysToEndOfPrecedingMonth[mm] - 1;
  170. while (sss < this.serial) {
  171. mm = mm + 1;
  172. sss = ss2 + daysToEndOfPrecedingMonth[mm] - 1;
  173. }
  174. this.month = mm - 1;
  175. // what's left is d(+1);
  176. this.day = this.serial - ss2
  177. - daysToEndOfPrecedingMonth[this.month] + 1;
  178. }
  179. /**
  180. * Returns the serial number for the date, where 1 January 1900 = 2
  181. * (this corresponds, almost, to the numbering system used in Microsoft
  182. * Excel for Windows and Lotus 1-2-3).
  183. *
  184. * @return The serial number of this date.
  185. */
  186. public int toSerial() {
  187. return this.serial;
  188. }
  189. /**
  190. * Returns a <code>java.util.Date</code> equivalent to this date.
  191. *
  192. * @return The date.
  193. */
  194. public Date toDate() {
  195. final Calendar calendar = Calendar.getInstance();
  196. calendar.set(getYYYY(), getMonth() - 1, getDayOfMonth(), 0, 0, 0);
  197. return calendar.getTime();
  198. }
  199. /**
  200. * Returns the year (assume a valid range of 1900 to 9999).
  201. *
  202. * @return The year.
  203. */
  204. public int getYYYY() {
  205. return this.year;
  206. }
  207. /**
  208. * Returns the month (January = 1, February = 2, March = 3).
  209. *
  210. * @return The month of the year.
  211. */
  212. public int getMonth() {
  213. return this.month;
  214. }
  215. /**
  216. * Returns the day of the month.
  217. *
  218. * @return The day of the month.
  219. */
  220. public int getDayOfMonth() {
  221. return this.day;
  222. }
  223. /**
  224. * Returns a code representing the day of the week.
  225. * <P>
  226. * The codes are defined in the {@link SerialDate} class as:
  227. * <code>SUNDAY</code>, <code>MONDAY</code>, <code>TUESDAY</code>,
  228. * <code>WEDNESDAY</code>, <code>THURSDAY</code>, <code>FRIDAY</code>, and
  229. * <code>SATURDAY</code>.
  230. *
  231. * @return A code representing the day of the week.
  232. */
  233. public int getDayOfWeek() {
  234. return (this.serial + 6) % 7 + 1;
  235. }
  236. /**
  237. * Tests the equality of this date with an arbitrary object.
  238. * <P>
  239. * This method will return true ONLY if the object is an instance of the
  240. * {@link SerialDate} base class, and it represents the same day as this
  241. * {@link SpreadsheetDate}.
  242. *
  243. * @param object the object to compare (<code>null</code> permitted).
  244. *
  245. * @return A boolean.
  246. */
  247. public boolean equals(final Object object) {
  248. if (object instanceof SerialDate) {
  249. final SerialDate s = (SerialDate) object;
  250. return (s.toSerial() == this.toSerial());
  251. }
  252. else {
  253. return false;
  254. }
  255. }
  256. /**
  257. * Returns a hash code for this object instance.
  258. *
  259. * @return A hash code.
  260. */
  261. public int hashCode() {
  262. return toSerial();
  263. }
  264. /**
  265. * Returns the difference (in days) between this date and the specified
  266. * 'other' date.
  267. *
  268. * @param other the date being compared to.
  269. *
  270. * @return The difference (in days) between this date and the specified
  271. * 'other' date.
  272. */
  273. public int compare(final SerialDate other) {
  274. return this.serial - other.toSerial();
  275. }
  276. /**
  277. * Implements the method required by the Comparable interface.
  278. *
  279. * @param other the other object (usually another SerialDate).
  280. *
  281. * @return A negative integer, zero, or a positive integer as this object
  282. * is less than, equal to, or greater than the specified object.
  283. */
  284. public int compareTo(final Object other) {
  285. return compare((SerialDate) other);
  286. }
  287. /**
  288. * Returns true if this SerialDate represents the same date as the
  289. * specified SerialDate.
  290. *
  291. * @param other the date being compared to.
  292. *
  293. * @return <code>true</code> if this SerialDate represents the same date as
  294. * the specified SerialDate.
  295. */
  296. public boolean isOn(final SerialDate other) {
  297. return (this.serial == other.toSerial());
  298. }
  299. /**
  300. * Returns true if this SerialDate represents an earlier date compared to
  301. * the specified SerialDate.
  302. *
  303. * @param other the date being compared to.
  304. *
  305. * @return <code>true</code> if this SerialDate represents an earlier date
  306. * compared to the specified SerialDate.
  307. */
  308. public boolean isBefore(final SerialDate other) {
  309. return (this.serial < other.toSerial());
  310. }
  311. /**
  312. * Returns true if this SerialDate represents the same date as the
  313. * specified SerialDate.
  314. *
  315. * @param other the date being compared to.
  316. *
  317. * @return <code>true</code> if this SerialDate represents the same date
  318. * as the specified SerialDate.
  319. */
  320. public boolean isOnOrBefore(final SerialDate other) {
  321. return (this.serial <= other.toSerial());
  322. }
  323. /**
  324. * Returns true if this SerialDate represents the same date as the
  325. * specified SerialDate.
  326. *
  327. * @param other the date being compared to.
  328. *
  329. * @return <code>true</code> if this SerialDate represents the same date
  330. * as the specified SerialDate.
  331. */
  332. public boolean isAfter(final SerialDate other) {
  333. return (this.serial > other.toSerial());
  334. }
  335. /**
  336. * Returns true if this SerialDate represents the same date as the
  337. * specified SerialDate.
  338. *
  339. * @param other the date being compared to.
  340. *
  341. * @return <code>true</code> if this SerialDate represents the same date as
  342. * the specified SerialDate.
  343. */
  344. public boolean isOnOrAfter(final SerialDate other) {
  345. return (this.serial >= other.toSerial());
  346. }
  347. /**
  348. * Returns <code>true</code> if this {@link SerialDate} is within the
  349. * specified range (INCLUSIVE). The date order of d1 and d2 is not
  350. * important.
  351. *
  352. * @param d1 a boundary date for the range.
  353. * @param d2 the other boundary date for the range.
  354. *
  355. * @return A boolean.
  356. */
  357. public boolean isInRange(final SerialDate d1, final SerialDate d2) {
  358. return isInRange(d1, d2, SerialDate.INCLUDE_BOTH);
  359. }
  360. /**
  361. * Returns true if this SerialDate is within the specified range (caller
  362. * specifies whether or not the end-points are included). The order of d1
  363. * and d2 is not important.
  364. *
  365. * @param d1 one boundary date for the range.
  366. * @param d2 a second boundary date for the range.
  367. * @param include a code that controls whether or not the start and end
  368. * dates are included in the range.
  369. *
  370. * @return <code>true</code> if this SerialDate is within the specified
  371. * range.
  372. */
  373. public boolean isInRange(final SerialDate d1, final SerialDate d2,
  374. final int include) {
  375. final int s1 = d1.toSerial();
  376. final int s2 = d2.toSerial();
  377. final int start = Math.min(s1, s2);
  378. final int end = Math.max(s1, s2);
  379. final int s = toSerial();
  380. if (include == SerialDate.INCLUDE_BOTH) {
  381. return (s >= start && s <= end);
  382. }
  383. else if (include == SerialDate.INCLUDE_FIRST) {
  384. return (s >= start && s < end);
  385. }
  386. else if (include == SerialDate.INCLUDE_SECOND) {
  387. return (s > start && s <= end);
  388. }
  389. else {
  390. return (s > start && s < end);
  391. }
  392. }
  393. /**
  394. * Calculate the serial number from the day, month and year.
  395. * <P>
  396. * 1-Jan-1900 = 2.
  397. *
  398. * @param d the day.
  399. * @param m the month.
  400. * @param y the year.
  401. *
  402. * @return the serial number from the day, month and year.
  403. */
  404. private int calcSerial(final int d, final int m, final int y) {
  405. final int yy = ((y - 1900) * 365) + SerialDate.leapYearCount(y - 1);
  406. int mm = SerialDate.AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH[m];
  407. if (m > MonthConstants.FEBRUARY) {
  408. if (SerialDate.isLeapYear(y)) {
  409. mm = mm + 1;
  410. }
  411. }
  412. final int dd = d;
  413. return yy + mm + dd + 1;
  414. }
  415. }