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

ddekany pushed a commit to branch FREEMARKER-35
in repository https://gitbox.apache.org/repos/asf/freemarker.git


The following commit(s) were added to refs/heads/FREEMARKER-35 by this push:
     new 93b5edd  [FREEMARKER-35] For LocalDateTime and LocalTime, 
automatically decrease time style until formatting succeeds, as the style has 
no time zone part. Like when date_time_format is "long", for LocalDateTime the 
effective value will be "long_medium" for most locales.
93b5edd is described below

commit 93b5edd52b9a284b6d54f38775edc358ad7726d1
Author: ddekany <[email protected]>
AuthorDate: Fri Dec 17 21:08:39 2021 +0100

    [FREEMARKER-35] For LocalDateTime and LocalTime, automatically decrease 
time style until formatting succeeds, as the style has no time zone part. Like 
when date_time_format is "long", for LocalDateTime the effective value will be 
"long_medium" for most locales.
---
 src/main/java/freemarker/core/Configurable.java    | 25 +++----
 .../core/JavaTemplateTemporalFormat.java           | 83 +++++++++++++++++-----
 src/main/java/freemarker/core/_Java8.java          | 34 ---------
 .../test/templatesuite/templates/temporal.ftl      | 26 +++----
 4 files changed, 90 insertions(+), 78 deletions(-)

