root/trunk/src/java/org/jcoderz/commons/types/YearMonth.java

Revision 1011, 10.2 kB (checked in by amandel, 2 years ago)

Aligned svn keyword settings.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 *
4 * Copyright 2006, The jCoderZ.org Project. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 *    * Redistributions of source code must retain the above copyright
11 *      notice, this list of conditions and the following disclaimer.
12 *    * Redistributions in binary form must reproduce the above
13 *      copyright notice, this list of conditions and the following
14 *      disclaimer in the documentation and/or other materials
15 *      provided with the distribution.
16 *    * Neither the name of the jCoderZ.org Project nor the names of
17 *      its contributors may be used to endorse or promote products
18 *      derived from this software without specific prior written
19 *      permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33package org.jcoderz.commons.types;
34
35import java.io.Serializable;
36import java.util.Calendar;
37import java.util.GregorianCalendar;
38import java.util.TimeZone;
39
40import org.jcoderz.commons.ArgumentMalformedException;
41import org.jcoderz.commons.util.Assert;
42
43
44/**
45 * Encapsulates the Year Month type.
46 * Instances of this class are immutable.
47 * Years before 1 are not fully supported and might result in wrong string
48 * representations. Not parseable time zones might be ignored.
49 * @author Andreas Mandel
50 */
51public final class YearMonth
52      implements Serializable
53{
54   /** The name of this type. */
55   public static final String TYPE_NAME = "YearMonth";
56
57   /** Minimum length for the year. */
58   public static final int MINIMUM_NUMBER_OF_YEAR_DIGITS = 4;
59
60   /** Fixed length for the month. */
61   public static final int MONTH_LENGTH = 2;
62
63   private static final int TWO_DIGIT_MONTH = 10;
64
65   /** The <code>serialVersionUID</code>. */
66   private static final long serialVersionUID = 1L;
67
68   private final int mYear;
69   /** Month counting from 1 (January) to 12 (December). */
70   private final int mMonth;
71
72   // Lazy init members
73   private transient Date mEndDate;
74   private transient Date mStartDate;
75   private transient int mHashCode;
76   private transient String mString;
77   private transient Period mPeriod;
78
79   /**
80    *
81    */
82   private YearMonth (int year, int month)
83   {
84      if (1 > month || month > Date.MONTH_PER_YEAR)
85      {
86         throw new ArgumentMalformedException(TYPE_NAME, String.valueOf(month),
87               "Month must be between 1 and 12.");
88      }
89      if (year == 0)
90      {
91         throw new ArgumentMalformedException(TYPE_NAME, String.valueOf(year),
92               "Value of Year must not be 0.");
93      }
94      mYear = year;
95      mMonth = month;
96   }
97
98   /**
99    * Parses a valid XML representation of the gYearMonth type.
100    * The format is <tt>CCYY-MM</tt>.
101    * @param str the string representing the year month.
102    * @return a YearMonth object representing the given year month.
103    */
104   public static YearMonth fromString (String str)
105   {
106      Assert.notNull(str, TYPE_NAME);
107      // find separating '-' char.
108      final int minusPos = str.indexOf('-', 1);
109      if (minusPos == -1)
110      {
111         throw new ArgumentMalformedException(TYPE_NAME, str,
112               "MonthYear type must contain a '-' character. (CCYY-MM)");
113      }
114      if ((minusPos + 1) < MINIMUM_NUMBER_OF_YEAR_DIGITS)
115      {
116         throw new ArgumentMalformedException(TYPE_NAME, str,
117               "MonthYear type have at least "
118               + MINIMUM_NUMBER_OF_YEAR_DIGITS + " digits in front of the '-' "
119               + "character. (CCYY-MM)");
120      }
121      final int year;
122      try
123      {
124         year = Integer.parseInt(str.substring(0, minusPos));
125      }
126      catch (NumberFormatException ex)
127      {
128         throw new ArgumentMalformedException(TYPE_NAME, str,
129               "Failed to parse year. (CCYY-MM)", ex);
130      }
131      if (str.length() - minusPos <= MONTH_LENGTH)
132      {
133         throw new ArgumentMalformedException(TYPE_NAME, str,
134               "Month must be 2 digits long. (CCYY-MM)");
135      }
136      final int month;
137      try
138      {
139         month = Integer.parseInt(str.substring(minusPos + 1,
140               minusPos + 1 + MONTH_LENGTH));
141      }
142      catch (NumberFormatException ex)
143      {
144         throw new ArgumentMalformedException(TYPE_NAME, str,
145               "Failed to parse month. (CCYY-MM)", ex);
146      }
147      if (str.length() > minusPos + 1 + MONTH_LENGTH)
148      {
149         // Check timezone
150         final String tz = str.substring(minusPos + 1 + MONTH_LENGTH);
151         final TimeZone timeZone = TimeZone.getTimeZone(tz);
152         if (timeZone.getRawOffset() != 0)
153         {
154            throw new ArgumentMalformedException(TYPE_NAME, str,
155                  "Only UTC is supported as time zone, not '" + tz + "'.");
156         }
157      }
158      return new YearMonth(year, month);
159   }
160
161   /**
162    * Returns the valid date that represents the beginning of the
163    * month year type.
164    * The date in time is the first second in the month year.
165    * @return a date denoting the first second in the month year.
166    */
167   public Date toStartDate ()
168   {
169      if (mStartDate == null)
170      {
171         final Calendar cal = Calendar.getInstance(Date.TIME_ZONE);
172         cal.setLenient(false);
173         cal.clear();
174         if (getYear() > 0)
175         {
176            cal.set(getYear(), getMonth() - 1, 1);
177         }
178         else
179         {
180            cal.set(-getYear(), getMonth() - 1, 1);
181            cal.set(Calendar.ERA, GregorianCalendar.BC);
182         }
183         cal.set(Calendar.DAY_OF_MONTH,
184            cal.getActualMinimum(Calendar.DAY_OF_MONTH));
185         cal.set(Calendar.HOUR_OF_DAY,
186            cal.getActualMinimum(Calendar.HOUR_OF_DAY));
187         cal.set(Calendar.MINUTE, cal.getActualMinimum(Calendar.MINUTE));
188         cal.set(Calendar.SECOND, cal.getActualMinimum(Calendar.SECOND));
189         cal.set(Calendar.MILLISECOND,
190               cal.getActualMinimum(Calendar.MILLISECOND));
191         mStartDate = new Date(cal.getTimeInMillis());
192      }
193      return mStartDate;
194   }
195
196   /**
197    * Returns the valid date that represents the end of the
198    * month year type.
199    * The date in time is the first second in the month year.
200    * @return a date denoting the first second in the month year.
201    */
202   public Date toEndDate ()
203   {
204      if (mEndDate == null)
205      {
206         final Calendar cal = Calendar.getInstance(Date.TIME_ZONE);
207         cal.setLenient(false);
208         cal.clear();
209         if (getYear() > 0)
210         {
211            cal.set(getYear(), getMonth() - 1, 1);
212         }
213         else
214         {
215            cal.set(-getYear(), getMonth() - 1, 1);
216            cal.set(Calendar.ERA, GregorianCalendar.BC);
217         }
218         cal.set(Calendar.DAY_OF_MONTH,
219            cal.getActualMaximum(Calendar.DAY_OF_MONTH));
220         cal.set(Calendar.HOUR_OF_DAY,
221            cal.getActualMaximum(Calendar.HOUR_OF_DAY));
222         cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE));
223         cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND));
224         cal.set(Calendar.MILLISECOND,
225               cal.getActualMaximum(Calendar.MILLISECOND));
226         mEndDate = new Date(cal.getTimeInMillis());
227      }
228      return mEndDate;
229   }
230
231   /**
232    * Returns this as period from start date to end date of this
233    * YearMonth.
234    * @return a period representing the time period of this year month.
235    */
236   public Period toPeriod ()
237   {
238      if (mPeriod == null)
239      {
240         mPeriod = Period.createPeriod(toStartDate(), toEndDate());
241      }
242      return mPeriod;
243   }
244
245   /**
246    * Returns the month counting from 1 (January) to 12 (December).
247    * @return The month counting from 1 (January) to 12 (December).
248    */
249   public int getMonth ()
250   {
251      return mMonth;
252   }
253   /**
254    * @return Returns the year.
255    */
256   public int getYear ()
257   {
258      return mYear;
259   }
260
261   /**
262    * Returns true if the given date is within this year/month.
263    * @param date the point in time to check.
264    * @return true if the given date is within this year/month.
265    */
266   public boolean isWithin (Date date)
267   {
268      final long current = date.getTime();
269      return toStartDate().getTime() <= current
270            && current <= toEndDate().getTime();
271   }
272
273   /** {@inheritDoc} */
274   public String toString ()
275   {
276      if (mString == null)
277      {
278         String year;
279         if (mYear >= 0)
280         {
281            year = Integer.toString(mYear);
282            if (year.length() < MINIMUM_NUMBER_OF_YEAR_DIGITS)
283            {
284               year = "0000".substring(year.length()) + year;
285            }
286         }
287         else
288         {
289            year = Integer.toString(-mYear);
290            if (year.length() < MINIMUM_NUMBER_OF_YEAR_DIGITS)
291            {
292               year = "0000".substring(year.length()) + year;
293            }
294            year = "-" + year;
295         }
296         if (mMonth < TWO_DIGIT_MONTH)
297         {
298            mString = year + "-0" + Integer.toString(mMonth) + 'Z';
299         }
300         else
301         {
302            mString = year + '-' + Integer.toString(mMonth) + 'Z';
303         }
304      }
305      return mString;
306   }
307
308   /** {@inheritDoc} */
309   public int hashCode ()
310   {
311      if (mHashCode == 0)
312      {
313         mHashCode = mYear * Date.MONTH_PER_YEAR + mMonth;
314      }
315      return mHashCode;
316   }
317
318   /** {@inheritDoc} */
319   public boolean equals (Object o)
320   {
321      final boolean result;
322      if (o instanceof YearMonth)
323      {
324         final YearMonth other =  (YearMonth) o;
325         result = other.mMonth == mMonth && other.mYear == mYear;
326      }
327      else
328      {
329         result = false;
330      }
331      return result;
332   }
333}
Note: See TracBrowser for help on using the browser.