SLING-8559 - JcrValueMap should allow getting ZonedDateTime values
diff --git a/src/main/java/org/apache/sling/jcr/resource/internal/helper/BooleanConverter.java b/src/main/java/org/apache/sling/jcr/resource/internal/helper/BooleanConverter.java
index 38c2490..7c9e20e 100644
--- a/src/main/java/org/apache/sling/jcr/resource/internal/helper/BooleanConverter.java
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/helper/BooleanConverter.java
@@ -19,6 +19,7 @@
 package org.apache.sling.jcr.resource.internal.helper;
 
 import java.math.BigDecimal;
+import java.time.ZonedDateTime;
 import java.util.Calendar;
 import java.util.Date;
 
@@ -87,6 +88,11 @@
         return this.getNumber().floatValue();
     }
 
+    @Override
+    public ZonedDateTime toZonedDateTime() {
+        return new CalendarConverter(toCalendar()).toZonedDateTime();
+    }
+
     /**
      * @see org.apache.sling.jcr.resource.internal.helper.Converter#toCalendar()
      */
diff --git a/src/main/java/org/apache/sling/jcr/resource/internal/helper/CalendarConverter.java b/src/main/java/org/apache/sling/jcr/resource/internal/helper/CalendarConverter.java
index 8385161..cf24f18 100644
--- a/src/main/java/org/apache/sling/jcr/resource/internal/helper/CalendarConverter.java
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/helper/CalendarConverter.java
@@ -18,6 +18,7 @@
  */
 package org.apache.sling.jcr.resource.internal.helper;
 
+import java.time.ZonedDateTime;
 import java.util.Calendar;
 import java.util.Date;
 
@@ -35,6 +36,11 @@
         this.value = val;
     }
 
+    @Override
+    public ZonedDateTime toZonedDateTime() {
+        return ZonedDateTime.ofInstant(this.value.toInstant(), this.value.getTimeZone().toZoneId().normalized());
+    }
+
     /**
      * @see org.apache.sling.jcr.resource.internal.helper.Converter#toCalendar()
      */
diff --git a/src/main/java/org/apache/sling/jcr/resource/internal/helper/Converter.java b/src/main/java/org/apache/sling/jcr/resource/internal/helper/Converter.java
index 42ca217..50acb7c 100644
--- a/src/main/java/org/apache/sling/jcr/resource/internal/helper/Converter.java
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/helper/Converter.java
@@ -20,6 +20,7 @@
 
 
 import java.math.BigDecimal;
+import java.time.ZonedDateTime;
 import java.util.Calendar;
 import java.util.Date;
 
@@ -72,6 +73,13 @@
     Float toFloat();
 
     /**
+     * Convert to ZonedDateTime.
+     * @return Calendar representation of the converted value
+     * @throws IllegalArgumentException  if the value cannot be parsed into a calendar
+     */
+    ZonedDateTime toZonedDateTime();
+
+    /**
      * Convert to Calendar.
      * @return Calendar representation of the converted value
      * @throws IllegalArgumentException  if the value cannot be parsed into a calendar
diff --git a/src/main/java/org/apache/sling/jcr/resource/internal/helper/DateConverter.java b/src/main/java/org/apache/sling/jcr/resource/internal/helper/DateConverter.java
index 8b81124..9fdb0ff 100644
--- a/src/main/java/org/apache/sling/jcr/resource/internal/helper/DateConverter.java
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/helper/DateConverter.java
@@ -18,6 +18,7 @@
  */
 package org.apache.sling.jcr.resource.internal.helper;
 
+import java.time.ZonedDateTime;
 import java.util.Calendar;
 import java.util.Date;
 
@@ -35,6 +36,11 @@
         this.value = val;
     }
 
+    @Override
+    public ZonedDateTime toZonedDateTime() {
+        return new CalendarConverter(toCalendar()).toZonedDateTime();
+    }
+
     /**
      * @see org.apache.sling.jcr.resource.internal.helper.Converter#toCalendar()
      */
