diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java index af860e50936dd..f1ea12dbaa76f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java @@ -30,6 +30,6 @@ public Date unmarshal(String arg0) throws Exception { @Override public String marshal(Date arg0) throws Exception { - return new ISO8601DateConverter().format(arg0); + return new ISO8601DateConverter().shortFormat(arg0); } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java index 9e6eef462738d..34444b858a039 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java @@ -17,6 +17,7 @@ import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.TimeZone; @@ -26,38 +27,60 @@ */ public class ISO8601DateConverter { // Note: because of the trailing "0000000", this is not quite ISO 8601 compatible - private static final String DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'"; + private static final String DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; private static final String SHORT_DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + private static final String DATETIME_PATTERN_NO_S = "yyyy-MM-dd'T'HH:mm'Z'"; + private static final String DATETIME_PATTERN_TO_DECIMAL = "yyyy-MM-dd'T'HH:mm:ss."; public String format(Date date) { - return getFormat().format(date); + DateFormat iso8601Format = new SimpleDateFormat(DATETIME_PATTERN, Locale.US); + iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT")); + return iso8601Format.format(date); } public String shortFormat(Date date) { - return getShortFormat().format(date); + DateFormat iso8601Format = new SimpleDateFormat(SHORT_DATETIME_PATTERN, Locale.US); + iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT")); + return iso8601Format.format(date); } public Date parse(String date) throws ParseException { if (date == null) return null; - // Sometimes, the date comes back without the ".SSSSSSS" part (presumably when the decimal value - // of the date is "0". Use the short format in that case. - if (date.indexOf('.') < 0) - return getShortFormat().parse(date); - else - return getFormat().parse(date); - } + int length = date.length(); + if (length == 17) { + // [2012-01-04T23:21Z] length = 17 + return parseDateFromString(date, DATETIME_PATTERN_NO_S); + } + else if (length == 20) { + // [2012-01-04T23:21:59Z] length = 20 + return parseDateFromString(date, SHORT_DATETIME_PATTERN); + } + else if (length >= 22 && length <= 28) { + // [2012-01-04T23:21:59.1Z] length = 22 + // [2012-01-04T23:21:59.1234567Z] length = 28 + // Need to handle the milliseconds gently. - private DateFormat getFormat() { - DateFormat iso8601Format = new SimpleDateFormat(DATETIME_PATTERN, Locale.US); - iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT")); - return iso8601Format; + Date allExceptMilliseconds = parseDateFromString(date, DATETIME_PATTERN_TO_DECIMAL); + long timeWithSecondGranularity = allExceptMilliseconds.getTime(); + // Decimal point is at 19 + String secondDecimalString = date.substring(19, date.indexOf('Z')); + Float secondDecimal = Float.parseFloat(secondDecimalString); + int milliseconds = Math.round(secondDecimal * 1000); + long timeInMS = timeWithSecondGranularity + milliseconds; + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(timeInMS); + return calendar.getTime(); + } + else { + throw new IllegalArgumentException(String.format("Invalid Date String: %s", date)); + } } - private DateFormat getShortFormat() { - DateFormat iso8601Format = new SimpleDateFormat(SHORT_DATETIME_PATTERN, Locale.US); + private static Date parseDateFromString(final String value, final String pattern) throws ParseException { + DateFormat iso8601Format = new SimpleDateFormat(pattern, Locale.US); iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT")); - return iso8601Format; + return iso8601Format.parse(value); } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java index 525c2adf086ae..ddff3f9aa8895 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java @@ -196,7 +196,7 @@ private InputStream generateEntry(PropertiesWriter propertiesWriter) { writer.writeEndElement(); // title writer.writeStartElement("updated"); - writer.writeCharacters(iso8601DateConverter.shortFormat(dateFactory.getDate())); + writer.writeCharacters(iso8601DateConverter.format(dateFactory.getDate())); writer.writeEndElement(); // updated writer.writeStartElement("author"); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java index 41b38735afa51..6988596979495 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java @@ -40,7 +40,7 @@ public String serialize(String edmType, Object value) { String serializedValue; if (value instanceof Date) { - serializedValue = iso8601DateConverter.shortFormat((Date) value); + serializedValue = iso8601DateConverter.format((Date) value); } else if (value instanceof byte[]) { serializedValue = new String(Base64.encode((byte[]) value)); diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java index bb9e36e07d9c0..c69d18d8f1ebb 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java @@ -16,7 +16,9 @@ import static org.junit.Assert.*; +import java.util.Calendar; import java.util.Date; +import java.util.TimeZone; import org.junit.Test; @@ -29,9 +31,23 @@ public void shortFormatWorks() throws Exception { // Act Date result = converter.parse(value); + String value2 = converter.format(result); // Assert assertNotNull(result); + + Calendar calendar = Calendar.getInstance(); + calendar.setTime(result); + calendar.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals("Year", 2012, calendar.get(Calendar.YEAR)); + assertEquals("Month", 1, calendar.get(Calendar.MONTH) + 1); + assertEquals("Day", 12, calendar.get(Calendar.DAY_OF_MONTH)); + assertEquals("Hour", 0, calendar.get(Calendar.HOUR)); + assertEquals("Minute", 35, calendar.get(Calendar.MINUTE)); + assertEquals("Second", 58, calendar.get(Calendar.SECOND)); + assertEquals("Millisecond", 0, calendar.get(Calendar.MILLISECOND)); + + assertEquals("2012-01-12T00:35:58.000Z", value2); } @Test @@ -42,9 +58,50 @@ public void longFormatWorks() throws Exception { // Act Date result = converter.parse(value); + String value2 = converter.format(result); // Assert assertNotNull(result); + + Calendar calendar = Calendar.getInstance(); + calendar.setTime(result); + calendar.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals("Year", 2012, calendar.get(Calendar.YEAR)); + assertEquals("Month", 1, calendar.get(Calendar.MONTH) + 1); + assertEquals("Day", 12, calendar.get(Calendar.DAY_OF_MONTH)); + assertEquals("Hour", 0, calendar.get(Calendar.HOUR)); + assertEquals("Minute", 35, calendar.get(Calendar.MINUTE)); + assertEquals("Second", 58, calendar.get(Calendar.SECOND)); + assertEquals("Millisecond", 123, calendar.get(Calendar.MILLISECOND)); + + assertEquals("2012-01-12T00:35:58.123Z", value2); + } + + @Test + public void mixedFormatWorks() throws Exception { + // Arrange + ISO8601DateConverter converter = new ISO8601DateConverter(); + String value = "2012-01-12T00:35:58.12Z"; + + // Act + Date result = converter.parse(value); + String value2 = converter.format(result); + + // Assert + assertNotNull(result); + + Calendar calendar = Calendar.getInstance(); + calendar.setTime(result); + calendar.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals("Year", 2012, calendar.get(Calendar.YEAR)); + assertEquals("Month", 1, calendar.get(Calendar.MONTH) + 1); + assertEquals("Day", 12, calendar.get(Calendar.DAY_OF_MONTH)); + assertEquals("Hour", 0, calendar.get(Calendar.HOUR)); + assertEquals("Minute", 35, calendar.get(Calendar.MINUTE)); + assertEquals("Second", 58, calendar.get(Calendar.SECOND)); + assertEquals("Millisecond", 120, calendar.get(Calendar.MILLISECOND)); + + assertEquals("2012-01-12T00:35:58.120Z", value2); } @Test @@ -56,9 +113,11 @@ public void shortFormatRoundTrips() throws Exception { // Act Date result = converter.parse(value); String value2 = converter.shortFormat(result); + String value3 = converter.format(result); // Assert assertNotNull(result); assertEquals(value, value2); + assertEquals("2012-01-12T00:35:58.000Z", value3); } }