Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java (revision 629712)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java (working copy)
@@ -18,6 +18,7 @@
import org.apache.jackrabbit.core.value.InternalValue;
import org.apache.jackrabbit.util.Base64;
+import org.apache.jackrabbit.util.ISO8601;
import org.apache.jackrabbit.util.TransientFileFactory;
import org.apache.jackrabbit.value.ValueHelper;
import org.apache.jackrabbit.value.ValueFactoryImpl;
@@ -39,6 +40,7 @@
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
+import java.util.Calendar;
/**
* BufferedStringValue represents an appendable
@@ -260,6 +262,16 @@
reader.close();
}
}
+ } else if (targetType == PropertyType.DATE) {
+ // try to parse date in relaxed date format
+ String value = retrieve();
+ Calendar cal = ISO8601.relaxedParse(value);
+ if (cal != null) {
+ return ValueHelper.deserialize(ISO8601.format(cal), targetType, true, ValueFactoryImpl.getInstance());
+ } else {
+ // allow the format exception to be thrown later on
+ return ValueHelper.deserialize(value, targetType, true, ValueFactoryImpl.getInstance());
+ }
} else {
// all other types
return ValueHelper.deserialize(retrieve(), targetType, true, ValueFactoryImpl.getInstance());
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/StringValue.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/StringValue.java (revision 629712)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/StringValue.java (working copy)
@@ -18,6 +18,7 @@
import org.apache.jackrabbit.core.value.InternalValue;
import org.apache.jackrabbit.util.Base64;
+import org.apache.jackrabbit.util.ISO8601;
import org.apache.jackrabbit.value.ValueHelper;
import org.apache.jackrabbit.value.ValueFactoryImpl;
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
@@ -28,6 +29,7 @@
import javax.jcr.ValueFormatException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.util.Calendar;
/**
* StringValue represents an immutable serialized value.
@@ -68,6 +70,15 @@
return ival.toJCRValue(resolver);
} else if (type == PropertyType.BINARY) {
return ValueHelper.deserialize(value, type, false, ValueFactoryImpl.getInstance());
+ } else if (type == PropertyType.DATE) {
+ // try to parse date in relaxed date format
+ Calendar cal = ISO8601.relaxedParse(value);
+ if (cal != null) {
+ return ValueHelper.deserialize(ISO8601.format(cal), type, true, ValueFactoryImpl.getInstance());
+ } else {
+ // allow the format exception to be thrown later on
+ return ValueHelper.deserialize(value, type, true, ValueFactoryImpl.getInstance());
+ }
} else {
// all other types
return ValueHelper.deserialize(value, type, true, ValueFactoryImpl.getInstance());
Index: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO8601.java
===================================================================
--- jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO8601.java (revision 629712)
+++ jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO8601.java (working copy)
@@ -19,6 +19,8 @@
import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
import java.util.TimeZone;
/**
@@ -55,7 +57,7 @@
private static final DecimalFormat XXXX_FORMAT = new DecimalFormat("0000");
/**
- * Parses an ISO8601-compliant date/time string.
+ * Parses an ISO8601-compliant date/time string that strictly follows JCR specification.
*
* @param text the date/time string to be parsed
* @return a Calendar, or null if the input could
@@ -204,7 +206,198 @@
return cal;
}
+
/**
+ * Parses an ISO8601-compliant date/time string that follows xs:date and xs:dateTime types specification.
+ *
+ * @param text the date/time string to be parsed
+ * @return a Calendar, or null if the input could not be parsed
+ * @throws IllegalArgumentException if a null argument is passed
+ */
+ public static Calendar relaxedParse(String text) {
+
+ if (text == null) {
+ throw new IllegalArgumentException("argument can not be null");
+ }
+
+ StringTokenizer st = new StringTokenizer(text, "-T:.+Z", true);
+
+ Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ calendar.clear();
+ calendar.setLenient(false);
+
+ if (st.hasMoreElements()) {
+
+ try {
+
+ // year (YYYY) and sign
+ String token = st.nextToken();
+
+ int year;
+ char sign = '+';
+ if (token.matches("[+-]")) {
+ if (st.hasMoreTokens()) {
+ if (token.equals("-")) {
+ sign = '-';
+ }
+ year = Integer.parseInt(st.nextToken());
+ } else {
+ return null;
+ }
+ } else {
+ year = Integer.parseInt(token);
+ }
+
+ if (sign == '-' || year == 0) {
+ calendar.set(Calendar.YEAR, year + 1);
+ calendar.set(Calendar.ERA, GregorianCalendar.BC);
+ } else {
+ calendar.set(Calendar.YEAR, year);
+ calendar.set(Calendar.ERA, GregorianCalendar.AD);
+ }
+
+ // month (MM)
+ if (checkToken(st, "-") && (st.hasMoreTokens())) {
+ int month = Integer.parseInt(st.nextToken()) - 1;
+ calendar.set(Calendar.MONTH, month);
+ } else {
+ return null;
+ }
+
+ // day
+ if (checkToken(st, "-") && (st.hasMoreTokens())) {
+ int day = Integer.parseInt(st.nextToken());
+ calendar.set(Calendar.DAY_OF_MONTH, day);
+ } else {
+ return null;
+ }
+
+ // optional time part
+ if (st.hasMoreTokens()) {
+
+ // hour
+ if (checkToken(st, "T") && (st.hasMoreTokens())) {
+ int hour = Integer.parseInt(st.nextToken());
+ calendar.set(Calendar.HOUR_OF_DAY, hour);
+ } else {
+ return null;
+ }
+ // minutes
+ if (checkToken(st, ":") && (st.hasMoreTokens())) {
+ int minutes = Integer.parseInt(st.nextToken());
+ calendar.set(Calendar.MINUTE, minutes);
+ } else {
+ return null;
+ }
+
+ // seconds
+ if (checkToken(st, ":") && (st.hasMoreTokens())) {
+ int seconds = Integer.parseInt(st.nextToken());
+ calendar.set(Calendar.SECOND, seconds);
+ calendar.set(Calendar.MILLISECOND, 0);
+ } else {
+ return null;
+ }
+
+ // optional parts (miliseconds, time zone)
+ if (st.hasMoreTokens()) {
+
+ token = st.nextToken();
+
+ // miliseconds
+ if (token.equals(".")) {
+ if (st.hasMoreTokens()) {
+ int milis = Math.round(Float.parseFloat("0." + st.nextToken()) * 1000);
+ calendar.set(Calendar.MILLISECOND, milis);
+ if (st.hasMoreTokens()) {
+ token = st.nextToken();
+ } else {
+ token = null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ // GMT time zone
+ if ((token != null) && token.equals("Z")) {
+ calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
+ token = null;
+ }
+
+ // custom time zone
+ if ((token != null) && token.matches("[+-]")) {
+ if (st.hasMoreTokens()) {
+ String hourToken = st.nextToken();
+ if (checkToken(st, ":") && (st.hasMoreTokens())) {
+ String minuteToken = st.nextToken();
+ String tzID = "GMT" + token + hourToken + ":" + minuteToken;
+ TimeZone tz = TimeZone.getTimeZone(tzID);
+ // verify id of returned time zone (getTimeZone defaults to "GMT")
+ if (tz.getID().equals(tzID)) {
+ calendar.setTimeZone(tz);
+ } else {
+ // invalid time zone
+ return null;
+ }
+ token = null;
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ if ((token != null) || st.hasMoreTokens()) {
+ return null;
+ }
+
+ }
+
+ }
+
+ } catch (IndexOutOfBoundsException e) {
+ return null;
+ } catch (NumberFormatException e) {
+ return null;
+ } catch (NoSuchElementException ex) {
+ return null;
+ }
+
+ } else {
+ return null;
+ }
+
+ try {
+ // the following call will trigger an IllegalArgumentException if any of
+ // the set values are illegal or out of range
+ calendar.getTime();
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+
+ return calendar;
+
+ }
+
+ /**
+ * Checks if given token is present in tokenizer.
+ *
+ * @param st tokenizer object.
+ * @param token token to check.
+ * @return true if given token is available, false otherwise.
+ */
+ private static boolean checkToken(StringTokenizer st, String token) {
+ try {
+ return st.nextToken().equals(token);
+ } catch (NoSuchElementException ex) {
+ return false;
+ }
+ }
+
+
+ /**
* Formats a Calendar value into an ISO8601-compliant
* date/time string.
*