diff --git a/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrPropertyMapCacheEntry.java b/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrPropertyMapCacheEntry.java
index ed40e47..2fe32eb 100644
--- a/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrPropertyMapCacheEntry.java
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrPropertyMapCacheEntry.java
@@ -26,6 +26,7 @@
 import java.io.Serializable;
 import java.lang.reflect.Array;
 import java.math.BigDecimal;
+import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
@@ -381,6 +382,10 @@
         } else if (Calendar.class == type) {
             return (T) getConverter(value).toCalendar();
 
+        } else if (ZonedDateTime.class == type) {
+            Calendar calendar = getConverter(value).toCalendar();
+            return (T) ZonedDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId().normalized());
+
         } else if (Value.class == type) {
             return (T) this.createValue(value, node);
 
@@ -407,6 +412,8 @@
             return new DateConverter((Date)value);
         } else if ( value instanceof Calendar ) {
             return new CalendarConverter((Calendar)value);
+        } else if ( value instanceof ZonedDateTime ) {
+            return new ZonedDateTimeConverter((ZonedDateTime)value);
         }
         // default string based
         return new StringConverter(value);
diff --git a/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrResourceUtil.java b/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrResourceUtil.java
index 849041b..76b7718 100644
--- a/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrResourceUtil.java
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrResourceUtil.java
@@ -20,6 +20,7 @@
 
 import java.io.InputStream;
 import java.math.BigDecimal;
+import java.time.ZonedDateTime;
 import java.util.Calendar;
 
 import javax.jcr.Node;
@@ -146,7 +147,9 @@
     throws RepositoryException {
         Value val;
         ValueFactory fac = session.getValueFactory();
-        if(value instanceof Calendar) {
+        if(value instanceof ZonedDateTime) {
+            val = fac.createValue(new ZonedDateTimeConverter((ZonedDateTime)value).toCalendar());
+        } else if(value instanceof Calendar) {
             val = fac.createValue((Calendar)value);
         } else if (value instanceof InputStream) {
             val = fac.createValue(fac.createBinary((InputStream)value));
diff --git a/src/main/java/org/apache/sling/jcr/resource/internal/helper/NumberConverter.java b/src/main/java/org/apache/sling/jcr/resource/internal/helper/NumberConverter.java
index 5045d95..dd0fc28 100644
--- a/src/main/java/org/apache/sling/jcr/resource/internal/helper/NumberConverter.java
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/helper/NumberConverter.java
@@ -19,6 +19,7 @@
 package org.apache.sling.jcr.resource.internal.helper;
 
 import java.math.BigDecimal;
+import java.time.ZonedDateTime;
 import java.util.Calendar;
 import java.util.Date;
 
@@ -83,6 +84,11 @@
         return this.value.floatValue();
     }
 
+    @Override
+    public ZonedDateTime toZonedDateTime() {
+        return new CalendarConverter(toCalendar()).toZonedDateTime();
+    }
+
     /**
      * @see org.apache.sling.jcr.resource.internal.helper.Converter#toCalendar()
      */
diff --git a/src/main/java/org/apache/sling/jcr/resource/internal/helper/StringConverter.java b/src/main/java/org/apache/sling/jcr/resource/internal/helper/StringConverter.java
index bd5af3d..9b0ef07 100644
--- a/src/main/java/org/apache/sling/jcr/resource/internal/helper/StringConverter.java
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/helper/StringConverter.java
@@ -19,6 +19,7 @@
 package org.apache.sling.jcr.resource.internal.helper;
 
 import java.math.BigDecimal;
+import java.time.ZonedDateTime;
 import java.util.Calendar;
 import java.util.Date;
 
@@ -85,6 +86,11 @@
         return Float.parseFloat(this.toString());
     }
 
+    @Override
+    public ZonedDateTime toZonedDateTime() {
+        return new CalendarConverter(toCalendar()).toZonedDateTime();
+    }
+
     /**
      * @see org.apache.sling.jcr.resource.internal.helper.Converter#toCalendar()
      */
diff --git a/src/main/java/org/apache/sling/jcr/resource/internal/helper/ZonedDateTimeConverter.java b/src/main/java/org/apache/sling/jcr/resource/internal/helper/ZonedDateTimeConverter.java
new file mode 100644
index 0000000..82cba61
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/helper/ZonedDateTimeConverter.java
@@ -0,0 +1,11 @@
+package org.apache.sling.jcr.resource.internal.helper;
+
+import java.time.ZonedDateTime;
+import java.util.GregorianCalendar;
+
+public class ZonedDateTimeConverter extends CalendarConverter {
+
+    public ZonedDateTimeConverter(ZonedDateTime value) {
+        super(GregorianCalendar.from(value));
+    }
+}
diff --git a/src/test/java/org/apache/sling/jcr/resource/internal/JcrModifiableValueMapTest.java b/src/test/java/org/apache/sling/jcr/resource/internal/JcrModifiableValueMapTest.java
index 205bb2b..a590ac1 100644
--- a/src/test/java/org/apache/sling/jcr/resource/internal/JcrModifiableValueMapTest.java
+++ b/src/test/java/org/apache/sling/jcr/resource/internal/JcrModifiableValueMapTest.java
@@ -25,6 +25,8 @@
 import java.io.Serializable;
 import java.lang.reflect.Array;
 import java.math.BigDecimal;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
@@ -33,6 +35,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TimeZone;
 import java.util.concurrent.atomic.AtomicReference;
 
 import javax.jcr.Node;
@@ -72,7 +75,7 @@
         }
         getSession().save();
     }
