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

commit 6476732d962c888212e5c2084d178d1a46e329c9
Author: ddekany <[email protected]>
AuthorDate: Wed Dec 15 18:10:25 2021 +0100

    [FREEMARKER-35] Added automatic adjustment of the format style for 
OffsetTime, if the time zone has DST, and the style doesn't show the offset.
---
 .../core/JavaTemplateTemporalFormat.java           | 101 ++++++++++++++-------
 .../test/templatesuite/templates/temporal.ftl      |   2 +-
 2 files changed, 68 insertions(+), 35 deletions(-)

diff --git a/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java 
b/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
index f07fbcc..1d232d3 100644
--- a/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
@@ -50,7 +50,7 @@ import freemarker.template.utility.StringUtil;
  */
 class JavaTemplateTemporalFormat extends TemplateTemporalFormat {
 
-    enum FormatTimeConversion {
+    enum PreFormatValueConversion {
         INSTANT_TO_ZONED_DATE_TIME,
         SET_ZONE_FROM_OFFSET,
         CONVERT_TO_CURRENT_ZONE
@@ -69,27 +69,30 @@ class JavaTemplateTemporalFormat extends 
TemplateTemporalFormat {
     private final DateTimeFormatter dateTimeFormatter;
     private final ZoneId zoneId;
     private final String formatString;
-    private final FormatTimeConversion formatTimeConversion;
+    private final PreFormatValueConversion preFormatValueConversion;
 
     JavaTemplateTemporalFormat(String formatString, Class<? extends Temporal> 
temporalClass, Locale locale, TimeZone timeZone)
             throws InvalidFormatParametersException {
-        this.formatString = formatString;
-
         temporalClass = 
_CoreTemporalUtils.normalizeSupportedTemporalClass(temporalClass);
 
         Matcher formatStylePatternMatcher = 
FORMAT_STYLE_PATTERN.matcher(formatString);
-        boolean isFormatStyleString = formatStylePatternMatcher.matches();
+        final boolean isFormatStyleString = 
formatStylePatternMatcher.matches();
+        FormatStyle timePartFormatStyle;
 
         DateTimeFormatter dateTimeFormatter;
         if (isFormatStyleString) {
-            String group1 = formatStylePatternMatcher.group(1);
-            FormatStyle datePartFormatStyle = group1 != null
-                    ? FormatStyle.valueOf(group1.toUpperCase(Locale.ROOT))
-                    : FormatStyle.MEDIUM;
-            String group2 = formatStylePatternMatcher.group(2);
-            FormatStyle timePartFormatStyle = group2 != null
-                    ? FormatStyle.valueOf(group2.toUpperCase(Locale.ROOT))
-                    : datePartFormatStyle;
+            FormatStyle datePartFormatStyle;
+            {
+                String group1 = formatStylePatternMatcher.group(1);
+                datePartFormatStyle = group1 != null
+                        ? FormatStyle.valueOf(group1.toUpperCase(Locale.ROOT))
+                        : FormatStyle.MEDIUM;
+                String group2 = formatStylePatternMatcher.group(2);
+                timePartFormatStyle = group2 != null
+                        ? FormatStyle.valueOf(group2.toUpperCase(Locale.ROOT))
+                        : datePartFormatStyle;
+            }
+
             if (temporalClass == LocalDateTime.class || temporalClass == 
ZonedDateTime.class
                     || temporalClass == OffsetDateTime.class || temporalClass 
== Instant.class) {
                 dateTimeFormatter = 
DateTimeFormatter.ofLocalizedDateTime(datePartFormatStyle, timePartFormatStyle);
@@ -103,6 +106,7 @@ class JavaTemplateTemporalFormat extends 
TemplateTemporalFormat {
                         + temporalClass.getName() + " values.");
             }
         } else {
+            timePartFormatStyle = null;
             try {
                 dateTimeFormatter = DateTimeFormatter.ofPattern(formatString);
             } catch (IllegalArgumentException e) {
@@ -112,40 +116,69 @@ class JavaTemplateTemporalFormat extends 
TemplateTemporalFormat {
         this.dateTimeFormatter = dateTimeFormatter.withLocale(locale);
 
         if (isLocalTemporalClass(temporalClass)) {
-            this.formatTimeConversion = null;
+            this.preFormatValueConversion = null;
         } else {
-            if (showsZone(dateTimeFormatter)) {
-                if (temporalClass == Instant.class) {
-                    this.formatTimeConversion = 
FormatTimeConversion.INSTANT_TO_ZONED_DATE_TIME;
-                } else if (isFormatStyleString &&
-                        (temporalClass == OffsetDateTime.class || 
temporalClass == OffsetTime.class)) {
-                    this.formatTimeConversion = 
FormatTimeConversion.SET_ZONE_FROM_OFFSET;
+            PreFormatValueConversion preFormatValueConversion;
+            nonLocalFormatAttempt: do {
+                if (showsZone(dateTimeFormatter)) {
+                    if (temporalClass == Instant.class) {
+                        preFormatValueConversion = 
PreFormatValueConversion.INSTANT_TO_ZONED_DATE_TIME;
+                    } else if (isFormatStyleString &&
+                            (temporalClass == OffsetDateTime.class || 
temporalClass == OffsetTime.class)) {
+                        preFormatValueConversion = 
PreFormatValueConversion.SET_ZONE_FROM_OFFSET;
+                    } else {
+                        preFormatValueConversion = null;
+                    }
                 } else {
-                    this.formatTimeConversion = null;
-                }
-            } else {
-                if (temporalClass == OffsetTime.class && 
timeZone.useDaylightTime()) {
-                    throw new InvalidFormatParametersException(
-                            "The format must show the time offset, as the 
current FreeMarker time zone, "
-                                    + StringUtil.jQuote(timeZone.getID()) + ", 
may uses Daylight Saving Time, and thus "
-                                    + "it's not possible to convert the value 
to the local time in that zone, "
-                                    + "since we don't know the day.");
+                    if (temporalClass == OffsetTime.class && 
timeZone.useDaylightTime()) {
+                        if (isFormatStyleString) {
+                            // To find the closest style that already shows 
the offset
+                            timePartFormatStyle = 
getMoreVerboseStyle(timePartFormatStyle);
+                        }
+                        if (timePartFormatStyle == null) {
+                            throw new InvalidFormatParametersException(
+                                    "The format must show the time offset, as 
the current FreeMarker time zone, "
+                                            + 
StringUtil.jQuote(timeZone.getID()) +
+                                            ", may uses Daylight Saving Time, 
and thus "
+                                            + "it's not possible to convert 
the value to the local time in that zone, "
+                                            + "since we don't know the day.");
+                        }
+                        formatString = 
timePartFormatStyle.name().toLowerCase(Locale.ROOT);
+                        preFormatValueConversion = null; // Avoid false alarm 
"might not have been initialized"
+                        continue nonLocalFormatAttempt;
+                    } else {
+                        preFormatValueConversion = 
PreFormatValueConversion.CONVERT_TO_CURRENT_ZONE;
+                    }
                 }
-                this.formatTimeConversion = 
FormatTimeConversion.CONVERT_TO_CURRENT_ZONE;
-            }
+            } while (false);
+            this.preFormatValueConversion = preFormatValueConversion;
         }
 
+        this.formatString = formatString;
         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;
         Temporal temporal = TemplateFormatUtil.getNonNullTemporal(tm);
 
-        if (formatTimeConversion == 
FormatTimeConversion.INSTANT_TO_ZONED_DATE_TIME) {
+        if (preFormatValueConversion == 
PreFormatValueConversion.INSTANT_TO_ZONED_DATE_TIME) {
             temporal = ((Instant) temporal).atZone(zoneId);
-        } else if (formatTimeConversion == 
FormatTimeConversion.CONVERT_TO_CURRENT_ZONE) {
+        } else if (preFormatValueConversion == 
PreFormatValueConversion.CONVERT_TO_CURRENT_ZONE) {
             if (temporal instanceof Instant) {
                 temporal = ((Instant) temporal).atZone(zoneId);
             } else if (temporal instanceof OffsetDateTime) {
@@ -161,7 +194,7 @@ class JavaTemplateTemporalFormat extends 
TemplateTemporalFormat {
                                 + "FreeMarker time zone, " + 
StringUtil.jQuote(zoneId.getId()) + ", which is "
                                 + "needed to format with " + 
StringUtil.jQuote(formatString) + ".");
             }
-        } else if (formatTimeConversion == 
FormatTimeConversion.SET_ZONE_FROM_OFFSET) {
+        } else if (preFormatValueConversion == 
PreFormatValueConversion.SET_ZONE_FROM_OFFSET) {
             // Formats like "long" want a time zone field, but oddly, they 
don't treat the zoneOffset as such.
             if (temporal instanceof OffsetDateTime) {
                 OffsetDateTime offsetDateTime = (OffsetDateTime) temporal;
diff --git 
a/src/test/resources/freemarker/test/templatesuite/templates/temporal.ftl 
b/src/test/resources/freemarker/test/templatesuite/templates/temporal.ftl
index db30dcf..b850552 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/temporal.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/temporal.ftl
@@ -22,7 +22,7 @@
 <@assertEquals expected="Apr 5, 2003" actual=localDate?string />
 <@assertEquals expected="6:07:08 AM" actual=localTime?string />
 <@assertEquals expected="Apr 5, 2003 7:07:08 AM" actual=offsetDateTime?string 
/>
-<@assertEquals expected="6:07:08 AM Z" actual=offsetTime?string />
+<@assertEquals expected="7:07:08 AM" actual=offsetTime?string />
 <@assertEquals expected="2003" actual=year?string />
 <@assertEquals expected="2003-04" actual=yearMonth?string />
 <@assertEquals expected="Apr 5, 2003 7:07:08 AM" actual=zonedDateTime?string />

Reply via email to