001: /*
002: * $Id: Date.java,v 1.37 2007/09/18 11:21:02 agoubard Exp $
003: *
004: * Copyright 2003-2007 Orange Nederland Breedband B.V.
005: * See the COPYRIGHT file for redistribution and use restrictions.
006: */
007: package org.xins.common.types.standard;
008:
009: import java.text.SimpleDateFormat;
010: import java.util.Calendar;
011: import org.xins.common.types.Type;
012: import org.xins.common.types.TypeValueException;
013: import org.xins.common.MandatoryArgumentChecker;
014:
015: /**
016: * Standard type <em>_date</em>. A value of this type represents a
017: * certain moment in time, with day-precision, without an indication of the
018: * time zone.
019: *
020: * <p>The textual representation of a timestamp is always 8 numeric
021: * characters, in the format:
022: *
023: * <blockquote><em>YYYYMMDD</em></blockquote>
024: *
025: * where:
026: *
027: * <ul>
028: * <li><em>YYYY</em> is the year, including the century, between 1970 and
029: * 2999, for example <code>"2005"</code>.
030: * <li><em>MM</em> is the month of the year, 1-based, for example
031: * <code>"12"</code> for December.
032: * <li><em>DD</em> is the day of the month, 1-based, for example
033: * <code>"31"</code> for the last day of December.
034: * </ul>
035: *
036: * <p>Note that all timestamps will be based on the current time zone (see
037: * {@link java.util.TimeZone#getDefault()}).
038: *
039: * <p>A number of milliseconds can be used to indicate a specific instant in
040: * time. This number of milliseconds is since the
041: * <a href="http://en.wikipedia.org/wiki/Unix_Epoch">UNIX Epoch</a>.
042: *
043: * @version $Revision: 1.37 $ $Date: 2007/09/18 11:21:02 $
044: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
045: * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
046: *
047: * @since XINS 1.0.0
048: */
049: public class Date extends Type {
050:
051: /**
052: * The only instance of this class. This field is never <code>null</code>.
053: */
054: public static final Date SINGLETON = new Date();
055:
056: /**
057: * Formatter that converts a date to a string.
058: */
059: private static final SimpleDateFormat FORMATTER = new SimpleDateFormat(
060: "yyyyMMdd");
061:
062: /**
063: * Constructs a new <code>Date</code> instance.
064: * This constructor is private, the field {@link #SINGLETON} should be
065: * used.
066: */
067: private Date() {
068: super ("_date", Value.class);
069: }
070:
071: /**
072: * Constructs a <code>Date.Value</code> with the value of the current date.
073: *
074: * @return
075: * the {@link Value} for today, never <code>null</code>.
076: */
077: public static Value today() {
078: return new Value(System.currentTimeMillis());
079: }
080:
081: /**
082: * Constructs a <code>Date.Value</code> from the specified string
083: * which is guaranteed to be non-<code>null</code>.
084: *
085: * @param string
086: * the string to convert in the ISO format YYYYMMDD, cannot be <code>null</code>.
087: *
088: * @return
089: * the {@link Value} object, never <code>null</code>.
090: *
091: * @throws IllegalArgumentException
092: * if <code>string == null</code>.
093: *
094: * @throws TypeValueException
095: * if the specified string does not represent a valid value for this
096: * type.
097: */
098: public static Value fromStringForRequired(String string)
099: throws IllegalArgumentException, TypeValueException {
100:
101: // Check preconditions
102: MandatoryArgumentChecker.check("string", string);
103:
104: return (Value) SINGLETON.fromString(string);
105: }
106:
107: /**
108: * Constructs a <code>Date.Value</code> from the specified string.
109: *
110: * @param string
111: * the string to convert in the ISO format YYYYMMDD, can be <code>null</code>.
112: *
113: * @return
114: * the {@link Value}, or <code>null</code> if
115: * <code>string == null</code>.
116: *
117: * @throws TypeValueException
118: * if the specified string does not represent a valid value for this
119: * type.
120: */
121: public static Value fromStringForOptional(String string)
122: throws TypeValueException {
123: return (Value) SINGLETON.fromString(string);
124: }
125:
126: /**
127: * Converts the specified <code>Date.Value</code> to a string.
128: *
129: * @param value
130: * the value to convert, can be <code>null</code>.
131: *
132: * @return
133: * the textual representation of the value in the ISO format YYYYMMDD,
134: * or <code>null</code> if and only if <code>value == null</code>.
135: */
136: public static String toString(Value value) {
137:
138: // Short-circuit if the argument is null
139: if (value == null) {
140: return null;
141: }
142:
143: return toString(value.getYear(), value.getMonthOfYear(), value
144: .getDayOfMonth());
145: }
146:
147: /**
148: * Converts the specified combination of a year, month and day to a string.
149: *
150: * @param year
151: * the year, must be >=0 and <= 9999.
152: *
153: * @param month
154: * the month of the year, must be >= 1 and <= 12.
155: *
156: * @param day
157: * the day of the month, must be >= 1 and <= 31.
158: *
159: * @return
160: * the textual representation of the value in the ISO format YYYYMMDD,
161: * never <code>null</code>.
162: */
163: private static String toString(int year, int month, int day) {
164:
165: // Use a buffer to create the string
166: StringBuffer buffer = new StringBuffer(8);
167:
168: // Append the year
169: if (year < 10) {
170: buffer.append("000");
171: } else if (year < 100) {
172: buffer.append("00");
173: } else if (year < 1000) {
174: buffer.append('0');
175: }
176: buffer.append(year);
177:
178: // Append the month
179: if (month < 10) {
180: buffer.append('0');
181: }
182: buffer.append(month);
183:
184: // Append the day
185: if (day < 10) {
186: buffer.append('0');
187: }
188: buffer.append(day);
189:
190: return buffer.toString();
191: }
192:
193: protected final boolean isValidValueImpl(String value) {
194:
195: // First check the length
196: if (value.length() != 8) {
197: return false;
198: }
199:
200: // Convert all 3 components of the string to integers
201: int y, m, d;
202: try {
203: y = Integer.parseInt(value.substring(0, 4));
204: m = Integer.parseInt(value.substring(4, 6));
205: d = Integer.parseInt(value.substring(6, 8));
206: } catch (NumberFormatException nfe) {
207: return false;
208: }
209:
210: // Check that the values are in the correct range
211: return (y >= 0) && (m >= 1) && (m <= 12) && (d >= 1)
212: && (d <= 31);
213: }
214:
215: protected final Object fromStringImpl(String string)
216: throws TypeValueException {
217:
218: // Convert all 3 components of the string to integers
219: int y, m, d;
220: try {
221: y = Integer.parseInt(string.substring(0, 4));
222: m = Integer.parseInt(string.substring(4, 6));
223: d = Integer.parseInt(string.substring(6, 8));
224: } catch (NumberFormatException nfe) {
225:
226: // Should never happen, since isValidValueImpl(String) will have been
227: // called
228: throw new TypeValueException(this , string);
229: }
230:
231: // Check that the values are in the correct range
232: return new Value(y, m, d);
233: }
234:
235: public final String toString(Object value)
236: throws IllegalArgumentException, ClassCastException,
237: TypeValueException {
238:
239: // Check preconditions
240: MandatoryArgumentChecker.check("value", value);
241:
242: // The argument must be a PropertyReader
243: return toString((Value) value);
244: }
245:
246: public String getDescription() {
247: return "A date. The format is YYYYMMDD.";
248: }
249:
250: /**
251: * Value for the type <em>_date</em>. Represents a specific moment in
252: * time, with day-precision.
253: *
254: * @version $Revision: 1.37 $ $Date: 2007/09/18 11:21:02 $
255: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
256: *
257: * @since XINS 1.0.0
258: */
259: public static final class Value implements Cloneable {
260:
261: /**
262: * Calendar representing the moment in time.
263: */
264: private Calendar _calendar;
265:
266: /**
267: * Constructs a new date value. The values will not be checked.
268: *
269: * @param year
270: * the year, e.g. <code>2005</code>.
271: *
272: * @param month
273: * the month of the year, e.g. <code>11</code> for November.
274: *
275: * @param day
276: * the day of the month, e.g. <code>1</code> for the first day of the
277: * month.
278: */
279: public Value(int year, int month, int day) {
280:
281: // Construct the Calendar
282: _calendar = Calendar.getInstance();
283: _calendar.set(year, month - 1, day);
284: }
285:
286: /**
287: * Constructs a new date value based on the specified
288: * <code>Calendar</code>.
289: *
290: * @param calendar
291: * the {@link java.util.Calendar} object to get the exact date from, cannot be
292: * <code>null</code>.
293: *
294: * @throws IllegalArgumentException
295: * if <code>calendar == null</code>.
296: *
297: * @since XINS 1.2.0
298: */
299: public Value(Calendar calendar) throws IllegalArgumentException {
300:
301: // Check preconditions
302: MandatoryArgumentChecker.check("calendar", calendar);
303:
304: // Initialize fields
305: _calendar = (Calendar) calendar.clone();
306: }
307:
308: /**
309: * Constructs a new date value based on the specified
310: * <code>java.util.Date</code> object.
311: *
312: * @param date
313: * the {@link java.util.Date} object to get the exact date from,
314: * cannot be <code>null</code>.
315: *
316: * @throws IllegalArgumentException
317: * if <code>date == null</code>.
318: *
319: * @since XINS 1.2.0
320: */
321: public Value(java.util.Date date)
322: throws IllegalArgumentException {
323:
324: // Check preconditions
325: MandatoryArgumentChecker.check("date", date);
326:
327: // Construct the Calendar
328: _calendar = Calendar.getInstance();
329: _calendar.setTime(date);
330: }
331:
332: /**
333: * Constructs a new date value based on the specified number of
334: * milliseconds since the UNIX Epoch.
335: *
336: * @param millis
337: * the number of milliseconds since the
338: * <a href="http://en.wikipedia.org/wiki/Unix_Epoch">UNIX Epoch</a>.
339: *
340: * @throws IllegalArgumentException
341: * if <code>millis < 0L</code>.
342: *
343: * @see System#currentTimeMillis()
344: *
345: * @since XINS 1.2.0
346: */
347: public Value(long millis) throws IllegalArgumentException {
348:
349: // Check preconditions
350: if (millis < 0L) {
351: throw new IllegalArgumentException("millis (" + millis
352: + " < 0L");
353: }
354:
355: // Convert the number of milliseconds to a Date object
356: java.util.Date date = new java.util.Date(millis);
357:
358: // Construct the Calendar
359: _calendar = Calendar.getInstance();
360: _calendar.setTime(date);
361: }
362:
363: /**
364: * Creates and returns a copy of this object.
365: *
366: * @return
367: * a copy of this object, never <code>null</code>.
368: *
369: * @see Object#clone()
370: */
371: public Object clone() {
372: return new Value(_calendar);
373: }
374:
375: /**
376: * Returns the year.
377: *
378: * @return
379: * the year, between 1970 and 2999 (inclusive).
380: */
381: public int getYear() {
382: return _calendar.get(Calendar.YEAR);
383: }
384:
385: /**
386: * Returns the month of the year.
387: *
388: * @return
389: * the month of the year, between 1 and 12 (inclusive).
390: */
391: public int getMonthOfYear() {
392: return _calendar.get(Calendar.MONTH) + 1;
393: }
394:
395: /**
396: * Returns the day of the month.
397: *
398: * @return
399: * the day of the month, between 1 and 31 (inclusive).
400: */
401: public int getDayOfMonth() {
402: return _calendar.get(Calendar.DAY_OF_MONTH);
403: }
404:
405: public boolean equals(Object obj) {
406: if (!(obj instanceof Value)) {
407: return false;
408: }
409:
410: // Compare relevant values
411: Value that = (Value) obj;
412: return (getYear() == that.getYear())
413: && (getMonthOfYear() == that.getMonthOfYear())
414: && (getDayOfMonth() == that.getDayOfMonth());
415: }
416:
417: public int hashCode() {
418: return _calendar.hashCode();
419: }
420:
421: /**
422: * Converts to a <code>java.util.Date</code> object.
423: *
424: * @return
425: * the {@link java.util.Date} corresponding to this value.
426: *
427: * @since XINS 1.2.0
428: */
429: public java.util.Date toDate() {
430: return _calendar.getTime();
431: }
432:
433: /**
434: * Returns a textual representation of this object.
435: *
436: * @return
437: * the textual representation of this timestamp, never
438: * <code>null</code>.
439: */
440: public String toString() {
441: synchronized (FORMATTER) {
442: return FORMATTER.format(_calendar.getTime());
443: }
444: }
445: }
446: }
|