-    
+
     private void setProperty(final Node node,
             final String propertyName,
             final Object propertyValue)
@@ -91,7 +94,7 @@
             node.setProperty(propertyName, createValue(propertyValue, node.getSession()));
         }
     }
-    
+
     Value createValue(final Object value, final Session session)
             throws RepositoryException {
                 Value val;
@@ -399,4 +402,52 @@
 
     }
 
-}
+    /**
+     * Test conversions involving ZonedDateTime
+     */
+    public void testZonedDateTimeConversions() throws Exception {
+        this.rootNode.getSession().refresh(false);
+
+        String dateAsIso8601 = "2019-07-04T14:05:37.123+02:00";
+        ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateAsIso8601, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone(zonedDateTime.getOffset()));
+        calendar.setTimeInMillis(zonedDateTime.toInstant().toEpochMilli());
+
+        // write property
+        final Node testNode = this.rootNode.addNode("dateConversionTest" + System.currentTimeMillis());
+        testNode.setProperty(PROP1, calendar);
+        testNode.getSession().save();
+
+        // write with property map
+        final ModifiableValueMap pvm = new JcrModifiableValueMap(testNode, getHelperData());
+        pvm.put(PROP2, zonedDateTime);
+        pvm.put(PROP3, dateAsIso8601);
+        getSession().save();
+
+        // read with property map
+        final ValueMap vm = new JcrModifiableValueMap(testNode, getHelperData());
+
+        // check types
+        assertTrue(vm.get(PROP1) instanceof Calendar);
+        assertTrue(vm.get(PROP2) instanceof Calendar);
+        assertTrue(vm.get(PROP3) instanceof String);
+
+        // to ZonedDateTime
+        assertEquals(zonedDateTime, vm.get(PROP1, ZonedDateTime.class)); // from Calendar
+        assertEquals(zonedDateTime, vm.get(PROP2, ZonedDateTime.class)); // from ZonedDateTime
+        assertEquals(zonedDateTime, vm.get(PROP3, ZonedDateTime.class)); // from ISO-8601 String
+
+        // from ZonedDateTime
+        assertEqualsCalendar(calendar, vm.get(PROP2, Calendar.class)); // to Calendar
+        assertEquals(calendar.getTime(), vm.get(PROP2, Date.class));   // to Date
+        assertEquals(dateAsIso8601, vm.get(PROP2, String.class));      // to String
+
+
+        // read properties
+        assertEqualsCalendar(calendar, testNode.getProperty(PROP1).getDate());
+        assertEqualsCalendar(calendar, testNode.getProperty(PROP2).getDate());
+        assertEqualsCalendar(calendar, testNode.getProperty(PROP3).getDate());
+
+    }
+
+}
\ No newline at end of file