/******************************************************************************* * Copyright 2013 SAP AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.sap.core.odata.core.edm; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.sap.core.odata.api.edm.EdmFacets; import com.sap.core.odata.api.edm.EdmLiteralKind; import com.sap.core.odata.api.edm.EdmSimpleTypeException; /** * Implementation of the EDM simple type DateTimeOffset. * * Details about parsing of time strings to {@link EdmDateTimeOffset} objects can be found in {@link com.sap.core.odata.api.edm.EdmSimpleType} documentation. * * @author SAP AG */ public class EdmDateTimeOffset 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}{1,7})?)?" + "(Z|([-+]\\p{Digit}{1,2}:\\p{Digit}{2}))?"); private static final Pattern JSON_PATTERN = Pattern.compile( "/Date\\((-?\\p{Digit}+)(?:(\\+|-)(\\p{Digit}{1,4}))?\\)/"); private static final EdmDateTimeOffset instance = new EdmDateTimeOffset(); public static EdmDateTimeOffset getInstance() { return instance; } @Override public Class<?> getDefaultType() { return Calendar.class; } @Override protected <T> T internalValueOfString(final String value, final EdmLiteralKind literalKind, final EdmFacets facets, final Class<T> returnType) throws EdmSimpleTypeException { if (literalKind == EdmLiteralKind.URI) { if (value.length() > 16 && value.startsWith("datetimeoffset'") && value.endsWith("'")) { return internalValueOfString(value.substring(15, value.length() - 1), EdmLiteralKind.DEFAULT, facets, returnType); } else { throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value)); } } Calendar dateTimeValue = null; 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) { throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value), e); } String timeZone = "GMT"; if (matcher.group(2) != null) { final int offsetInMinutes = Integer.parseInt(matcher.group(3)); if (offsetInMinutes >= 24 * 60) { throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value)); } if (offsetInMinutes != 0) { timeZone += matcher.group(2) + String.valueOf(offsetInMinutes / 60) + ":" + String.format("%02d", offsetInMinutes % 60); // Convert the local-time milliseconds to UTC. millis -= (matcher.group(2).equals("+") ? 1 : -1) * offsetInMinutes * 60 * 1000; } } dateTimeValue = Calendar.getInstance(TimeZone.getTimeZone(timeZone)); dateTimeValue.clear(); dateTimeValue.setTimeInMillis(millis); } } if (dateTimeValue == null) { final Matcher matcher = PATTERN.matcher(value); if (!matcher.matches()) { throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value)); } final String timeZoneOffset = matcher.group(1) != null && matcher.group(2) != null && !matcher.group(2).matches("[-+]0+:0+") ? matcher.group(2) : null; dateTimeValue = Calendar.getInstance(TimeZone.getTimeZone("GMT" + timeZoneOffset)); if (dateTimeValue.get(Calendar.ZONE_OFFSET) == 0 && timeZoneOffset != null) { 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); } 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)); } } @Override protected <T> String internalValueToString(final T value, final EdmLiteralKind literalKind, final EdmFacets facets) throws EdmSimpleTypeException { Long milliSeconds; // number of milliseconds since 1970-01-01T00:00:00Z int offset; // offset in milliseconds from GMT to the requested time zone if (value instanceof Date) { milliSeconds = ((Date) value).getTime(); // Although java.util.Date, as stated in its documentation, // "is intended to reflect coordinated universal time (UTC)", // its toString() method uses the default time zone. And so do we. Calendar dateTimeValue = Calendar.getInstance(); dateTimeValue.setTime((Date) value); offset = dateTimeValue.get(Calendar.ZONE_OFFSET) + dateTimeValue.get(Calendar.DST_OFFSET); } else if (value instanceof Calendar) { final Calendar dateTimeValue = (Calendar) ((Calendar) value).clone(); milliSeconds = dateTimeValue.getTimeInMillis(); offset = dateTimeValue.get(Calendar.ZONE_OFFSET) + dateTimeValue.get(Calendar.DST_OFFSET); } else if (value instanceof Long) { milliSeconds = (Long) value; offset = 0; } else { throw new EdmSimpleTypeException(EdmSimpleTypeException.VALUE_TYPE_NOT_SUPPORTED.addContent(value.getClass())); } milliSeconds += offset; // Convert from UTC to local time. final int offsetInMinutes = offset / 60 / 1000; if (literalKind == EdmLiteralKind.JSON) { return "/Date(" + milliSeconds + (offset == 0 ? "" : String.format("%+05d", offsetInMinutes)) + ")/"; } else { final String localTimeString = EdmDateTime.getInstance().valueToString(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); return localTimeString + offsetString; } } @Override public String toUriLiteral(final String literal) throws EdmSimpleTypeException { return "datetimeoffset'" + literal + "'"; } }