Repository: olingo-odata2 Updated Branches: refs/heads/master 55655f3d9 -> d18f9f0bd
[OLINGO-881] support for java.sql.Timestamp in simple types Signed-off-by: Christian Amend <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/olingo-odata2/repo Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata2/commit/d18f9f0b Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata2/tree/d18f9f0b Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata2/diff/d18f9f0b Branch: refs/heads/master Commit: d18f9f0bdf915031e31d796250aea30b7fbf9a9b Parents: 55655f3 Author: Klaus Straubinger <[email protected]> Authored: Fri Feb 12 09:53:48 2016 +0100 Committer: Christian Amend <[email protected]> Committed: Fri Feb 12 10:39:44 2016 +0100 ---------------------------------------------------------------------- .../olingo/odata2/api/edm/EdmSimpleType.java | 52 +++--- .../api/processor/ODataSingleProcessor.java | 10 +- .../olingo/odata2/core/edm/EdmDateTime.java | 121 ++++++++------ .../odata2/core/edm/EdmDateTimeOffset.java | 39 +++-- .../apache/olingo/odata2/core/edm/EdmTime.java | 75 +++++---- .../odata2/core/edm/EdmSimpleTypeTest.java | 162 ++++++++++--------- 6 files changed, 257 insertions(+), 202 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/d18f9f0b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/edm/EdmSimpleType.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/edm/EdmSimpleType.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/edm/EdmSimpleType.java index 99da498..2cbe4b9 100644 --- a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/edm/EdmSimpleType.java +++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/edm/EdmSimpleType.java @@ -30,8 +30,10 @@ package org.apache.olingo.odata2.api.edm; * <tr><td>Binary</td><td>byte[], {@link Byte}[]</td></tr> * <tr><td>Boolean</td><td>{@link Boolean}</td></tr> * <tr><td>Byte</td><td>{@link Short}, {@link Byte}, {@link Integer}, {@link Long}</td></tr> - * <tr><td>DateTime</td><td>{@link java.util.Calendar}, {@link java.util.Date}, {@link Long}</td></tr> - * <tr><td>DateTimeOffset</td><td>{@link java.util.Calendar}, {@link java.util.Date}, {@link Long}</td></tr> + * <tr><td>DateTime</td><td>{@link java.util.Calendar}, {@link java.util.Date}, {@link java.sql.Timestamp}, + * {@link Long}</td></tr> + * <tr><td>DateTimeOffset</td><td>{@link java.util.Calendar}, {@link java.util.Date}, {@link java.sql.Timestamp}, + * {@link Long}</td></tr> * <tr><td>Decimal</td><td>{@link java.math.BigDecimal}, {@link java.math.BigInteger}, {@link Double}, {@link Float}, * {@link Byte}, {@link Short}, {@link Integer}, {@link Long}</td></tr> * <tr><td>Double</td><td>{@link Double}, {@link Float}, {@link java.math.BigDecimal}, {@link Byte}, {@link Short}, @@ -45,7 +47,8 @@ package org.apache.olingo.odata2.api.edm; * <tr><td>Single</td><td>{@link Float}, {@link Double}, {@link java.math.BigDecimal}, {@link Byte}, {@link Short}, * {@link Integer}, {@link Long}</td></tr> * <tr><td>String</td><td>{@link String}</td></tr> - * <tr><td>Time</td><td>{@link java.util.Calendar}, {@link java.util.Date}, {@link Long}</td></tr> + * <tr><td>Time</td><td>{@link java.util.Calendar}, {@link java.util.Date}, {@link java.sql.Timestamp}, + * {@link java.sql.Time}, {@link Long}</td></tr> * </tbody> * </table></p> * <p>The first Java type is the default type for the respective EDM simple type.</p> @@ -57,38 +60,25 @@ package org.apache.olingo.odata2.api.edm; * The EDM simple types <code>DateTime</code>, <code>DateTimeOffset</code>, and * <code>Time</code> can have a <code>Precision</code> facet. * <code>Decimal</code> can have the facets <code>Precision</code> and <code>Scale</code>.</p> - * <p> - * <table frame="box" rules="all"> - * <thead> - * <tr><th>EDM simple type</th><th>Parsing details</th></tr> - * </thead> - * <tbody> - * <tr><td><b>DateTimeOffset</b></td> - * <td> - * When an time string is parsed to an according <code>EdmDateTimeOffset</code> object it is assumed that this time - * string represents the local time with a timezone set. - * <br/> - * As an example, when the following time string <code>"2012-02-29T15:33:00-04:00"</code> is parsed it is assumed that - * we have the local time ("15:33:00") which is in a timezone with an offset from UTC of "-04:00". - * Hence the result is a calendar object within the local time (which is "15:33:00") and the according timezone offset - * ("-04:00") which then results in the UTC time of "19:33:00+00:00" ("15:33:00" - "-04:00" -> "19:33:00 UTC"). - * <br/> - * As further explanation about our date time handling I reference to the following ISO specification: ISO 8601 - - * http://en.wikipedia.org/wiki/ISO_8601 and the copied section: - * Time_offsets_from_UTC - http://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC - * <blockquote>> - * The following times all refer to the same moment: "18:30Z", "22:30+04:00", and "15:00-03:30". Nautical time zone - * letters are not used with the exception of Z. + * + * <p><b>Parsing details for the EDM simple type DateTimeOffset</b></p> + * <p>When an time string is parsed to an according value object it is assumed that the time part + * in this string represents the local time with a timezone set.</p> + * <p>As an example, when the following string <code>"2012-02-29T15:33:00-04:00"</code> is parsed + * it is assumed that we have the local time ("15:33:00") which is in a timezone with an offset from UTC of "-04:00". + * Hence the result is a calendar object with the local time (which is "15:33:00") and the according timezone offset + * ("-04:00") which then results in the UTC time of "19:33:00+00:00" ("15:33:00" - "-04:00" -> "19:33:00 UTC").</p> + * <p>Please see ISO specification <a href="http://www.iso.org/iso/iso8601">ISO 8601</a> for further details. + * Time offsets are also explained in + * <a href="https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC">Wikipedia</a>: + * <blockquote> + * The following times all refer to the same moment: "18:30Z", "22:30+04:00", and "15:00-03:30". + * Nautical time zone letters are not used with the exception of Z. * To calculate UTC time one has to subtract the offset from the local time, e.g. for "15:00-03:30" do 15:00 - (-03:30) * to get 18:30 UTC. * </blockquote> - * <em>The behavior of our ABAP OData Library and Microsoft examples is the same as described above.</em> - * </td> - * </tr> - * </tbody> - * </table></p> * </p> - * + * * @org.apache.olingo.odata2.DoNotImplement */ public interface EdmSimpleType extends EdmType { http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/d18f9f0b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/processor/ODataSingleProcessor.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/processor/ODataSingleProcessor.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/processor/ODataSingleProcessor.java index 27b8aa2..e5ee823 100644 --- a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/processor/ODataSingleProcessor.java +++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/processor/ODataSingleProcessor.java @@ -32,7 +32,6 @@ import org.apache.olingo.odata2.api.edm.EdmServiceMetadata; import org.apache.olingo.odata2.api.ep.EntityProvider; import org.apache.olingo.odata2.api.exception.ODataException; import org.apache.olingo.odata2.api.exception.ODataNotImplementedException; -import org.apache.olingo.odata2.api.processor.ODataResponse.ODataResponseBuilder; import org.apache.olingo.odata2.api.processor.feature.CustomContentType; import org.apache.olingo.odata2.api.processor.part.BatchProcessor; import org.apache.olingo.odata2.api.processor.part.EntityComplexPropertyProcessor; @@ -358,13 +357,14 @@ public abstract class ODataSingleProcessor implements MetadataProcessor, Service @Override public ODataResponse readServiceDocument(final GetServiceDocumentUriInfo uriInfo, final String contentType) throws ODataException { - final Edm entityDataModel = getContext().getService().getEntityDataModel(); - final String serviceRoot = getContext().getPathInfo().getServiceRoot().toASCIIString(); + final Edm edm = getContext().getService().getEntityDataModel(); - if(getContext().getHttpMethod().equals("HEAD")) { + //Service Document has version 1.0 specifically + if (getContext().getHttpMethod().equals("HEAD")) { return ODataResponse.header(ODataHttpHeaders.DATASERVICEVERSION, ODataServiceVersion.V10).build(); } else { - final ODataResponse response = EntityProvider.writeServiceDocument(contentType, entityDataModel, serviceRoot); + final String serviceRoot = getContext().getPathInfo().getServiceRoot().toASCIIString(); + final ODataResponse response = EntityProvider.writeServiceDocument(contentType, edm, serviceRoot); return ODataResponse.fromResponse(response) .header(ODataHttpHeaders.DATASERVICEVERSION, ODataServiceVersion.V10).build(); } http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/d18f9f0b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmDateTime.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmDateTime.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmDateTime.java index e33d217..38b213d 100644 --- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmDateTime.java +++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmDateTime.java @@ -18,6 +18,7 @@ ******************************************************************************/ package org.apache.olingo.odata2.core.edm; +import java.sql.Timestamp; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; @@ -36,7 +37,7 @@ public class EdmDateTime extends AbstractSimpleType { private static final Pattern PATTERN = Pattern.compile( "(\\p{Digit}{1,4})-(\\p{Digit}{1,2})-(\\p{Digit}{1,2})" - + "T(\\p{Digit}{1,2}):(\\p{Digit}{1,2})(?::(\\p{Digit}{1,2})(\\.(\\p{Digit}{0,3}?)0*)?)?"); + + "T(\\p{Digit}{1,2}):(\\p{Digit}{1,2})(?::(\\p{Digit}{1,2})(\\.(\\p{Digit}{0,9}?)0*)?)?"); private static final Pattern JSON_PATTERN = Pattern.compile("/Date\\((-?\\p{Digit}+)\\)/"); private static final EdmDateTime instance = new EdmDateTime(); @@ -81,37 +82,18 @@ public class EdmDateTime extends AbstractSimpleType { Calendar dateTimeValue = Calendar.getInstance(TimeZone.getTimeZone("GMT")); dateTimeValue.clear(); + String valueString; if (literalKind == EdmLiteralKind.URI) { if (value.length() > 10 && value.startsWith("datetime'") && value.endsWith("'")) { - parseLiteral(value.substring(9, value.length() - 1), facets, dateTimeValue); + valueString = value.substring(9, value.length() - 1); } else { throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value)); } } else { - parseLiteral(value, facets, dateTimeValue); + valueString = value; } - if (returnType.isAssignableFrom(Calendar.class)) { - return returnType.cast(dateTimeValue); - } else if (returnType.isAssignableFrom(Long.class)) { - return returnType.cast(dateTimeValue.getTimeInMillis()); - } else if (returnType.isAssignableFrom(Date.class)) { - return returnType.cast(dateTimeValue.getTime()); - } else { - throw new EdmSimpleTypeException(EdmSimpleTypeException.VALUE_TYPE_NOT_SUPPORTED.addContent(returnType)); - } - } - - /** - * Parses a formatted date/time value and sets the values of a {@link Calendar} object accordingly. - * @param value the formatted date/time value as String - * @param facets additional constraints for parsing (optional) - * @param dateTimeValue the Calendar object to be set to the parsed value - * @throws EdmSimpleTypeException - */ - protected static void parseLiteral(final String value, final EdmFacets facets, final Calendar dateTimeValue) - throws EdmSimpleTypeException { - final Matcher matcher = PATTERN.matcher(value); + final Matcher matcher = PATTERN.matcher(valueString); if (!matcher.matches()) { throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value)); } @@ -124,16 +106,23 @@ public class EdmDateTime extends AbstractSimpleType { Byte.parseByte(matcher.group(5)), matcher.group(6) == null ? 0 : Byte.parseByte(matcher.group(6))); + int nanoSeconds = 0; if (matcher.group(7) != null) { - if (matcher.group(7).length() == 1 || matcher.group(7).length() > 8) { + if (matcher.group(7).length() == 1 || matcher.group(7).length() > 10) { throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value)); } final String decimals = matcher.group(8); if (facets != null && facets.getPrecision() != null && facets.getPrecision() < decimals.length()) { throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_FACETS_NOT_MATCHED.addContent(value, facets)); } - final String milliSeconds = decimals + "000".substring(decimals.length()); - dateTimeValue.set(Calendar.MILLISECOND, Short.parseShort(milliSeconds)); + nanoSeconds = Integer.parseInt(decimals + "000000000".substring(decimals.length())); + if (!(returnType.isAssignableFrom(Timestamp.class))) { + if (nanoSeconds % (1000 * 1000) == 0) { + dateTimeValue.set(Calendar.MILLISECOND, nanoSeconds / (1000 * 1000)); + } else { + throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value)); + } + } } // The Calendar class does not check any values until a get method is called, @@ -147,6 +136,20 @@ public class EdmDateTime extends AbstractSimpleType { throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value), e); } dateTimeValue.setLenient(true); + + if (returnType.isAssignableFrom(Calendar.class)) { + return returnType.cast(dateTimeValue); + } else if (returnType.isAssignableFrom(Long.class)) { + return returnType.cast(dateTimeValue.getTimeInMillis()); + } else if (returnType.isAssignableFrom(Date.class)) { + return returnType.cast(dateTimeValue.getTime()); + } else if (returnType.isAssignableFrom(Timestamp.class)) { + Timestamp timestamp = new Timestamp(dateTimeValue.getTimeInMillis()); + timestamp.setNanos(nanoSeconds); + return returnType.cast(timestamp); + } else { + throw new EdmSimpleTypeException(EdmSimpleTypeException.VALUE_TYPE_NOT_SUPPORTED.addContent(returnType)); + } } @Override @@ -164,13 +167,17 @@ public class EdmDateTime extends AbstractSimpleType { } if (literalKind == EdmLiteralKind.JSON) { - return "/Date(" + timeInMillis + ")/"; + if (value instanceof Timestamp && ((Timestamp) value).getNanos() % (1000 * 1000) != 0) { + throw new EdmSimpleTypeException(EdmSimpleTypeException.VALUE_ILLEGAL_CONTENT.addContent(value)); + } else { + return "/Date(" + timeInMillis + ")/"; + } } Calendar dateTimeValue = Calendar.getInstance(TimeZone.getTimeZone("GMT")); dateTimeValue.setTimeInMillis(timeInMillis); - StringBuilder result = new StringBuilder(23); // 23 characters are enough for millisecond precision. + StringBuilder result = new StringBuilder(29); // 29 characters are enough for nanosecond precision. final int year = dateTimeValue.get(Calendar.YEAR); appendTwoDigits(result, year / 100); appendTwoDigits(result, year % 100); @@ -185,8 +192,11 @@ public class EdmDateTime extends AbstractSimpleType { result.append(':'); appendTwoDigits(result, dateTimeValue.get(Calendar.SECOND)); + final int fractionalSecs = value instanceof Timestamp ? + ((Timestamp) value).getNanos() : + dateTimeValue.get(Calendar.MILLISECOND); try { - appendMilliseconds(result, dateTimeValue.get(Calendar.MILLISECOND), facets); + appendFractionalSeconds(result, fractionalSecs, value instanceof Timestamp, facets); } catch (final IllegalArgumentException e) { throw new EdmSimpleTypeException(EdmSimpleTypeException.VALUE_FACETS_NOT_MATCHED.addContent(value, facets), e); } @@ -205,29 +215,48 @@ public class EdmDateTime extends AbstractSimpleType { result.append((char) ('0' + number % 10)); } - protected static void appendMilliseconds(final StringBuilder result, final long milliseconds, final EdmFacets facets) - throws IllegalArgumentException { - final int digits = milliseconds % 1000 == 0 ? 0 : milliseconds % 100 == 0 ? 1 : milliseconds % 10 == 0 ? 2 : 3; - if (digits > 0) { + /** + * Appends the given milli- or nanoseconds to the given string builder, performance-optimized. + * @param result a {@link StringBuilder} + * @param fractionalSeconds fractional seconds (nonnegative and assumed to be in the valid range) + * @param isNano whether the value is to be interpreted as nanoseconds (milliseconds if false) + * @param facets the EDM facets containing an upper limit for decimal digits (optional, defaults to zero) + * @throws IllegalArgumentException if precision is not met + */ + protected static void appendFractionalSeconds(StringBuilder result, final int fractionalSeconds, + final boolean isNano, final EdmFacets facets) throws IllegalArgumentException { + int significantDigits = 0; + if (fractionalSeconds > 0) { + // Determine the number of significant digits. + significantDigits = isNano ? 9 : 3; + int output = fractionalSeconds; + while (output % 10 == 0) { + output /= 10; + significantDigits--; + } + result.append('.'); - for (int d = 100; d > 0; d /= 10) { - final byte digit = (byte) (milliseconds % (d * 10) / d); - if (digit > 0 || milliseconds % d > 0) { + for (int d = 100 * (isNano ? 1000 * 1000 : 1); d > 0; d /= 10) { + final byte digit = (byte) (fractionalSeconds % (d * 10) / d); + if (digit > 0 || fractionalSeconds % d > 0) { result.append((char) ('0' + digit)); } } } - if (facets != null && facets.getPrecision() != null) { - final int precision = facets.getPrecision(); - if (digits > precision) { + // Check precision constraint. + final Integer precision = facets == null || facets.getPrecision() == null ? null : facets.getPrecision(); + if (precision != null) { + if (precision < significantDigits) { throw new IllegalArgumentException(); - } - if (digits == 0 && precision > 0) { - result.append('.'); - } - for (int i = digits; i < precision; i++) { - result.append('0'); + } else { + // Add additional zeroes if the precision is larger than the number of significant digits. + if (significantDigits == 0 && precision > 0) { + result.append('.'); + } + for (int i = significantDigits; i < precision; i++) { + result.append('0'); + } } } } http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/d18f9f0b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmDateTimeOffset.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmDateTimeOffset.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmDateTimeOffset.java index 6c01ac0..16f867f 100644 --- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmDateTimeOffset.java +++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmDateTimeOffset.java @@ -18,6 +18,7 @@ ******************************************************************************/ package org.apache.olingo.odata2.core.edm; +import java.sql.Timestamp; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; @@ -31,10 +32,8 @@ import org.apache.olingo.odata2.api.edm.EdmSimpleTypeException; /** * Implementation of the EDM simple type DateTimeOffset. * - * Details about parsing of time strings to {@link EdmDateTimeOffset} objects can be found in + * Details about parsing of time strings to value objects can be found in the * {@link org.apache.olingo.odata2.api.edm.EdmSimpleType} documentation. - * - * */ public class EdmDateTimeOffset extends AbstractSimpleType { @@ -68,11 +67,11 @@ public class EdmDateTimeOffset extends AbstractSimpleType { } Calendar dateTimeValue = null; + long millis = 0; if (literalKind == EdmLiteralKind.JSON) { final Matcher matcher = JSON_PATTERN.matcher(value); if (matcher.matches()) { - long millis; try { millis = Long.parseLong(matcher.group(1)); } catch (final NumberFormatException e) { @@ -92,11 +91,10 @@ public class EdmDateTimeOffset extends AbstractSimpleType { } } dateTimeValue = Calendar.getInstance(TimeZone.getTimeZone(timeZone)); - dateTimeValue.clear(); - dateTimeValue.setTimeInMillis(millis); } } + int nanoSeconds = 0; if (dateTimeValue == null) { final Matcher matcher = PATTERN.matcher(value); if (!matcher.matches()) { @@ -111,16 +109,28 @@ public class EdmDateTimeOffset extends AbstractSimpleType { throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value)); } dateTimeValue.clear(); - EdmDateTime.parseLiteral(value.substring(0, matcher.group(1) == null ? value.length() : matcher.start(1)), - facets, dateTimeValue); + final Timestamp timestamp = EdmDateTime.getInstance().internalValueOfString( + value.substring(0, matcher.group(1) == null ? value.length() : matcher.start(1)), + EdmLiteralKind.DEFAULT, facets, Timestamp.class); + millis = timestamp.getTime() - dateTimeValue.get(Calendar.ZONE_OFFSET); + nanoSeconds = timestamp.getNanos(); + if (nanoSeconds % (1000 * 1000) != 0 && !returnType.isAssignableFrom(Timestamp.class)) { + throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value)); + } } if (returnType.isAssignableFrom(Calendar.class)) { + dateTimeValue.clear(); + dateTimeValue.setTimeInMillis(millis); return returnType.cast(dateTimeValue); } else if (returnType.isAssignableFrom(Long.class)) { - return returnType.cast(dateTimeValue.getTimeInMillis()); + return returnType.cast(millis); } else if (returnType.isAssignableFrom(Date.class)) { - return returnType.cast(dateTimeValue.getTime()); + return returnType.cast(new Date(millis)); + } else if (returnType.isAssignableFrom(Timestamp.class)) { + Timestamp timestamp = new Timestamp(millis); + timestamp.setNanos(nanoSeconds); + return returnType.cast(timestamp); } else { throw new EdmSimpleTypeException(EdmSimpleTypeException.VALUE_TYPE_NOT_SUPPORTED.addContent(returnType)); } @@ -154,11 +164,16 @@ public class EdmDateTimeOffset extends AbstractSimpleType { final int offsetInMinutes = offset / 60 / 1000; if (literalKind == EdmLiteralKind.JSON) { - return "/Date(" + milliSeconds + (offset == 0 ? "" : String.format("%+05d", offsetInMinutes)) + ")/"; + if (value instanceof Timestamp && ((Timestamp) value).getNanos() % (1000 * 1000) != 0) { + throw new EdmSimpleTypeException(EdmSimpleTypeException.VALUE_ILLEGAL_CONTENT.addContent(value)); + } else { + return "/Date(" + milliSeconds + (offset == 0 ? "" : String.format("%+05d", offsetInMinutes)) + ")/"; + } } else { final String localTimeString = - EdmDateTime.getInstance().valueToString(milliSeconds, EdmLiteralKind.DEFAULT, facets); + EdmDateTime.getInstance().valueToString( + value instanceof Timestamp ? value : milliSeconds, EdmLiteralKind.DEFAULT, facets); final int offsetHours = offsetInMinutes / 60; final int offsetMinutes = Math.abs(offsetInMinutes % 60); final String offsetString = offset == 0 ? "Z" : String.format("%+03d:%02d", offsetHours, offsetMinutes); http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/d18f9f0b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmTime.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmTime.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmTime.java index 7b7680b..fb80227 100644 --- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmTime.java +++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmTime.java @@ -18,6 +18,8 @@ ******************************************************************************/ package org.apache.olingo.odata2.core.edm; +import java.sql.Time; +import java.sql.Timestamp; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; @@ -54,33 +56,17 @@ public class EdmTime extends AbstractSimpleType { @Override protected <T> T internalValueOfString(final String value, final EdmLiteralKind literalKind, final EdmFacets facets, final Class<T> returnType) throws EdmSimpleTypeException { - Calendar valueCalendar; - if (literalKind == EdmLiteralKind.URI) { - if (value.length() > 6 && value.startsWith("time'") && value.endsWith("'")) { - valueCalendar = parseLiteral(value.substring(5, value.length() - 1), facets); - } else { - throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value)); - } - } else { - valueCalendar = parseLiteral(value, facets); - } - if (returnType.isAssignableFrom(Calendar.class)) { - return returnType.cast(valueCalendar); - } else if (returnType.isAssignableFrom(Long.class)) { - return returnType.cast(valueCalendar.getTimeInMillis()); - } else if (returnType.isAssignableFrom(Date.class)) { - return returnType.cast(valueCalendar.getTime()); - } else { - throw new EdmSimpleTypeException(EdmSimpleTypeException.VALUE_TYPE_NOT_SUPPORTED.addContent(returnType)); + if (literalKind == EdmLiteralKind.URI + && (value.length() <= 6 || !value.startsWith("time'") || !value.endsWith("'"))) { + throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value)); } - } - private Calendar parseLiteral(final String literal, final EdmFacets facets) throws EdmSimpleTypeException { - final Matcher matcher = PATTERN.matcher(literal); + final Matcher matcher = PATTERN.matcher( + literalKind == EdmLiteralKind.URI ? value.substring(5, value.length() - 1) : value); if (!matcher.matches() || (matcher.group(1) == null && matcher.group(2) == null && matcher.group(3) == null)) { - throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(literal)); + throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value)); } Calendar dateTimeValue = Calendar.getInstance(); @@ -93,23 +79,41 @@ public class EdmTime extends AbstractSimpleType { dateTimeValue.set(Calendar.SECOND, matcher.group(3) == null ? 0 : Integer.parseInt(matcher.group(3))); + int nanoSeconds = 0; if (matcher.group(4) != null) { - if (facets == null || facets.getPrecision() == null || facets.getPrecision() >= matcher.group(4).length()) { - if (matcher.group(4).length() <= 3) { - dateTimeValue.set(Calendar.MILLISECOND, - Short.parseShort(matcher.group(4) + "000".substring(0, 3 - matcher.group(4).length()))); - } else { - throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(literal)); + final String decimals = matcher.group(4); + if (facets == null || facets.getPrecision() == null || facets.getPrecision() >= decimals.length()) { + nanoSeconds = Integer.parseInt(decimals + "000000000".substring(decimals.length())); + if (!(returnType.isAssignableFrom(Timestamp.class))) { + if (nanoSeconds % (1000 * 1000) == 0) { + dateTimeValue.set(Calendar.MILLISECOND, nanoSeconds / (1000 * 1000)); + } else { + throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value)); + } } } else { - throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_FACETS_NOT_MATCHED.addContent(literal, facets)); + throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_FACETS_NOT_MATCHED.addContent(value, facets)); } } - if (dateTimeValue.get(Calendar.DAY_OF_YEAR) == 1) { - return dateTimeValue; + if (dateTimeValue.get(Calendar.DAY_OF_YEAR) != 1) { + throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value)); + } + + if (returnType.isAssignableFrom(Calendar.class)) { + return returnType.cast(dateTimeValue); + } else if (returnType.isAssignableFrom(Long.class)) { + return returnType.cast(dateTimeValue.getTimeInMillis()); + } else if (returnType.isAssignableFrom(Date.class)) { + return returnType.cast(dateTimeValue.getTime()); + } else if (returnType.isAssignableFrom(Time.class)) { + return returnType.cast(new Time(dateTimeValue.getTimeInMillis())); + } else if (returnType.isAssignableFrom(Timestamp.class)) { + Timestamp timestamp = new Timestamp(dateTimeValue.getTimeInMillis()); + timestamp.setNanos(nanoSeconds); + return returnType.cast(timestamp); } else { - throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(literal)); + throw new EdmSimpleTypeException(EdmSimpleTypeException.VALUE_TYPE_NOT_SUPPORTED.addContent(returnType)); } } @@ -131,7 +135,7 @@ public class EdmTime extends AbstractSimpleType { throw new EdmSimpleTypeException(EdmSimpleTypeException.VALUE_TYPE_NOT_SUPPORTED.addContent(value.getClass())); } - StringBuilder result = new StringBuilder(15); // 15 characters are enough for millisecond precision. + StringBuilder result = new StringBuilder(21); // 21 characters are enough for nanosecond precision. result.append('P'); result.append('T'); result.append(dateTimeValue.get(Calendar.HOUR_OF_DAY)); @@ -140,8 +144,11 @@ public class EdmTime extends AbstractSimpleType { result.append('M'); result.append(dateTimeValue.get(Calendar.SECOND)); + final int fractionalSecs = value instanceof Timestamp ? + ((Timestamp) value).getNanos() : + dateTimeValue.get(Calendar.MILLISECOND); try { - EdmDateTime.appendMilliseconds(result, dateTimeValue.get(Calendar.MILLISECOND), facets); + EdmDateTime.appendFractionalSeconds(result, fractionalSecs, value instanceof Timestamp, facets); } catch (final IllegalArgumentException e) { throw new EdmSimpleTypeException(EdmSimpleTypeException.VALUE_FACETS_NOT_MATCHED.addContent(value, facets), e); } http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/d18f9f0b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/edm/EdmSimpleTypeTest.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/edm/EdmSimpleTypeTest.java b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/edm/EdmSimpleTypeTest.java index 47a9417..5b1b50b 100644 --- a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/edm/EdmSimpleTypeTest.java +++ b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/edm/EdmSimpleTypeTest.java @@ -29,6 +29,8 @@ import static org.mockito.Mockito.when; import java.math.BigDecimal; import java.math.BigInteger; +import java.sql.Time; +import java.sql.Timestamp; import java.util.Arrays; import java.util.Calendar; import java.util.Date; @@ -563,7 +565,39 @@ public class EdmSimpleTypeTest extends BaseTest { Calendar dateTime2 = Calendar.getInstance(TimeZone.getTimeZone("GMT-11:30")); dateTime2.clear(); dateTime2.set(1969, 11, 31, 12, 29, 58); - assertEquals("/Date(-2000)/", instance.valueToString(dateTime2, EdmLiteralKind.JSON, null)); + dateTime2.set(Calendar.MILLISECOND, 1); + assertEquals("/Date(-1999)/", instance.valueToString(dateTime2, EdmLiteralKind.JSON, null)); + + Calendar dateTime3 = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + dateTime3.clear(); + dateTime3.set(1954, 7, 4); + dateTime3.set(Calendar.MILLISECOND, 42); + expectErrorInValueToString(instance, dateTime3, EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(0, null), + EdmSimpleTypeException.VALUE_FACETS_NOT_MATCHED); + expectErrorInValueToString(instance, dateTime3, EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(1, null), + EdmSimpleTypeException.VALUE_FACETS_NOT_MATCHED); + expectErrorInValueToString(instance, dateTime3, EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(2, null), + EdmSimpleTypeException.VALUE_FACETS_NOT_MATCHED); + assertEquals("1954-08-04T00:00:00.042", instance.valueToString(dateTime3, EdmLiteralKind.DEFAULT, + getPrecisionScaleFacets(3, null))); + assertEquals("1954-08-04T00:00:00.0420", instance.valueToString(dateTime3, EdmLiteralKind.DEFAULT, + getPrecisionScaleFacets(4, null))); + assertEquals("1954-08-04T00:00:00.04200", instance.valueToString(dateTime3, EdmLiteralKind.DEFAULT, + getPrecisionScaleFacets(5, null))); + + Timestamp timestamp = new Timestamp(0); + assertEquals("1970-01-01T00:00:00", instance.valueToString(timestamp, EdmLiteralKind.DEFAULT, null)); + timestamp.setNanos(1000 * 1000); + assertEquals("/Date(1)/", instance.valueToString(timestamp, EdmLiteralKind.JSON, null)); + timestamp.setNanos(1500 * 1000); + assertEquals("1970-01-01T00:00:00.0015", instance.valueToString(timestamp, EdmLiteralKind.DEFAULT, null)); + timestamp.setNanos(42); + assertEquals("1970-01-01T00:00:00.000000042", instance.valueToString(timestamp, EdmLiteralKind.DEFAULT, + getPrecisionScaleFacets(9, null))); + expectErrorInValueToString(instance, timestamp, EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(8, null), + EdmSimpleTypeException.VALUE_FACETS_NOT_MATCHED); + expectErrorInValueToString(instance, timestamp, EdmLiteralKind.JSON, null, + EdmSimpleTypeException.VALUE_ILLEGAL_CONTENT); dateTime.add(Calendar.MILLISECOND, -100); expectErrorInValueToString(instance, dateTime, EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(0, null), @@ -574,64 +608,6 @@ public class EdmSimpleTypeTest extends BaseTest { expectErrorInValueToString(instance, dateTime, null, null, EdmSimpleTypeException.LITERAL_KIND_MISSING); } - /** - * Extended test for combination of precision with dates before 1970 (and for regression after 1970) - */ - @Test - public void valueToStringDateTimeSpecial() throws Exception { - - for (int precision = 0; precision < 3; precision++) { - final EdmSimpleType instance = EdmSimpleTypeKind.DateTime.getEdmSimpleTypeInstance(); - final EdmFacets facets = getPrecisionScaleFacets(precision, null); - final Calendar date = Calendar.getInstance(TimeZone.getTimeZone("GMT")); - date.set(Calendar.MILLISECOND, 10 * precision + 1); - - date.set(1954, 7, 4); - expectErrorInValueToString(instance, date, EdmLiteralKind.DEFAULT, facets, - EdmSimpleTypeException.VALUE_FACETS_NOT_MATCHED); - - date.set(1999, 7, 4); - expectErrorInValueToString(instance, date, EdmLiteralKind.DEFAULT, facets, - EdmSimpleTypeException.VALUE_FACETS_NOT_MATCHED); - } - - for (int precision = 3; precision < 6; precision++) { - assertValueToStringDateTimeSpecial(1954, 7, 4, precision); - assertValueToStringDateTimeSpecial(1999, 7, 4, precision); - } - } - - private void assertValueToStringDateTimeSpecial(final int year, final int month, final int day, final int precision) - throws Exception { - final EdmSimpleType instance = EdmSimpleTypeKind.DateTime.getEdmSimpleTypeInstance(); - final StringBuilder regExToMatch = new StringBuilder();// = new StringBuilder("1954-08-04T\\d\\d:\\d\\d:\\d\\d"); - regExToMatch.append(year).append("-"); - if (month < 9) { - regExToMatch.append("0"); - } - // add '1' to the month because java calendar month begin with '0' - regExToMatch.append(month + 1).append("-"); - if (day < 10) { - regExToMatch.append("0"); - } - regExToMatch.append(day).append("T\\d\\d:\\d\\d:\\d\\d"); - - if (precision > 0) { - regExToMatch.append("\\."); - } - for (int i = 0; i < precision; i++) { - regExToMatch.append("\\d"); - } - Calendar date = Calendar.getInstance(TimeZone.getTimeZone("GMT")); - date.set(year, month, day); - date.set(Calendar.MILLISECOND, 10 * precision + 1); - - // - String formated = instance.valueToString(date, EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(precision, null)); - assertTrue("Formated date '" + formated + "' is wrong for precision '" + precision + - "'. (used regex = [" + regExToMatch.toString() + "])", formated.matches(regExToMatch.toString())); - } - @Test public void valueToStringDateTimeOffset() throws Exception { final EdmSimpleType instance = EdmSimpleTypeKind.DateTimeOffset.getEdmSimpleTypeInstance(); @@ -668,6 +644,14 @@ public class EdmSimpleTypeTest extends BaseTest { final String time = date.toString().substring(11, 19); assertTrue(instance.valueToString(date, EdmLiteralKind.DEFAULT, null).contains(time)); + Timestamp timestamp = new Timestamp(millis); + assertTrue(instance.valueToString(timestamp, EdmLiteralKind.JSON, null).contains("007")); + timestamp.setNanos(42); + expectErrorInValueToString(instance, timestamp, EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(8, null), + EdmSimpleTypeException.VALUE_FACETS_NOT_MATCHED); + expectErrorInValueToString(instance, timestamp, EdmLiteralKind.JSON, null, + EdmSimpleTypeException.VALUE_ILLEGAL_CONTENT); + expectErrorInValueToString(instance, 0, EdmLiteralKind.DEFAULT, null, EdmSimpleTypeException.VALUE_TYPE_NOT_SUPPORTED); expectErrorInValueToString(instance, dateTime, null, null, EdmSimpleTypeException.LITERAL_KIND_MISSING); @@ -958,6 +942,12 @@ public class EdmSimpleTypeTest extends BaseTest { assertEquals("PT23H32M2.9S", instance.valueToString(dateTime, EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(1, null))); + Timestamp timestamp = new Timestamp(millis); + timestamp.setNanos(123456789); + assertTrue(instance.valueToString(timestamp, EdmLiteralKind.DEFAULT, null).contains("M3.123456789S")); + expectErrorInValueToString(instance, timestamp, EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(8, null), + EdmSimpleTypeException.VALUE_FACETS_NOT_MATCHED); + expectErrorInValueToString(instance, dateTime, EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(0, null), EdmSimpleTypeException.VALUE_FACETS_NOT_MATCHED); expectErrorInValueToString(instance, 0, EdmLiteralKind.DEFAULT, null, @@ -1177,15 +1167,20 @@ public class EdmSimpleTypeTest extends BaseTest { dateTime.set(1969, 11, 31, 23, 59, 18); assertEquals(dateTime, instance.valueOfString("/Date(-42000)/", EdmLiteralKind.JSON, null, Calendar.class)); - expectErrorInValueOfString(instance, "2012-02-29T23:32:02.9", EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(0, - null), EdmSimpleTypeException.LITERAL_FACETS_NOT_MATCHED); - expectErrorInValueOfString(instance, "2012-02-29T23:32:02.98700", EdmLiteralKind.DEFAULT, getPrecisionScaleFacets( - 2, null), EdmSimpleTypeException.LITERAL_FACETS_NOT_MATCHED); + Timestamp timestamp = new Timestamp(0); + timestamp.setNanos(987654321); + assertEquals(timestamp, instance.valueOfString("1970-01-01T00:00:00.987654321", EdmLiteralKind.DEFAULT, null, + Timestamp.class)); + + expectErrorInValueOfString(instance, "2012-02-29T23:32:02.9", EdmLiteralKind.DEFAULT, + getPrecisionScaleFacets(0, null), EdmSimpleTypeException.LITERAL_FACETS_NOT_MATCHED); + expectErrorInValueOfString(instance, "2012-02-29T23:32:02.98700", EdmLiteralKind.DEFAULT, + getPrecisionScaleFacets(2, null), EdmSimpleTypeException.LITERAL_FACETS_NOT_MATCHED); expectErrorInValueOfString(instance, "2012-02-29T23:32:02.9876", EdmLiteralKind.DEFAULT, null, EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT); expectErrorInValueOfString(instance, "2012-02-29T23:32:02.", EdmLiteralKind.DEFAULT, null, EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT); - expectErrorInValueOfString(instance, "2012-02-29T23:32:02.00000000", EdmLiteralKind.DEFAULT, null, + expectErrorInValueOfString(instance, "2012-02-29T23:32:02.0000000000", EdmLiteralKind.DEFAULT, null, EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT); expectErrorInValueOfString(instance, "20120229T233202", EdmLiteralKind.DEFAULT, null, EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT); @@ -1264,8 +1259,18 @@ public class EdmSimpleTypeTest extends BaseTest { dateTime.set(1969, 11, 31, 23, 59, 18); assertEquals(dateTime, instance.valueOfString("/Date(-42000+0660)/", EdmLiteralKind.JSON, null, Calendar.class)); - expectErrorInValueOfString(instance, "2012-02-29T23:32:02.9Z", EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(0, - null), EdmSimpleTypeException.LITERAL_FACETS_NOT_MATCHED); + assertEquals(Long.valueOf(3601234L), + instance.valueOfString("1970-01-01T00:00:01.234-01:00", EdmLiteralKind.DEFAULT, null, Long.class)); + + Timestamp timestamp = new Timestamp(0); + timestamp.setNanos(987654300); + assertEquals(timestamp, instance.valueOfString("1970-01-01T00:00:00.9876543Z", EdmLiteralKind.DEFAULT, null, + Timestamp.class)); + + expectErrorInValueOfString(instance, "2012-02-29T23:32:02.9Z", EdmLiteralKind.DEFAULT, + getPrecisionScaleFacets(0, null), EdmSimpleTypeException.LITERAL_FACETS_NOT_MATCHED); + expectErrorInValueOfString(instance, "2012-02-29T23:32:02.9876Z", EdmLiteralKind.DEFAULT, null, + EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT); expectErrorInValueOfString(instance, "datetime'2012-02-29T23:32:02'", EdmLiteralKind.URI, null, EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT); expectErrorInValueOfString(instance, "2012-02-29T23:32:02X", EdmLiteralKind.DEFAULT, null, @@ -1652,16 +1657,16 @@ public class EdmSimpleTypeTest extends BaseTest { assertEquals(dateTime, instance.valueOfString("PT23H32M3S", EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(0, null), Calendar.class)); dateTime.add(Calendar.MILLISECOND, 10); - assertEquals(dateTime, instance.valueOfString("PT23H32M3.01S", EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(2, - null), Calendar.class)); + assertEquals(dateTime, instance.valueOfString("PT23H32M3.01S", EdmLiteralKind.DEFAULT, + getPrecisionScaleFacets(2, null), Calendar.class)); dateTime.add(Calendar.MILLISECOND, -23); - assertEquals(dateTime, instance.valueOfString("PT23H32M2.987S", EdmLiteralKind.DEFAULT, getPrecisionScaleFacets( - null, null), Calendar.class)); - assertEquals(dateTime, instance.valueOfString("PT23H32M2.98700S", EdmLiteralKind.DEFAULT, getPrecisionScaleFacets( - 5, null), Calendar.class)); + assertEquals(dateTime, instance.valueOfString("PT23H32M2.987S", EdmLiteralKind.DEFAULT, + getPrecisionScaleFacets(null, null), Calendar.class)); + assertEquals(dateTime, instance.valueOfString("PT23H32M2.98700S", EdmLiteralKind.DEFAULT, + getPrecisionScaleFacets(5, null), Calendar.class)); dateTime.add(Calendar.MILLISECOND, -87); - assertEquals(dateTime, instance.valueOfString("PT23H32M2.9S", EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(1, - null), Calendar.class)); + assertEquals(dateTime, instance.valueOfString("PT23H32M2.9S", EdmLiteralKind.DEFAULT, + getPrecisionScaleFacets(1, null), Calendar.class)); dateTime.add(Calendar.HOUR, -23); assertEquals(dateTime, instance.valueOfString("PT32M2.9S", EdmLiteralKind.DEFAULT, null, Calendar.class)); @@ -1673,6 +1678,15 @@ public class EdmSimpleTypeTest extends BaseTest { dateTime.add(Calendar.MINUTE, 59); assertEquals(dateTime, instance.valueOfString("PT59M", EdmLiteralKind.DEFAULT, null, Calendar.class)); + assertEquals(Long.valueOf(dateTime.getTimeInMillis()), + instance.valueOfString("PT59M", EdmLiteralKind.DEFAULT, null, Long.class)); + + assertEquals(dateTime.getTimeInMillis(), + instance.valueOfString("PT59M", EdmLiteralKind.DEFAULT, null, Time.class).getTime()); + + assertEquals(123456789, + instance.valueOfString("PT59M0.123456789S", EdmLiteralKind.DEFAULT, null, Timestamp.class).getNanos()); + expectErrorInValueOfString(instance, "PT1H2M3.1234S", EdmLiteralKind.DEFAULT, null, EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT); expectErrorInValueOfString(instance, "PT13H2M3.9S", EdmLiteralKind.DEFAULT, getPrecisionScaleFacets(0, null),
