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') />