This is an automated email from the ASF dual-hosted git repository. diru pushed a commit to branch issue/SLING-10654 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-sightly.git
commit 2603a64365291ea722b6e36be96514246da70b0a Merge: 474697c e6acc53 Author: Dirk Rudolph <[email protected]> AuthorDate: Fri Jul 23 12:31:44 2021 +0200 Merge branch 'master' into issue/SLING-10654 pom.xml | 37 +++--- .../engine/extension/FormatFilterExtension.java | 80 +++++------- .../extension/FormatFilterExtensionTest.java | 145 ++++++++++++++++++++- 3 files changed, 199 insertions(+), 63 deletions(-) diff --cc src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtension.java index f7cf405,ff438de..6ec400e --- 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 @@@ -55,25 -60,6 +62,9 @@@ public class FormatFilterExtension impl private static final Logger LOG = LoggerFactory.getLogger(FormatFilterExtension.class); private static final Pattern PLACEHOLDER_REGEX = Pattern.compile("\\{\\d+}"); + private static final Pattern COMPLEX_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"; - - private static final String DATE_FORMAT_TYPE = "date"; - private static final String NUMBER_FORMAT_TYPE = "number"; - private static final String STRING_FORMAT_TYPE = "string"; + - static { - try { - FormatFilterExtension.class.getClassLoader().loadClass("com.ibm.icu.text.MessageFormat"); - hasIcuSupport = true; - } catch (ClassNotFoundException ex) { - LOG.trace("Initialize without ICU support: {}", ex.getMessage(), ex); - hasIcuSupport = false; - } - } ++ private boolean hasIcuSupport = true; @Override public Object call(final RenderContext renderContext, Object... arguments) { @@@ -100,14 -79,15 +91,15 @@@ return getNumberFormattedString(runtimeObjectModel, source, options, formatObject); } if (hasPlaceHolders) { - return getFormattedString(runtimeObjectModel, source, formatObject); + return getFormattedString(runtimeObjectModel, source, options, formatObject, hasComplexPlaceholders); } + 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 diff --cc src/test/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtensionTest.java index 63fd1e9,f168883..76f5c63 --- 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 @@@ -18,8 -18,12 +18,13 @@@ ******************************************************************************/ package org.apache.sling.scripting.sightly.impl.engine.extension; + import java.time.LocalDateTime; + import java.time.ZoneId; +import java.util.Arrays; + import java.util.Collections; + import java.util.Date; import java.util.HashMap; + import java.util.Map; import javax.script.Bindings; import javax.script.SimpleBindings; @@@ -29,9 -34,10 +35,12 @@@ import org.apache.sling.scripting.sight import org.apache.sling.scripting.sightly.render.RuntimeObjectModel; import org.junit.Test; +import com.google.common.collect.ImmutableMap; + + 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 { @@@ -50,30 -56,134 +59,160 @@@ } }; 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)); + } -} + + @Test + public void testSimpleFormat() { + Object result = subject.call(renderContext, - "This {0} a {1} format", ImmutableMap.of("format", Arrays.asList("is", "simple"))); ++ "This {0} a {1} format", Collections.singletonMap("format", Arrays.asList("is", "simple"))); + assertEquals("This is a simple format", result); + } + + @Test + public void testComplexFormatNoSimplePlaceholderWithLocale() { + Object result = subject.call(renderContext, + "This query has {0,plural,zero {# results} one {# result} other {# results}}", + new HashMap<String, Object>() {{ - put("format", Arrays.asList(7)); ++ put("format", Collections.singletonList(7)); + put("locale", "en_US"); + }}); + assertEquals("This query has 7 results", result); + } + + @Test + public void testComplexFormatWithSimplePlaceholderNoLocale() { + Object result = subject.call(renderContext, + "This {0} has {1,plural,zero {# results} one {# result} other {# results}}", - ImmutableMap.of("format", Arrays.asList("query", 7))); ++ Collections.singletonMap("format", Arrays.asList("query", 7))); + assertEquals("This query has 7 results", result); + } +}
