This is an automated email from the ASF dual-hosted git repository.

diru pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-sightly.git


The following commit(s) were added to refs/heads/master by this push:
     new e6acc53  SLING-9989: migrate date formating to java.time API (#11)
e6acc53 is described below

commit e6acc53b4744cd5a78472a0f740bc3f2be685319
Author: Dirk Rudolph <[email protected]>
AuthorDate: Thu Jul 22 17:31:27 2021 +0200

    SLING-9989: migrate date formating to java.time API (#11)
---
 pom.xml                                            |  26 +++
 .../engine/extension/FormatFilterExtension.java    |  73 ++++----
 .../extension/FormatFilterExtensionTest.java       | 189 +++++++++++++++++++++
 3 files changed, 254 insertions(+), 34 deletions(-)

diff --git a/pom.xml b/pom.xml
index c9a63a1..8797d39 100644
--- a/pom.xml
+++ b/pom.xml
@@ -54,6 +54,9 @@
 
     <properties>
         <sling.java.version>8</sling.java.version>
+        <!-- There is significant difference between the locale data provided 
in JDK8 and starting with JDK9. In order to achieve
+        compatibility between newer versions of the JDK and JDK8, we use 
COMPAT as the default locale data provider for tests. -->
+        <java.locale.providers>COMPAT,JRE,SPI</java.locale.providers>
     </properties>
 
     <!-- 
======================================================================= -->
@@ -62,6 +65,13 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    
<argLine>-Djava.locale.providers=${java.locale.providers}</argLine>
+                </configuration>
+            </plugin>
+            <plugin>
                 <groupId>com.github.spotbugs</groupId>
                 <artifactId>spotbugs-maven-plugin</artifactId>
                 <version>3.1.11</version>
@@ -292,4 +302,20 @@
         </dependency>
     </dependencies>
 
+    <profiles>
+        <profile>
+            <id>jacoco-report</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <argLine>@{argLine} 
-Djava.locale.providers=${java.locale.providers}</argLine>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
 </project>
diff --git 
a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtension.java
 
b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtension.java
index 06527cf..ff438de 100644
--- 
a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtension.java
+++ 
b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtension.java
@@ -18,11 +18,11 @@
  
******************************************************************************/
 package org.apache.sling.scripting.sightly.impl.engine.extension;
 
-import java.text.DateFormat;
 import java.text.DecimalFormat;
 import java.text.DecimalFormatSymbols;
 import java.text.NumberFormat;
-import java.text.SimpleDateFormat;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Locale;
@@ -38,6 +38,8 @@ import 
org.apache.sling.scripting.sightly.extension.RuntimeExtension;
 import org.apache.sling.scripting.sightly.render.RenderContext;
 import org.apache.sling.scripting.sightly.render.RuntimeObjectModel;
 import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @Component(
         service = RuntimeExtension.class,
@@ -47,15 +49,17 @@ import org.osgi.service.component.annotations.Component;
 )
 public class FormatFilterExtension implements RuntimeExtension {
 
-    private static final Pattern PLACEHOLDER_REGEX = 
Pattern.compile("\\{\\d+}");
-    private static final String FORMAT_OPTION = "format";
-    private static final String TYPE_OPTION = "type";
-    private static final String LOCALE_OPTION = "locale";
-    private static final String TIMEZONE_OPTION = "timezone";
+    protected static final String FORMAT_OPTION = "format";
+    protected static final String TYPE_OPTION = "type";
+    protected static final String LOCALE_OPTION = "locale";
+    protected static final String TIMEZONE_OPTION = "timezone";
+
+    protected static final String DATE_FORMAT_TYPE = "date";
+    protected static final String NUMBER_FORMAT_TYPE = "number";
+    protected static final String STRING_FORMAT_TYPE = "string";
 
-    private static final String DATE_FORMAT_TYPE = "date";
-    private static final String NUMBER_FORMAT_TYPE = "number";
-    private static final String STRING_FORMAT_TYPE = "string";
+    private static final Logger LOG = 
LoggerFactory.getLogger(FormatFilterExtension.class);
+    private static final Pattern PLACEHOLDER_REGEX = 
Pattern.compile("\\{\\d+}");
 
     @Override
     public Object call(final RenderContext renderContext, Object... arguments) 
{
@@ -77,12 +81,13 @@ public class FormatFilterExtension implements 
RuntimeExtension {
         if (hasPlaceHolders) {
             return getFormattedString(runtimeObjectModel, source, 
formatObject);
         }
+
         try {
-            // somebody will hate me for this
-            new SimpleDateFormat(source);
+            // try to parse as DateTimeFormatter
+            DateTimeFormatter.ofPattern(source);
             return getDateFormattedString(runtimeObjectModel, source, options, 
formatObject);
-        } catch (IllegalArgumentException e) {
-            // ignore
+        } catch (IllegalArgumentException ex) {
+            LOG.trace("Not a datetime format: {}", source, ex);
         }
         try {
             // for this too, but such is life
@@ -176,20 +181,19 @@ public class FormatFilterExtension implements 
RuntimeExtension {
         return "";
     }
 
-    private int getPredefinedFormattingStyleFromValue(String value) {
+    private FormatStyle getPredefinedFormattingStyleFromValue(String value) {
         switch (value.toLowerCase(Locale.ROOT)) {
             case "default":
-                return DateFormat.DEFAULT;
-            case "short":
-                return DateFormat.SHORT;
             case "medium":
-                return DateFormat.MEDIUM;
+                return FormatStyle.MEDIUM;
+            case "short":
+                return FormatStyle.SHORT;
             case "long":
-                return DateFormat.LONG;
+                return FormatStyle.LONG;
             case "full":
-                return DateFormat.FULL;
+                return FormatStyle.FULL;
             default:
-                return -1;
+                return null;
         }
     }
 
@@ -198,25 +202,26 @@ public class FormatFilterExtension implements 
RuntimeExtension {
             return null;
         }
         try {
-            final DateFormat formatter;
-            int formattingStyle = 
getPredefinedFormattingStyleFromValue(format);
-            if (formattingStyle != -1) {
+            DateTimeFormatter formatter;
+            FormatStyle formattingStyle = 
getPredefinedFormattingStyleFromValue(format);
+            if (formattingStyle != null) {
+                formatter = DateTimeFormatter.ofLocalizedDate(formattingStyle);
                 if (locale != null) {
-                    formatter = DateFormat.getDateInstance(formattingStyle, 
locale);
-                } else {
-                    formatter = DateFormat.getDateInstance(formattingStyle);
+                    formatter = formatter.withLocale(locale);
                 }
             } else {
+                // escape reserved characters, that are allowed according to 
the htl-tck
+                format =  format.replaceAll("([{}#])", "'$1'");
+                // normalized text narrow form to full form to be compatible 
with java.text.SimpleDateFormat
+                // for example EEEEE becomes EEEE
+                format = format.replaceAll("([GMLQqEec])\\1{4}", "$1$1$1$1");
                 if (locale != null) {
-                    formatter = new SimpleDateFormat(format, locale);
+                    formatter = DateTimeFormatter.ofPattern(format, locale);
                 } else {
-                    formatter = new SimpleDateFormat(format);
+                    formatter = DateTimeFormatter.ofPattern(format);
                 }
             }
-            if (timezone != null) {
-                formatter.setTimeZone(timezone);
-            }
-            return formatter.format(date);
+            return formatter.format(timezone != null ? 
date.toInstant().atZone(timezone.toZoneId()) : date.toInstant());
         } catch (Exception e) {
             String error = String.format("Error during formatting of date %s 
with format %s, locale %s and timezone %s", date, format, locale, timezone);
             throw new SightlyException( error, e);
diff --git 
a/src/test/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtensionTest.java
 
b/src/test/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtensionTest.java
new file mode 100644
index 0000000..f168883
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtensionTest.java
@@ -0,0 +1,189 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.sling.scripting.sightly.impl.engine.extension;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.script.Bindings;
+import javax.script.SimpleBindings;
+
+import org.apache.sling.scripting.sightly.SightlyException;
+import org.apache.sling.scripting.sightly.render.AbstractRuntimeObjectModel;
+import org.apache.sling.scripting.sightly.render.RenderContext;
+import org.apache.sling.scripting.sightly.render.RuntimeObjectModel;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeThat;
+
+public class FormatFilterExtensionTest {
+
+    private final RenderContext renderContext = new RenderContext() {
+        @Override public RuntimeObjectModel getObjectModel() {
+            return new AbstractRuntimeObjectModel() {
+            };
+        }
+
+        @Override public Bindings getBindings() {
+            return new SimpleBindings();
+        }
+
+        @Override public Object call(String s, Object... objects) {
+            return null;
+        }
+    };
+    private final FormatFilterExtension subject = new FormatFilterExtension();
+    private final Date testDate = Date.from(LocalDateTime.of(1918, 12, 1, 0, 
0, 0, 0)
+        .atZone(ZoneId.of("UTC"))
+        .toInstant());
+
+    @Test
+    public void testDateFormatNull() {
+        assertNull(subject.call(renderContext, "default", new HashMap<String, 
Object>() {{
+            put(FormatFilterExtension.TYPE_OPTION, "date");
+            put(FormatFilterExtension.FORMAT, null);
+        }}));
+    }
+
+    @Test
+    public void testDateFormatNoDateObject() {
+        assertNull(subject.call(renderContext, "yyyy-MM-dd", 
Collections.singletonMap(FormatFilterExtension.FORMAT, new Object())));
+    }
+
+    @Test(expected = SightlyException.class)
+    public void testDateFormatFalseFormat() {
+        assertDate(null, "yT", null, null);
+    }
+
+    @Test
+    public void testDateFormatWithUTC() {
+        assertDate("1918-12-01 00:00:00.000Z", "yyyy-MM-dd HH:mm:ss.SSSXXX", 
"UTC", null);
+    }
+
+    @Test
+    public void testDateFormatWithZoneOffset() {
+        assertDate("1918-12-01 02:00:00.000+02:00", "yyyy-MM-dd 
HH:mm:ss.SSSXXX", "GMT+02:00", null);
+    }
+
+    @Test
+    public void testDateFormatWithZoneOffsetRFC822() {
+        assertDate("1918-12-01 02:00:00.000+0200", "yyyy-MM-dd HH:mm:ss.SSSZ", 
"GMT+02:00", null);
+    }
+
+    @Test
+    public void testDateFormatWithZoneName() {
+        assertDate("1918-12-01 02:00:00.000(GMT+02:00)", "yyyy-MM-dd 
HH:mm:ss.SSS(z)", "GMT+02:00", null);
+    }
+
+    /**
+     * When using jdk9 or newer, make sure to set the {@code 
java.locale.providers = COMPAT,SPI}
+     *
+     * @see <a 
href="https://docs.oracle.com/javase/9/docs/api/java/util/spi/LocaleServiceProvider.html";>LocaleServiceProvider</a>
+     */
+    @Test
+    public void testDateFormatWithEscapedCharacters() {
+        assumeJdk8LocaleData();
+        assertDate("01 December '18 12:00 AM; day in year: 335; week in year: 
49",
+            "dd MMMM ''yy hh:mm a; 'day in year': D; 'week in year': w",
+            "UTC",
+            null);
+    }
+
+    /**
+     * When using jdk9 or newer, make sure to set the {@code 
java.locale.providers = COMPAT,SPI}
+     *
+     * @see <a 
href="https://docs.oracle.com/javase/9/docs/api/java/util/spi/LocaleServiceProvider.html";>LocaleServiceProvider</a>
+     */
+    @Test
+    public void testDateFormatWithLocale() {
+        assumeJdk8LocaleData();
+        assertDate("Sonntag, 1 Dez 1918", "EEEE, d MMM y", "UTC", "de");
+    }
+
+    /**
+     * When using jdk9 or newer, make sure to set the {@code 
java.locale.providers = COMPAT,SPI}
+     *
+     * @see <a 
href="https://docs.oracle.com/javase/9/docs/api/java/util/spi/LocaleServiceProvider.html";>LocaleServiceProvider</a>
+     */
+    @Test
+    public void testDateFormatWithFormatStyleShort() {
+        assumeJdk8LocaleData();
+        assertDate("01/12/18", "short", "GMT+02:00", "fr");
+    }
+
+    @Test
+    public void testDateFormatWithFormatStyleMedium() {
+        assertDate("1 déc. 1918", "medium", "GMT+02:00", "fr");
+    }
+
+    @Test
+    public void testDateFormatWithFormatStyleDefault() {
+        assertDate("1 déc. 1918", "default", "GMT+02:00", "fr");
+    }
+
+    @Test
+    public void testDateFormatWithFormatStyleLong() {
+        assertDate("1 décembre 1918", "long", "GMT+02:00", "fr");
+    }
+
+    @Test
+    public void testDateFormatWithFormatStyleFull() {
+        assertDate("dimanche 1 décembre 1918", "full", "GMT+02:00", "fr");
+    }
+
+    @Test
+    public void testDateFormatMixedWithReservedCharacters() {
+        assertEquals("#1: 1918 {0}", subject.call(renderContext, "#1: yyyy 
{0}", new HashMap<String, Object>() {{
+            put(FormatFilterExtension.TYPE_OPTION, 
FormatFilterExtension.DATE_FORMAT_TYPE);
+            put(FormatFilterExtension.FORMAT_OPTION, testDate);
+        }}));
+    }
+
+    @Test
+    public void testDateFormatNoNarrowForm() {
+        assertDate("December", "MMMMM", "UTC", "en");
+        assertDate("Sunday", "EEEEE", "UTC", "en");
+        assertDate("Sonntag", "eeeee", "UTC", "de");
+    }
+
+    private void assumeJdk8LocaleData() {
+        if(!System.getProperty("java.version").startsWith("1.8")) {
+            assumeThat(System.getProperty("java.locale.providers"), 
startsWith("COMPAT"));
+        }
+    }
+
+    private void assertDate(String expected, String format, String timezone, 
String locale) {
+        Map<String, Object> options = new HashMap<>();
+        options.put(FormatFilterExtension.FORMAT, testDate);
+        if (timezone != null) {
+            options.put(FormatFilterExtension.TIMEZONE_OPTION, timezone);
+        }
+        if (locale != null) {
+            options.put(FormatFilterExtension.LOCALE_OPTION, locale);
+        }
+        assertEquals(expected, subject.call(renderContext, format, options));
+    }
+}
\ No newline at end of file

Reply via email to