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