org.exolab.castor.xml.handlers.DateFieldHandler Maven / Gradle / Ivy
Show all versions of castor-xml Show documentation
/*
* Redistribution and use of this software and associated documentation ("Software"), with or
* without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain copyright statements and notices. Redistributions
* must also contain a copy of this document.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials provided with
* the distribution.
*
* 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
* without prior written permission of Intalio, Inc. For written permission, please contact
* [email protected].
*
* 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
* their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
* Intalio, Inc.
*
* 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
*
* THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Copyright 1999-2004 (C) Intalio, Inc. All Rights Reserved.
*
* $Id$
*/
package org.exolab.castor.xml.handlers;
import java.lang.reflect.Array;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import org.exolab.castor.mapping.FieldHandler;
import org.exolab.castor.types.DateTime;
import org.exolab.castor.xml.XMLFieldHandler;
/**
* A specialized FieldHandler for the XML Schema Date/Time related types.
*
* @author Keith Visco
* @version $Revision$ $Date: 2005-02-09 13:04:19 -0700 (Wed, 09 Feb 2005) $
*/
public class DateFieldHandler extends XMLFieldHandler {
/** The default length of the date string, used by the format method. */
private static final byte DEFAULT_DATE_LENGTH = 25;
/** The error message prefix. */
private static final String INVALID_DATE = "Invalid dateTime format: ";
/** The default parse options when none are specified. */
private static final ParseOptions DEFAULT_PARSE_OPTIONS = new ParseOptions();
/** The local timezone offset from UTC. */
private static TimeZone _timezone = TimeZone.getDefault();
/**
* A boolean to indicate that the TimeZone can be suppressed if the TimeZone is equivalent to the
* "default" timezone.
*/
private static boolean _allowTimeZoneSuppression = false;
/** if true, milliseconds should be suppressed upon formatting. */
private static boolean _suppressMillis = false;
/** The nested FieldHandler. */
private final FieldHandler _handler;
/** The current set of parse options. */
private ParseOptions _options = new ParseOptions();
/** A flag to indicate that java.sql.Date should be returned instead. */
private boolean _useSQLDate = false;
// ----------------/
// - Constructors -/
// ----------------/
/**
* Creates a new DateFieldHandler using the given FieldHandler for delegation.
*
* @param fieldHandler the fieldHandler for delegation.
*/
public DateFieldHandler(final FieldHandler fieldHandler) {
if (fieldHandler == null) {
String err = "The FieldHandler argument passed to "
+ "the constructor of DateFieldHandler must not be null.";
throw new IllegalArgumentException(err);
}
_handler = fieldHandler;
} // -- DateFieldHandler
// ------------------/
// - Public Methods -/
// ------------------/
/**
* Returns the value of the field associated with this descriptor from the given target object.
*
* @param target the object to get the value from
* @return the value of the field associated with this descriptor from the given target object.
*/
public Object getValue(final Object target) {
Object val = _handler.getValue(target);
if (val == null) {
return val;
}
Object formatted = null;
Class type = val.getClass();
if (java.util.Date.class.isAssignableFrom(type)) {
formatted = format((Date) val);
} else if (type.isArray()) {
int size = Array.getLength(val);
String[] values = new String[size];
for (int i = 0; i < size; i++) {
values[i] = format(Array.get(val, i));
}
formatted = values;
} else if (java.util.Enumeration.class.isAssignableFrom(type)) {
Enumeration enumeration = (Enumeration) val;
List values = new ArrayList<>();
while (enumeration.hasMoreElements()) {
values.add(format(enumeration.nextElement()));
}
formatted = values.toArray(new String[values.size()]);
} else {
formatted = val.toString();
}
return formatted;
} // -- getValue
/**
* Sets the value of the field associated with this descriptor.
*
* @param target the object in which to set the value
* @param value the value of the field
* @throws IllegalStateException if the value provided cannot be parsed into a legal date/time.
*/
public void setValue(final Object target, final Object value)
throws java.lang.IllegalStateException {
Date date = null;
if (value == null || value instanceof Date) {
date = (Date) value;
} else {
try {
date = parse(value.toString(), _options);
// -- java.sql.Date?
if (_useSQLDate && date != null) {
date = new java.sql.Date(date.getTime());
}
} catch (java.text.ParseException px) {
// -- invalid dateTime
throw new IllegalStateException(px.getMessage());
}
}
_handler.setValue(target, date);
} // -- setValue
/**
* Sets the value of the field to a default value.
*
* @param target The object
* @throws IllegalStateException The Java object has changed and is no longer supported by this
* handler, or the handler is not compatiable with the Java object
*/
public void resetValue(final Object target) throws java.lang.IllegalStateException {
_handler.resetValue(target);
}
/**
* Creates a new instance of the object described by this field.
*
* @param parent The object for which the field is created
* @return A new instance of the field's value
* @throws IllegalStateException This field is a simple type and cannot be instantiated
*/
public Object newInstance(final Object parent) throws IllegalStateException {
Object obj = _handler.newInstance(parent);
if (obj == null) {
obj = new Date();
}
return obj;
} // -- newInstance
/**
* Returns true if the given object is an XMLFieldHandler that is equivalent to the delegated
* handler. An equivalent XMLFieldHandler is an XMLFieldHandler that is an instances of the same
* class.
*
* @param obj The object to compare against this
* @return true if the given object is an XMLFieldHandler that is equivalent to this one.
*/
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (!(obj instanceof FieldHandler)) {
return false;
}
return (_handler.getClass().isInstance(obj) || getClass().isInstance(obj));
} // -- equals
/**
* Sets whether or not the time zone should always be displayed when marshaling xsd:dateTime
* values. If true, then the time zone will not be displayed if the time zone is the current local
* time zone.
*
* @param allowTimeZoneSuppression if true, the time zone will not be displayed if it is the
* current local time zone.
*/
public static void setAllowTimeZoneSuppression(final boolean allowTimeZoneSuppression) {
_allowTimeZoneSuppression = allowTimeZoneSuppression;
} // -- setAlwaysUseUTCTime
/**
* Sets the default TimeZone used for comparing dates when marshaling xsd:dateTime values using
* this handler. This is used when determining if the timezone can be omitted when marshaling.
*
* Default is JVM default returned by TimeZone.getDefault()
*
* @param timeZone TimeZone to use instead of JVM default
* @see #setAllowTimeZoneSuppression
*/
public static void setDefaultTimeZone(final TimeZone timeZone) {
if (timeZone == null) {
// -- reset timezone to the default
_timezone = TimeZone.getDefault();
} else {
_timezone = (TimeZone) timeZone.clone();
}
} // -- setDefaultTimeZone
/**
* Sets a flag indicating whether or not Milliseconds should be suppressed upon formatting a
* dateTime as a String.
*
* @param suppressMillis a boolean when true indicates that millis should be suppressed during
* conversion of a dateTime to a String
*/
public static void setSuppressMillis(final boolean suppressMillis) {
_suppressMillis = suppressMillis;
} // -- setAlwaysUseUTCTime
/**
* Specifies that this DateFieldHandler should use java.sql.Date when creating new Date instances.
*
* @param useSQLDate a boolean that when true indicates that java.sql.Date should be used instead
* of java.util.Date.
*/
public void setUseSQLDate(final boolean useSQLDate) {
_useSQLDate = useSQLDate;
_options._allowNoTime = _useSQLDate;
} // -- setUseSQLDate
// -------------------/
// - Private Methods -/
// -------------------/
/**
* Parses the given string, which must be in the following format: CCYY-MM-DDThh:mm:ss or
* CCYY-MM-DDThh:mm:ss.sss where "CC" represents the century, "YY" the year, "MM" the month
* and "DD" the day. The letter "T" is the date/time separator and "hh", "mm", "ss" represent
* hour, minute and second respectively.
*
* CCYY represents the Year and each 'C' and 'Y' must be a digit from 0-9. A minimum of 4 digits
* must be present.
*
* MM represents the month and each 'M' must be a digit from 0-9, but together "MM" must not
* represent a value greater than 12. "MM" must be 2 digits, use of leading zero is required for
* all values less than 10.
*
* DD represents the day of the month and each 'D' must be a digit from 0-9. DD must be 2 digits
* (use a leading zero if necessary) and must not be greater than 31.
*
* 'T' is the date/time separator and must exist!
*
* hh represents the hour using 0-23. mm represents the minute using 0-59. ss represents the
* second using 0-60. (60 for leap second) sss represents the millisecond using 0-999.
*
* @param dateTime the string to convert to a Date
* @return a new Date that represents the given string.
* @throws ParseException when the given string does not conform to the above string.
*/
protected static Date parse(final String dateTime) throws ParseException {
return parse(dateTime, DEFAULT_PARSE_OPTIONS);
} // -- parse
/**
* Parses the given string, which must be in the following format: CCYY-MM-DDThh:mm:ss or
* CCYY-MM-DDThh:mm:ss.sss where "CC" represents the century, "YY" the year, "MM" the month
* and "DD" the day. The letter "T" is the date/time separator and "hh", "mm", "ss" represent
* hour, minute and second respectively.
*
* CCYY represents the Year and each 'C' and 'Y' must be a digit from 0-9. A minimum of 4 digits
* must be present.
*
* MM represents the month and each 'M' must be a digit from 0-9, but together "MM" must not
* represent a value greater than 12. "MM" must be 2 digits, use of leading zero is required for
* all values less than 10.
*
* DD represents the day of the month and each 'D' must be a digit from 0-9. DD must be 2 digits
* (use a leading zero if necessary) and must not be greater than 31.
*
* 'T' is the date/time separator and must exist!
*
* hh represents the hour using 0-23. mm represents the minute using 0-59. ss represents the
* second using 0-60. (60 for leap second) sss represents the millisecond using 0-999.
*
* @param dateTime the string to convert to a Date
* @param options the parsing options to use
* @return a new Date that represents the given string.
* @throws ParseException when the given string does not conform to the above string.
*/
protected static Date parse(final String dateTime, final ParseOptions options)
throws ParseException {
if (dateTime == null) {
throw new ParseException(INVALID_DATE + "null", 0);
}
ParseOptions pOptions = (options != null) ? options : DEFAULT_PARSE_OPTIONS;
String trimmed = dateTime.trim();
// If no time is present and we don't require time, use org.exolab.castor.types.Date
if (pOptions._allowNoTime && trimmed.indexOf('T') == -1) {
org.exolab.castor.types.Date parsedDate = new org.exolab.castor.types.Date(trimmed);
return parsedDate.toDate();
}
DateTime parsedDateTime = new DateTime(trimmed);
return parsedDateTime.toDate();
} // -- parse
/**
* Returns the given date in a String format, using the ISO8601 format as specified in the W3C XML
* Schema 1.0 Recommendation (Part 2: Datatypes) for dateTime.
*
* @param date the Date to format
* @return the formatted string
*/
protected static String format(final Date date) {
final SimpleDateFormat formatter;
if (_suppressMillis) {
formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
} else {
formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
}
/* ensure the formatter does not use the default system timezone */
formatter.setTimeZone(_timezone);
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(date);
cal.setTimeZone(_timezone);
StringBuilder buffer = new StringBuilder(DEFAULT_DATE_LENGTH);
if (cal.get(Calendar.ERA) == GregorianCalendar.BC) {
buffer.append('-');
}
buffer.append(formatter.format(date));
formatTimeZone(cal, buffer);
return buffer.toString();
} // -- format
/**
* Format the time zone information (only) from the provided Calendar.
*
* @param cal a calendar containing a time and time zone
* @param buffer the StringBuilder to which to format the time zone
*/
private static void formatTimeZone(final Calendar cal, final StringBuilder buffer) {
int value = cal.get(Calendar.ZONE_OFFSET);
int dstOffset = cal.get(Calendar.DST_OFFSET);
if (value == 0 && dstOffset == 0) {
buffer.append('Z'); // UTC
return;
}
if (_allowTimeZoneSuppression && value == _timezone.getRawOffset()) {
return;
}
// -- adjust for Daylight Savings Time
value = value + dstOffset;
if (value > 0) {
buffer.append('+');
} else {
value = -value;
buffer.append('-');
}
// -- convert to minutes from milliseconds
int minutes = value / 60000;
// -- hours: hh
value = minutes / 60;
if (value < 10) {
buffer.append('0');
}
buffer.append(value);
buffer.append(':');
// -- remaining minutes: mm
value = minutes % 60;
if (value < 10) {
buffer.append('0');
}
buffer.append(value);
}
/**
* Formats the given object. If the object is a java.util.Date, it will be formatted by a call to
* {@link #format(Date)}, otherwise the toString() method is called on the object.
*
* @param object The object to be formatted
*
* @return the formatted object.
*/
private static String format(final Object object) {
if (object == null) {
return null;
}
if (object instanceof java.util.Date) {
return format((Date) object);
}
return object.toString();
} // -- format
/**
* A class for controlling the parse options. There is currently only one parse option.
*/
static class ParseOptions {
/** If true and the 'T' field is not present, a xsd:date is parsed, else xsd:dateTime. */
public boolean _allowNoTime = false;
}
} // -- DateFieldHandler