From 2b3d4bf8bf76739dcc9d7267dfe877e981657a78 Mon Sep 17 00:00:00 2001 From: Jason Cooke Date: Thu, 8 Mar 2012 10:39:16 -0800 Subject: [PATCH 1/3] Fix #228 --- .../implementation/ISO8601DateConverter.java | 59 ++++++++++++------- .../implementation/AtomReaderWriter.java | 2 +- .../DefaultEdmValueConterter.java | 2 +- .../ISO8601DateConverterTests.java | 2 +- 4 files changed, 41 insertions(+), 24 deletions(-) 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 9e6eef462738..9fa415c44c8f 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,54 @@ */ 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 SHORT_DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + private static final String DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + private static final String DATETIME_PATTERN_NO_S = "yyyy-MM-dd'T'HH:mm'Z'"; + private static final String DATETIME_PATTERN_NO_MS = "yyyy-MM-dd'T'HH:mm:ss'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); - } - - public String shortFormat(Date date) { - return getShortFormat().format(date); + DateFormat iso8601Format = new SimpleDateFormat(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, DATETIME_PATTERN_NO_MS); + } + 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 cal = Calendar.getInstance(); + cal.setTimeInMillis(timeInMS); + return cal.getTime(); + } + else { + throw new IllegalArgumentException(String.format("Invalid Date String: %s", date)); + } } - private DateFormat getShortFormat() { - DateFormat iso8601Format = new SimpleDateFormat(SHORT_DATETIME_PATTERN, Locale.US); + public 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 e7b067f5d0ad..3324a62d68c5 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 @@ -193,7 +193,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 f7df91f533f5..a9ec25452eea 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 @@ -39,7 +39,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 { serializedValue = value.toString(); 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 bb9e36e07d9c..a777b001c584 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 @@ -55,7 +55,7 @@ public void shortFormatRoundTrips() throws Exception { // Act Date result = converter.parse(value); - String value2 = converter.shortFormat(result); + String value2 = converter.format(result); // Assert assertNotNull(result); From adcf3ad86c73fa8f28595c240131e19a0295f7f3 Mon Sep 17 00:00:00 2001 From: Jason Cooke Date: Tue, 3 Apr 2012 12:40:13 -0700 Subject: [PATCH 2/3] Added new ISO8601 unit tests Added back "shortFormat" function, needed by Blob --- .../ContainerACLDateAdapter.java | 2 +- .../implementation/ISO8601DateConverter.java | 12 +++- .../ISO8601DateConverterTests.java | 61 ++++++++++++++++++- 3 files changed, 70 insertions(+), 5 deletions(-) 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 af860e50936d..f1ea12dbaa76 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 9fa415c44c8f..41bb096c7daa 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 @@ -28,8 +28,8 @@ 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.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_NO_MS = "yyyy-MM-dd'T'HH:mm:ss'Z'"; private static final String DATETIME_PATTERN_TO_DECIMAL = "yyyy-MM-dd'T'HH:mm:ss."; public String format(Date date) { @@ -38,6 +38,12 @@ public String format(Date date) { return iso8601Format.format(date); } + public String shortFormat(Date 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; @@ -49,7 +55,7 @@ public Date parse(String date) throws ParseException { } else if (length == 20) { // [2012-01-04T23:21:59Z] length = 20 - return parseDateFromString(date, DATETIME_PATTERN_NO_MS); + return parseDateFromString(date, SHORT_DATETIME_PATTERN); } else if (length >= 22 && length <= 28) { // [2012-01-04T23:21:59.1Z] length = 22 @@ -72,7 +78,7 @@ else if (length >= 22 && length <= 28) { } } - public static Date parseDateFromString(final String value, final String pattern) throws ParseException { + 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.parse(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 a777b001c584..c69d18d8f1eb 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 @@ -55,10 +112,12 @@ public void shortFormatRoundTrips() throws Exception { // Act Date result = converter.parse(value); - String value2 = converter.format(result); + String value2 = converter.shortFormat(result); + String value3 = converter.format(result); // Assert assertNotNull(result); assertEquals(value, value2); + assertEquals("2012-01-12T00:35:58.000Z", value3); } } From 2962f94c79ebbcc8b5db68c26daa18b6c75b27c9 Mon Sep 17 00:00:00 2001 From: Jason Cooke Date: Tue, 3 Apr 2012 12:50:05 -0700 Subject: [PATCH 3/3] Making variable name more descriptive. --- .../services/blob/implementation/ISO8601DateConverter.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 41bb096c7daa..34444b858a03 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 @@ -69,9 +69,9 @@ else if (length >= 22 && length <= 28) { Float secondDecimal = Float.parseFloat(secondDecimalString); int milliseconds = Math.round(secondDecimal * 1000); long timeInMS = timeWithSecondGranularity + milliseconds; - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(timeInMS); - return cal.getTime(); + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(timeInMS); + return calendar.getTime(); } else { throw new IllegalArgumentException(String.format("Invalid Date String: %s", date));