diff --git a/src/main/java/freemarker/core/Configurable.java 
b/src/main/java/freemarker/core/Configurable.java
index 938d318..94c06c3 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -1168,14 +1168,15 @@ public class Configurable {
      * saving. This is because if the offset is not shown, FreeMarker has to 
convert the value to the time zone
      * specified in the {@link #setTimeZone(TimeZone) timeZone} setting, but 
we don't know the day, so we can't account
      * for daylight saving changes, and thus we can't do zone conversion 
reliably. To address this, you can do a few
-     * things (TODO [FREEMARKER-35] Check if these are implemented like shown):
+     * things:
      * <ul>
      *   <li>Use a format style, like {@code "medium"}, or the default {@code 
""}. In this case FreeMarker will
      *   automatically increase the veroboseness (like uses {@code "long"} 
instead of {@code "medium"}) until the
-     *   offset is shown. This format is also the defaults of FreeMarker, so 
by default you don't have to anything.
+     *   offset is shown. This format is also the default of FreeMarker, so by 
default you don't have to anything.
      *   </li>
      *   <li>Mark the offset/zone part optional in the format pattern: {@code 
"HH:mm[X];version=2"}.
-     *   ({@code ";version=2"} is needed for "[" and "]" to be interpreted via 
{@link DateTimeFormatter}.)</li>
+     *   ({@code ";version=2"} is needed for "[" and "]" to be interpreted via 
{@link DateTimeFormatter}.)
+     *   (TODO [FREEMARKER-35] Check if these were implemented as shown 
here)</li>
      * </ul>
      */
     public void setTimeFormat(String timeFormat) {
@@ -1299,19 +1300,19 @@ public class Configurable {
      *       offset is omitted for date values in the {@code "iso"} format, 
while it's preserved for the {@code "xs"}
      *       format.
      *       
-     *   <li><p>{@code "short"}, {@code "medium"}, {@code "long"}, or {@code 
"full"}, which that has locale-dependent
+     *   <li><p>{@code "short"}, {@code "medium"}, {@code "long"}, or {@code 
"full"}, which has locale-dependent
      *       meaning defined by the Java platform (see in the documentation of 
{@link java.text.DateFormat} in case
      *       of {@link Date}, and {@link FormatStyle} in case of {@link 
Temporal}-s).
-     *       For date-time values, you can specify the length of the date and 
time part independently, be separating
+     *       For date-time values, you can specify the length of the date and 
time part independently, by separating
      *       them with {@code _}, like {@code "short_medium"}. ({@code 
"medium"} means
      *       {@code "medium_medium"} for date-time values.)
-     *       TODO [FREEMARKER-35] Check if these are implemented
-     *       Note that Java 8 has a bug (JDK-8085887) where formatting {@link 
LocalDateTime} and {@link LocalTime}
-     *       fails if for the given locale the format contains a time zone 
field. This was fixed in Java 9. To work this
-     *       issue around, for these classes, FreeMarker will decrease the 
verbosity if the time part (like "full" to
-     *       "long", "long" to "medium", etc.), until formatting succeeds. 
Also, when formatting {@link OffsetTime}
-     *       values, FreeMarker might will increase the verboseness to display 
the offset (see at {@link #setTimeFormat}
-     *       why).
+     *       The value you set here is possibly automatically adjusted for 
specific temporal types, in two scenarios:
+     *       Secanrio 1. is when formatting {@link LocalDateTime} or {@link 
LocalTime} would fail because the format
+     *       contains a time zone field (for most locales "long" anb "full" 
are like that). To handle this, FreeMarker
+     *       will decrease the verbosity if the time part (like "full" is 
replaced with "long", "long" is replaced with
+     *       "medium", etc.), until successful formatting will be possible. 
Scenarion 2 is when formatting
+     *       {@link OffsetTime} values, and the current time zone has DST, in 
which case FreeMarker will increase the
+     *       verbosity until the offset is displayed (see at {@link 
#setTimeFormat} why).
      *
      *   <li><p>Anything that starts with {@code "@"} followed by a letter is 
interpreted as a custom
      *       date/time/dateTime format, but only if either {@link 
Configuration#getIncompatibleImprovements()}
diff --git a/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java 
b/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
index 06ef378..8bc49e9 100644
--- a/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
@@ -60,6 +60,10 @@ class JavaTemplateTemporalFormat extends 
TemplateTemporalFormat {
     static final String MEDIUM = "medium";
     static final String LONG = "long";
     static final String FULL = "full";
+
+    private static final LocalDateTime LOCAL_DATE_TIME_SAMPLE
+            = LocalDateTime.of(2020, 12, 1, 1, 2, 3);
+
     private static final String ANY_FORMAT_STYLE = "(" + SHORT + "|" + MEDIUM 
+ "|" + LONG + "|" + FULL + ")";
     // Matches format style patterns like "long_medium", "long", and "" 
(0-length string). It's a legacy from the
     // pre-Temporal code that "" means "medium", and that it's the default of 
the date/time-related format settings.
@@ -75,13 +79,14 @@ class JavaTemplateTemporalFormat extends 
TemplateTemporalFormat {
             throws InvalidFormatParametersException {
         temporalClass = 
_CoreTemporalUtils.normalizeSupportedTemporalClass(temporalClass);
 
-        Matcher formatStylePatternMatcher = 
FORMAT_STYLE_PATTERN.matcher(formatString);
+        final Matcher formatStylePatternMatcher = 
FORMAT_STYLE_PATTERN.matcher(formatString);
         final boolean isFormatStyleString = 
formatStylePatternMatcher.matches();
-        FormatStyle timePartFormatStyle;
+        FormatStyle timePartFormatStyle; // Maybe changes later for re-attempts
+        final FormatStyle datePartFormatStyle;
 
-        DateTimeFormatter dateTimeFormatter;
+        DateTimeFormatter dateTimeFormatter; // Maybe changes later for 
re-attempts
         if (isFormatStyleString) {
-            FormatStyle datePartFormatStyle;
+            // Set datePartFormatStyle, and timePartFormatStyle; both will be 
non-null
             {
                 String group1 = formatStylePatternMatcher.group(1);
                 datePartFormatStyle = group1 != null
@@ -106,7 +111,9 @@ class JavaTemplateTemporalFormat extends 
TemplateTemporalFormat {
                         + temporalClass.getName() + " values.");
             }
         } else {
+            datePartFormatStyle = null;
             timePartFormatStyle = null;
+
             try {
                 dateTimeFormatter = DateTimeFormatter.ofPattern(formatString);
             } catch (IllegalArgumentException e) {
@@ -114,8 +121,37 @@ class JavaTemplateTemporalFormat extends 
TemplateTemporalFormat {
             }
         }
 
+        // Handling of time zone related edge cases
         if (isLocalTemporalClass(temporalClass)) {
             this.preFormatValueConversion = null;
+            if (isFormatStyleString && (temporalClass == LocalTime.class || 
temporalClass == LocalDateTime.class)) {
+                // The localized pattern possibly contains the time zone (for 
most locales, LONG and FULL does), so they
+                // fail with local temporals that have a time part. To work 
this issue around, we decrease the verbosity
+                // of the time style until formatting succeeds. (See also: 
JDK-8085887)
+                localFormatAttempt: while (true) {
+                    try {
+                        dateTimeFormatter.format(LOCAL_DATE_TIME_SAMPLE); // 
We only care if it throws exception
+                        break localFormatAttempt; // It worked
+                    } catch (DateTimeException e) {
+                        timePartFormatStyle = 
getLessVerboseStyle(timePartFormatStyle);
+                        if (timePartFormatStyle == null) {
+                            throw e;
+                        }
+
+                        String timePartFormatString = 
timePartFormatStyle.name().toLowerCase(Locale.ROOT);
+                        if (temporalClass == LocalDateTime.class) {
+                            dateTimeFormatter = 
DateTimeFormatter.ofLocalizedDateTime(
+                                    datePartFormatStyle, timePartFormatStyle);
+                            formatString = datePartFormatStyle == 
timePartFormatStyle
+                                    ? timePartFormatString
+                                    : 
datePartFormatStyle.name().toLowerCase(Locale.ROOT) + "_" + 
timePartFormatString;
+                        } else {
+                            dateTimeFormatter = 
DateTimeFormatter.ofLocalizedTime(timePartFormatStyle);
+                            formatString = timePartFormatString;
+                        }
+                    }
+                }
+            }
         } else {
             PreFormatValueConversion preFormatValueConversion;
             nonLocalFormatAttempt: while (true) {
@@ -159,19 +195,6 @@ class JavaTemplateTemporalFormat extends 
TemplateTemporalFormat {
         this.zoneId = timeZone.toZoneId();
     }
 
-    private static FormatStyle getMoreVerboseStyle(FormatStyle style) {
-        switch (style) {
-            case SHORT:
-                return FormatStyle.MEDIUM;
-            case MEDIUM:
-                return FormatStyle.LONG;
-            case LONG:
-                return FormatStyle.FULL;
-            default:
-                return null;
-        }
-    }
-
     @Override
     public String formatToPlainText(TemplateTemporalModel tm) throws 
TemplateValueFormatException, TemplateModelException {
         DateTimeFormatter dateTimeFormatter = this.dateTimeFormatter;
@@ -256,4 +279,30 @@ class JavaTemplateTemporalFormat extends 
TemplateTemporalFormat {
                 || normalizedTemporalClass == YearMonth.class;
     }
 
+    private static FormatStyle getMoreVerboseStyle(FormatStyle style) {
+        switch (style) {
+            case SHORT:
+                return FormatStyle.MEDIUM;
+            case MEDIUM:
+                return FormatStyle.LONG;
+            case LONG:
+                return FormatStyle.FULL;
+            default:
+                return null;
+        }
+    }
+
+    private static FormatStyle getLessVerboseStyle(FormatStyle style) {
+        switch (style) {
+            case FULL:
+                return FormatStyle.LONG;
+            case LONG:
+                return FormatStyle.MEDIUM;
+            case MEDIUM:
+                return FormatStyle.SHORT;
+            default:
+                return null;
+        }
+    }
+
 }
diff --git a/src/main/java/freemarker/core/_Java8.java 
b/src/main/java/freemarker/core/_Java8.java
deleted file mode 100644
index 34979da..0000000
--- a/src/main/java/freemarker/core/_Java8.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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 freemarker.core;
-
-import java.lang.reflect.Method;
-
-/**
- * Used internally only, might changes without notice!
- * Used for accessing functionality that's only present in Java 8 or later.
- */
-public interface _Java8 {
-
-    /**
-     * Returns if it's a Java 8 "default method".
-     */
-    boolean isDefaultMethod(Method method);
-    
-}
diff --git 
a/src/test/resources/freemarker/test/templatesuite/templates/temporal.ftl 
b/src/test/resources/freemarker/test/templatesuite/templates/temporal.ftl
index 79346e8..05991b9 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/temporal.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/temporal.ftl
@@ -77,9 +77,9 @@
 
 <@assertEquals expected="05/04/03 06:07" actual=localDateTime?string.short />
 <@assertEquals expected="5 avr. 2003 06:07:08" 
actual=localDateTime?string.medium />
-<#-- These fail on Java 8 because of JDK-8085887
-<@assertEquals expected="5 avril 2003 06:07:08 ET" 
actual=localDateTime?string.long />
-<@assertEquals expected="samedi 5 avril 2003 06 h 07 ET" 
actual=localDateTime?string.full />
+<#-- Java 8 JDK-8085887 workaround test: -->
+<@assertEquals expected="5 avril 2003 06:07:08" 
actual=localDateTime?string.long />
+<@assertEquals expected="samedi 5 avril 2003 06:07:08" 
actual=localDateTime?string.full />
 
 <#-- Automatic short->medium->long step up: -->
 <@assertEquals expected="06:07:08 Z" actual=offsetTime?string.short />
@@ -87,7 +87,6 @@
 <@assertEquals expected="06:07:08 Z" actual=offsetTime?string.medium />
 <@assertEquals expected="06:07:08 Z" actual=offsetTime?string.long />
 <@assertEquals expected="06 h 07 Z" actual=offsetTime?string.full />
--->
 
 <#-- There combinations are clearly not supported by the "localized pattern" 
API. -->
 <@assertFails message="not supported for 
java.time.Year">${year?string.short}</@>
@@ -106,23 +105,20 @@
 
 <@assertEquals expected="05/04/03 06:07" 
actual=localDateTime?string.short_short />
 <@assertEquals expected="05/04/03 06:07:08" 
actual=localDateTime?string.short_medium />
-<#-- These fail on Java 8 because of JDK-8085887
-<@assertEquals expected="05/04/03 06:07:08 ET" 
actual=localDateTime?string.short_long />
-<@assertEquals expected="05/04/03 06 h 07 ET" 
actual=localDateTime?string.short_full />
--->
+<#-- Java 8 JDK-8085887 workaround test: -->
+<@assertEquals expected="05/04/03 06:07:08" 
actual=localDateTime?string.short_long />
+<@assertEquals expected="05/04/03 06:07:08" 
actual=localDateTime?string.short_full />
 
 <@assertEquals expected="5 avr. 2003 06:07:08" 
actual=localDateTime?string.medium_medium />
 <@assertEquals expected="5 avril 2003 06:07:08" 
actual=localDateTime?string.long_medium />
 <@assertEquals expected="samedi 5 avril 2003 06:07:08" 
actual=localDateTime?string.full_medium />
 
-<#-- These fail on Java 8 because of JDK-8085887
-<@assertEquals expected="5 avril 2003 06:07:08 ET" 
actual=localDateTime?string.long_long />
-<@assertEquals expected="samedi 5 avril 2003 06:07:08 ET" 
actual=localDateTime?string.full_long />
--->
+<#-- Java 8 JDK-8085887 workaround test: -->
+<@assertEquals expected="5 avril 2003 06:07:08" 
actual=localDateTime?string.long_long />
+<@assertEquals expected="samedi 5 avril 2003 06:07:08" 
actual=localDateTime?string.full_long />
 
-<#-- These fail on Java 8 because of JDK-8085887
-<@assertEquals expected="samedi 5 avril 2003 06 h 07 ET" 
actual=localDateTime?string.full_full />
--->
+<#-- Java 8 JDK-8085887 workaround test: -->
+<@assertEquals expected="samedi 5 avril 2003 06:07:08" 
actual=localDateTime?string.full_full />
 
 <@assertEquals expected="2003-04-05" actual=localDateTime?string('yyyy-MM-dd') 
/>
 <@assertEquals expected="2003-04-05 06:07:08" 
actual=localDateTime?string('yyyy-MM-dd HH:mm:ss') />

Reply via email to