1. /* ===========================================================
  2. * JFreeChart : a free chart library for the Java(tm) platform
  3. * ===========================================================
  4. *
  5. * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
  6. *
  7. * Project Info: http://www.jfree.org/jfreechart/index.html
  8. *
  9. * This library is free software; you can redistribute it and/or modify it under the terms
  10. * of the GNU Lesser General Public License as published by the Free Software Foundation;
  11. * either version 2.1 of the License, or (at your option) any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  14. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  15. * See the GNU Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public License along with this
  18. * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  19. * Boston, MA 02111-1307, USA.
  20. *
  21. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  22. * in the United States and other countries.]
  23. *
  24. * ----------
  25. * Month.java
  26. * ----------
  27. * (C) Copyright 2001-2005, by Object Refinery Limited.
  28. *
  29. * Original Author: David Gilbert (for Object Refinery Limited);
  30. * Contributor(s): -;
  31. *
  32. * $Id: Month.java,v 1.3 2005/01/14 17:29:49 mungady Exp $
  33. *
  34. * Changes
  35. * -------
  36. * 11-Oct-2001 : Version 1 (DG);
  37. * 14-Nov-2001 : Added method to get year as primitive (DG);
  38. * Override for toString() method (DG);
  39. * 18-Dec-2001 : Changed order of parameters in constructor (DG);
  40. * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG);
  41. * 29-Jan-2002 : Worked on the parseMonth(...) method (DG);
  42. * 14-Feb-2002 : Fixed bugs in the Month constructors (DG);
  43. * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to evaluate with reference
  44. * to a particular time zone (DG);
  45. * 19-Mar-2002 : Changed API for TimePeriod classes (DG);
  46. * 10-Sep-2002 : Added getSerialIndex() method (DG);
  47. * 04-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  48. * 10-Jan-2003 : Changed base class and method names (DG);
  49. * 13-Mar-2003 : Moved to com.jrefinery.data.time package, and implemented Serializable (DG);
  50. * 21-Oct-2003 : Added hashCode() method (DG);
  51. *
  52. */
  53. package org.jfree.data.time;
  54. import java.io.Serializable;
  55. import java.util.Calendar;
  56. import java.util.Date;
  57. import java.util.TimeZone;
  58. import org.jfree.date.MonthConstants;
  59. import org.jfree.date.SerialDate;
  60. /**
  61. * Represents a single month.
  62. * <P>
  63. * This class is immutable, which is a requirement for all
  64. * {@link RegularTimePeriod} subclasses.
  65. */
  66. public class Month extends RegularTimePeriod implements Serializable {
  67. /** The month (1-12). */
  68. private int month;
  69. /** The year in which the month falls. */
  70. private Year year;
  71. /**
  72. * Constructs a new Month, based on the current system time.
  73. */
  74. public Month() {
  75. this(new Date());
  76. }
  77. /**
  78. * Constructs a new month instance.
  79. *
  80. * @param month the month (in the range 1 to 12).
  81. * @param year the year.
  82. */
  83. public Month(int month, int year) {
  84. this(month, new Year(year));
  85. }
  86. /**
  87. * Constructs a new month instance.
  88. *
  89. * @param month the month (in the range 1 to 12).
  90. * @param year the year.
  91. */
  92. public Month(int month, Year year) {
  93. if ((month < 1) && (month > 12)) {
  94. throw new IllegalArgumentException("Month outside valid range.");
  95. }
  96. this.month = month;
  97. this.year = year;
  98. }
  99. /**
  100. * Constructs a new month instance, based on a date/time and the default time zone.
  101. *
  102. * @param time the date/time.
  103. */
  104. public Month(Date time) {
  105. this(time, RegularTimePeriod.DEFAULT_TIME_ZONE);
  106. }
  107. /**
  108. * Constructs a Month, based on a date/time and a time zone.
  109. *
  110. * @param time the date/time.
  111. * @param zone the time zone.
  112. */
  113. public Month(Date time, TimeZone zone) {
  114. Calendar calendar = Calendar.getInstance(zone);
  115. calendar.setTime(time);
  116. this.month = calendar.get(Calendar.MONTH) + 1;
  117. this.year = new Year(calendar.get(Calendar.YEAR));
  118. }
  119. /**
  120. * Returns the year in which the month falls.
  121. *
  122. * @return The year in which the month falls (as a Year object).
  123. */
  124. public Year getYear() {
  125. return this.year;
  126. }
  127. /**
  128. * Returns the year in which the month falls.
  129. *
  130. * @return The year in which the monht falls (as an int).
  131. */
  132. public int getYearValue() {
  133. return this.year.getYear();
  134. }
  135. /**
  136. * Returns the month. Note that 1=JAN, 2=FEB, ...
  137. *
  138. * @return The month.
  139. */
  140. public int getMonth() {
  141. return this.month;
  142. }
  143. /**
  144. * Returns the month preceding this one.
  145. *
  146. * @return The month preceding this one.
  147. */
  148. public RegularTimePeriod previous() {
  149. Month result;
  150. if (this.month != MonthConstants.JANUARY) {
  151. result = new Month(this.month - 1, this.year);
  152. }
  153. else {
  154. Year prevYear = (Year) this.year.previous();
  155. if (prevYear != null) {
  156. result = new Month(MonthConstants.DECEMBER, prevYear);
  157. }
  158. else {
  159. result = null;
  160. }
  161. }
  162. return result;
  163. }
  164. /**
  165. * Returns the month following this one.
  166. *
  167. * @return The month following this one.
  168. */
  169. public RegularTimePeriod next() {
  170. Month result;
  171. if (this.month != MonthConstants.DECEMBER) {
  172. result = new Month(this.month + 1, this.year);
  173. }
  174. else {
  175. Year nextYear = (Year) this.year.next();
  176. if (nextYear != null) {
  177. result = new Month(MonthConstants.JANUARY, nextYear);
  178. }
  179. else {
  180. result = null;
  181. }
  182. }
  183. return result;
  184. }
  185. /**
  186. * Returns a serial index number for the month.
  187. *
  188. * @return The serial index number.
  189. */
  190. public long getSerialIndex() {
  191. return this.year.getYear() * 12L + this.month;
  192. }
  193. /**
  194. * Returns a string representing the month (e.g. "January 2002").
  195. * <P>
  196. * To do: look at internationalisation.
  197. *
  198. * @return A string representing the month.
  199. */
  200. public String toString() {
  201. return SerialDate.monthCodeToString(this.month) + " " + this.year;
  202. }
  203. /**
  204. * Tests the equality of this Month object to an arbitrary object.
  205. * Returns true if the target is a Month instance representing the same
  206. * month as this object. In all other cases, returns false.
  207. *
  208. * @param obj the object.
  209. *
  210. * @return <code>true</code> if month and year of this and object are the same.
  211. */
  212. public boolean equals(Object obj) {
  213. if (obj != null) {
  214. if (obj instanceof Month) {
  215. Month target = (Month) obj;
  216. return ((this.month == target.getMonth()) && (this.year.equals(target.getYear())));
  217. }
  218. else {
  219. return false;
  220. }
  221. }
  222. else {
  223. return false;
  224. }
  225. }
  226. /**
  227. * Returns a hash code for this object instance.
  228. * <p>
  229. * The approach described by Joshua Bloch in "Effective Java" has been used here:
  230. * <p>
  231. * <code>http://developer.java.sun.com/developer/Books/effectivejava/Chapter3.pdf</code>
  232. *
  233. * @return A hash code.
  234. */
  235. public int hashCode() {
  236. int result = 17;
  237. result = 37 * result + this.month;
  238. result = 37 * result + this.year.hashCode();
  239. return result;
  240. }
  241. /**
  242. * Returns an integer indicating the order of this Month object relative to
  243. * the specified
  244. * object: negative == before, zero == same, positive == after.
  245. *
  246. * @param o1 the object to compare.
  247. *
  248. * @return negative == before, zero == same, positive == after.
  249. */
  250. public int compareTo(Object o1) {
  251. int result;
  252. // CASE 1 : Comparing to another Month object
  253. // --------------------------------------------
  254. if (o1 instanceof Month) {
  255. Month m = (Month) o1;
  256. result = this.year.getYear() - m.getYear().getYear();
  257. if (result == 0) {
  258. result = this.month - m.getMonth();
  259. }
  260. }
  261. // CASE 2 : Comparing to another TimePeriod object
  262. // -----------------------------------------------
  263. else if (o1 instanceof RegularTimePeriod) {
  264. // more difficult case - evaluate later...
  265. result = 0;
  266. }
  267. // CASE 3 : Comparing to a non-TimePeriod object
  268. // ---------------------------------------------
  269. else {
  270. // consider time periods to be ordered after general objects
  271. result = 1;
  272. }
  273. return result;
  274. }
  275. /**
  276. * Returns the first millisecond of the month, evaluated using the supplied
  277. * calendar (which determines the time zone).
  278. *
  279. * @param calendar the calendar.
  280. *
  281. * @return The first millisecond of the month.
  282. */
  283. public long getFirstMillisecond(Calendar calendar) {
  284. Day first = new Day(1, this.month, this.year.getYear());
  285. return first.getFirstMillisecond(calendar);
  286. }
  287. /**
  288. * Returns the last millisecond of the month, evaluated using the supplied
  289. * calendar (which determines the time zone).
  290. *
  291. * @param calendar the calendar.
  292. *
  293. * @return the last millisecond of the month.
  294. */
  295. public long getLastMillisecond(Calendar calendar) {
  296. int eom = SerialDate.lastDayOfMonth(this.month, this.year.getYear());
  297. Day last = new Day(eom, this.month, this.year.getYear());
  298. return last.getLastMillisecond(calendar);
  299. }
  300. /**
  301. * Parses the string argument as a month.
  302. * <P>
  303. * This method is required to accept the format "YYYY-MM". It will also
  304. * accept "MM-YYYY". Anything else, at the moment, is a bonus.
  305. *
  306. * @param s the string to parse.
  307. *
  308. * @return <code>null</code> if the string is not parseable, the month otherwise.
  309. */
  310. public static Month parseMonth(String s) {
  311. Month result = null;
  312. if (s != null) {
  313. // trim whitespace from either end of the string
  314. s = s.trim();
  315. int i = Month.findSeparator(s);
  316. if (i != -1) {
  317. String s1 = s.substring(0, i).trim();
  318. String s2 = s.substring(i + 1, s.length()).trim();
  319. Year year = Month.evaluateAsYear(s1);
  320. int month;
  321. if (year != null) {
  322. month = SerialDate.stringToMonthCode(s2);
  323. if (month == -1) {
  324. throw new TimePeriodFormatException(
  325. "Month.parseMonth(String): can't evaluate the month."
  326. );
  327. }
  328. result = new Month(month, year);
  329. }
  330. else {
  331. year = Month.evaluateAsYear(s2);
  332. if (year != null) {
  333. month = SerialDate.stringToMonthCode(s1);
  334. if (month == -1) {
  335. throw new TimePeriodFormatException(
  336. "Month.parseMonth(String): can't evaluate the month."
  337. );
  338. }
  339. result = new Month(month, year);
  340. }
  341. else {
  342. throw new TimePeriodFormatException(
  343. "Month.parseMonth(String): can't evaluate the year."
  344. );
  345. }
  346. }
  347. }
  348. else {
  349. throw new TimePeriodFormatException(
  350. "Month.parseMonth(String): could not find separator."
  351. );
  352. }
  353. }
  354. return result;
  355. }
  356. /**
  357. * Finds the first occurrence of ' ', '-', ',' or '.'
  358. *
  359. * @param s the string to parse.
  360. * @return <code>-1</code> if none of the characters where found, the
  361. * position of the first occurence otherwise.
  362. */
  363. private static int findSeparator(String s) {
  364. int result = s.indexOf('-');
  365. if (result == -1) {
  366. result = s.indexOf(',');
  367. }
  368. if (result == -1) {
  369. result = s.indexOf(' ');
  370. }
  371. if (result == -1) {
  372. result = s.indexOf('.');
  373. }
  374. return result;
  375. }
  376. /**
  377. * Creates a year from a string, or returns null (format exceptions suppressed).
  378. *
  379. * @param s the string to parse.
  380. *
  381. * @return <code>nukl</code> if the string is not parseable, the year otherwise.
  382. */
  383. private static Year evaluateAsYear(String s) {
  384. Year result = null;
  385. try {
  386. result = Year.parseYear(s);
  387. }
  388. catch (TimePeriodFormatException e) {
  389. // suppress
  390. }
  391. return result;
  392. }
  393. }