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 6f03463 [FREEMARKER-35] Improved ISO (and XS) formatters, added more
tests for them. Some code cleanup.
6f03463 is described below
commit 6f03463696e604fda66dab80d7900d10eb59f4fa
Author: ddekany <[email protected]>
AuthorDate: Sun Feb 13 23:12:31 2022 +0100
[FREEMARKER-35] Improved ISO (and XS) formatters, added more tests for
them. Some code cleanup.
---
.../core/AliasTemplateDateFormatFactory.java | 11 +-
.../ISOLikeTemplateTemporalTemporalFormat.java | 154 ++++++++--
.../core/JavaTemplateTemporalFormat.java | 12 +-
.../freemarker/core/TemplateDateFormatFactory.java | 2 +-
.../freemarker/core/TemplateTemporalFormat.java | 19 +-
.../core/TemplateTemporalFormatFactory.java | 11 +-
.../core/TemplateValueFormatFactory.java | 6 +-
.../freemarker/template/utility/TemporalUtils.java | 23 +-
.../core/AbstractTemporalFormatTest.java | 38 ++-
.../core/TemporalFormatWithIsoFormatTest.java | 320 ++++++++++++++++-----
.../core/TemporalFormatWithJavaFormatTest.java | 13 +-
11 files changed, 476 insertions(+), 133 deletions(-)
diff --git a/src/main/java/freemarker/core/AliasTemplateDateFormatFactory.java
b/src/main/java/freemarker/core/AliasTemplateDateFormatFactory.java
index 4dec545..4bb788f 100644
--- a/src/main/java/freemarker/core/AliasTemplateDateFormatFactory.java
+++ b/src/main/java/freemarker/core/AliasTemplateDateFormatFactory.java
@@ -22,11 +22,16 @@ import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
+import freemarker.template.Configuration;
import freemarker.template.utility.StringUtil;
/**
- * Creates an alias to another format, so that the format can be referred to
with a simple name in the template, rather
- * than as a concrete pattern or other kind of format string.
+ * Creates an alias to another format that's given with a {@link String}, so
that the format can be referred with a
+ * simple name in the template, rather than with a concrete pattern or other
kind of format string. Internally, this
+ * will call {@link Environment#getTemplateDateFormat(String, int, Locale,
TimeZone, boolean)} to resolve the other
+ * format.
+ *
+ * @see Configuration#customDateFormats
*
* @since 2.3.24
*/
@@ -51,7 +56,7 @@ public final class AliasTemplateDateFormatFactory extends
TemplateDateFormatFact
* @param localizedTargetFormatStrings
* Maps {@link Locale}-s to format strings. If the desired
locale doesn't occur in the map, a less
* specific locale is tried, repeatedly until only the language
part remains. For example, if locale is
- * {@code new Locale("en", "US", "Linux")}, then these keys
will be attempted untol a match is found, in
+ * {@code new Locale("en", "US", "Linux")}, then these keys
will be attempted until a match is found, in
* this order: {@code new Locale("en", "US", "Linux")}, {@code
new Locale("en", "US")},
* {@code new Locale("en")}. If there's still no matching key,
the value of the
* {@code targetFormatString} will be used.
diff --git
a/src/main/java/freemarker/core/ISOLikeTemplateTemporalTemporalFormat.java
b/src/main/java/freemarker/core/ISOLikeTemplateTemporalTemporalFormat.java
index 482b257..2923940 100644
--- a/src/main/java/freemarker/core/ISOLikeTemplateTemporalTemporalFormat.java
+++ b/src/main/java/freemarker/core/ISOLikeTemplateTemporalTemporalFormat.java
@@ -20,6 +20,7 @@
package freemarker.core;
import static freemarker.template.utility.StringUtil.*;
+import static freemarker.template.utility.TemporalUtils.*;
import java.time.DateTimeException;
import java.time.Instant;
@@ -30,10 +31,13 @@ import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQuery;
import java.util.TimeZone;
+import java.util.regex.Pattern;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateTemporalModel;
@@ -51,23 +55,26 @@ final class ISOLikeTemplateTemporalTemporalFormat extends
TemplateTemporalFormat
private final boolean instantConversion;
private final ZoneId zoneId;
private final String description;
- private final TemporalQuery temporalQuery;
+ private final TemporalQuery<? extends Temporal> temporalQuery;
private final Class<? extends Temporal> temporalClass;
private final DateTimeFormatter parserExtendedDateTimeFormatter;
private final DateTimeFormatter parserBasicDateTimeFormatter;
+ private final boolean localTemporalClass;
ISOLikeTemplateTemporalTemporalFormat(
DateTimeFormatter dateTimeFormatter,
DateTimeFormatter parserExtendedDateTimeFormatter,
DateTimeFormatter parserBasicDateTimeFormatter,
Class<? extends Temporal> temporalClass, TimeZone zone, String
formatString) {
+ temporalClass = normalizeSupportedTemporalClass(temporalClass);
this.dateTimeFormatter = dateTimeFormatter;
this.parserExtendedDateTimeFormatter = parserExtendedDateTimeFormatter;
this.parserBasicDateTimeFormatter = parserBasicDateTimeFormatter;
this.temporalQuery = TemporalUtils.getTemporalQuery(temporalClass);
- this.instantConversion = Instant.class.isAssignableFrom(temporalClass);
+ this.instantConversion = temporalClass == Instant.class;
this.temporalClass = temporalClass;
- this.zoneId = zone.toZoneId();
+ this.localTemporalClass = isLocalTemporalClass(temporalClass);
+ this.zoneId = temporalClass == Instant.class ? zone.toZoneId() : null;
this.description = formatString;
}
@@ -83,17 +90,75 @@ final class ISOLikeTemplateTemporalTemporalFormat extends
TemplateTemporalFormat
try {
return dateTimeFormatter.format(temporal);
} catch (DateTimeException e) {
- throw new UnformattableValueException(e.getMessage(), e);
+ throw new UnformattableValueException(
+ "Failed to format temporal " + temporal + ". Reason: " +
e.getMessage(),
+ e);
}
}
@Override
public Object parse(String s) throws TemplateValueFormatException {
- DateTimeFormatter parserDateTimeFormatter =
parserBasicDateTimeFormatter == null || isExtendedFormatString(s)
+ final boolean extendedFormat;
+ final boolean add1Day;
+ if (temporalClass == LocalDate.class || temporalClass ==
YearMonth.class) {
+ extendedFormat = s.indexOf('-', 1) != -1;
+ add1Day = false;
+ } else if (temporalClass == LocalTime.class || temporalClass ==
OffsetTime.class) {
+ extendedFormat = s.indexOf(":") != -1;
+ add1Day = false;
+ // ISO 8601 allows hour 24 if the rest of the time is 0:
+ if (isStartOf240000(s, 0)) {
+ s = "00" + s.substring(2);
+ }
+ } else if (temporalClass == Year.class) {
+ extendedFormat = false;
+ add1Day = false;
+ } else {
+ int tIndex = s.indexOf('T');
+ if (tIndex < 1) {
+ throw new UnparsableValueException(
+ "Failed to parse value " + jQuote(s) + " with format "
+ jQuote(description)
+ + ", and target class " +
temporalClass.getSimpleName() + ": "
+ + "Character \"T\" must be used to separate
the date and time part.");
+ }
+ if (s.indexOf(":", tIndex + 1) != -1) {
+ extendedFormat = true;
+ } else {
+ // Note: false for: -5000101T00, as there the last '-' has
index 0
+ extendedFormat = s.lastIndexOf('-', tIndex - 1) > 0;
+ }
+ // ISO 8601 allows hour 24 if the rest of the time is 0:
+ if (isStartOf240000(s, tIndex + 1)) {
+ s = s.substring(0, tIndex + 1) + "00" + s.substring(tIndex +
3);
+ add1Day = true;
+ } else {
+ add1Day = false;
+ }
+ }
+
+ DateTimeFormatter parserDateTimeFormatter =
parserBasicDateTimeFormatter == null || extendedFormat
? parserExtendedDateTimeFormatter :
parserBasicDateTimeFormatter;
try {
- return parserDateTimeFormatter.parse(s, temporalQuery);
- } catch (DateTimeParseException e) {
+ TemporalAccessor parseResult = parserDateTimeFormatter.parse(s);
+ if (!localTemporalClass &&
!parseResult.isSupported(ChronoField.OFFSET_SECONDS)) {
+ // Unlike for the Java format, for ISO we require the string
to contain the offset for a non-local
+ // target type. We could use the default time zone, but that's
really just guessing, also DST creates
+ // ambiguous cases. For the Java formatter we are lenient, as
the shared date-time format typically
+ // misses the offset, and because we don't want a
format-and-then-parse cycle to fail. But in ISO
+ // format, the offset is always shown for a non-local temporal.
+ throw new UnparsableValueException(
+ "Failed to parse value " + jQuote(s) + " with format "
+ jQuote(description)
+ + ", and target class " +
temporalClass.getSimpleName() + ": "
+ + "The string must contain the time zone
offset for this target class. "
+ + "(Defaulting to the current time zone is not
allowed for ISO-style formats.)");
+
+ }
+ Temporal resultTemporal = parseResult.query(temporalQuery);
+ if (add1Day) {
+ resultTemporal = resultTemporal.plus(1, ChronoUnit.DAYS);
+ }
+ return resultTemporal;
+ } catch (DateTimeException e) {
throw new UnparsableValueException(
"Failed to parse value " + jQuote(s) + " with format " +
jQuote(description)
+ ", and target class " +
temporalClass.getSimpleName() + ", "
@@ -104,31 +169,66 @@ final class ISOLikeTemplateTemporalTemporalFormat extends
TemplateTemporalFormat
}
}
- private boolean isExtendedFormatString(String s) throws
UnparsableValueException {
- if (temporalClass == LocalDate.class || temporalClass ==
YearMonth.class) {
- return !s.isEmpty() && s.indexOf('-', 1) != -1;
- } else if (temporalClass == LocalTime.class || temporalClass ==
OffsetTime.class) {
- return s.indexOf(":") != -1;
- } else if (temporalClass == Year.class) {
+ private final static Pattern ZERO_TIME_AFTER_HH =
Pattern.compile("(?::?+00(?::?+00(?:.?+0+)?)?)?");
+
+ private static boolean isStartOf240000(String s, int from) {
+ if (from + 1 >= s.length() || s.charAt(from) != '2' || s.charAt(from +
1) != '4') {
return false;
- } else {
- int tIndex = s.indexOf('T');
- if (tIndex < 1) {
- throw new UnparsableValueException(
- "Failed to parse value " + jQuote(s) + " with format "
+ jQuote(description)
- + ", and target class " +
temporalClass.getSimpleName() + ": "
- + "Character \"T\" must be used to separate
the date and time part.");
+ }
+
+ int index = from + 2;
+
+ int indexAfterHH = index;
+ // Seek for time zone start or end of string
+ while (index < s.length()) {
+ char c = s.charAt(index);
+ boolean cIsDigit = c >= '0' && c <= '9';
+ if (!(cIsDigit || c == ':' || c == '.')) {
+ break;
}
- if (s.indexOf(":", tIndex + 1) != -1) {
- return true;
+ if (cIsDigit && c != '0') {
+ return false;
}
- // Note: false for: -5000101T00, as there the last '-' has index 0
- return s.lastIndexOf('-', tIndex - 1) > 0;
+
+ index++;
}
+
+ String timeAfterHH = s.substring(indexAfterHH, index);
+ return ZERO_TIME_AFTER_HH.matcher(timeAfterHH).matches();
}
- private boolean temporalClassHasNoTimePart() {
- return temporalClass == LocalDate.class || temporalClass == Year.class
|| temporalClass == YearMonth.class;
+ //!!T
+ public static void main(String[] args) {
+ for (String original : new String[] {"24", "24:00", "24:00:00",
"24:00:00.0"}) {
+ for (boolean basic : new boolean[] {false, true}) {
+ for (String prefix : new String[] {"", "T"}) {
+ for (String suffix : new String[] {"", "Z", "-01", "+01"})
{
+ String s = prefix + (basic ? original.replace(":", "")
: original)+ suffix;
+
+ int startIndex = s.indexOf("24");
+ if (!isStartOf240000(s, startIndex)) {
+ throw new AssertionError("Couldn't find end of
time part in: " + s);
+ }
+ }
+ }
+ }
+ }
+
+ for (String original : new String[] {
+ "24:", "24:01", "24:00:01", "24:00:00.1", "24:0", "24:00:x",
+ "2401", "240001", "240000.1", "240"}) {
+ for (String prefix : new String[] {"", "T"}) {
+ for (String suffix : new String[] {"", "Z", "-01", "+01"}) {
+ String s = prefix + original + suffix;
+
+ int startIndex = s.indexOf("24");
+ if (isStartOf240000(s, startIndex)) {
+ throw new AssertionError("Shouldn't match: " + s);
+ }
+ }
+ }
+ }
+
}
@Override
diff --git a/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
b/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
index 64e2cfd..85311f6 100644
--- a/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
@@ -27,8 +27,6 @@ import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
-import java.time.Year;
-import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
@@ -136,7 +134,7 @@ class JavaTemplateTemporalFormat extends
TemplateTemporalFormat {
}
// Handling of time zone related edge cases
- if (isLocalTemporalClass(temporalClass)) {
+ if (TemporalUtils.isLocalTemporalClass(temporalClass)) {
this.preFormatValueConversion = null;
this.specialParsing = null;
formatWithZone = false;
@@ -316,14 +314,6 @@ class JavaTemplateTemporalFormat extends
TemplateTemporalFormat {
.equals(dateTimeFormatter.format(SHOWS_ZONE_SAMPLE_TEMPORAL_2));
}
- private static boolean isLocalTemporalClass(Class<? extends Temporal>
normalizedTemporalClass) {
- return normalizedTemporalClass == LocalDateTime.class
- || normalizedTemporalClass == LocalTime.class
- || normalizedTemporalClass == LocalDate.class
- || normalizedTemporalClass == Year.class
- || normalizedTemporalClass == YearMonth.class;
- }
-
private static FormatStyle getMoreVerboseStyle(FormatStyle style) {
switch (style) {
case SHORT:
diff --git a/src/main/java/freemarker/core/TemplateDateFormatFactory.java
b/src/main/java/freemarker/core/TemplateDateFormatFactory.java
index 81faa19..63af11b 100644
--- a/src/main/java/freemarker/core/TemplateDateFormatFactory.java
+++ b/src/main/java/freemarker/core/TemplateDateFormatFactory.java
@@ -70,7 +70,7 @@ public abstract class TemplateDateFormatFactory extends
TemplateValueFormatFacto
* <p>
* As of FreeMarker 2.3.21, this is {@code true} exactly when
the date is an SQL "date without time of
* the day" (i.e., a {@link java.sql.Date java.sql.Date}) or an
SQL "time of the day" value (i.e., a
- * {@link java.sql.Time java.sql.Time}, although this rule can
change in future, depending on
+ * {@link java.sql.Time java.sql.Time}, although this rule can
change in the future, depending on
* configuration settings and such, so you shouldn't rely on
this rule, just accept what this parameter
* says.
* @param env
diff --git a/src/main/java/freemarker/core/TemplateTemporalFormat.java
b/src/main/java/freemarker/core/TemplateTemporalFormat.java
index aeaffd9..75eede4 100644
--- a/src/main/java/freemarker/core/TemplateTemporalFormat.java
+++ b/src/main/java/freemarker/core/TemplateTemporalFormat.java
@@ -25,24 +25,27 @@ import freemarker.template.TemplateModelException;
import freemarker.template.TemplateTemporalModel;
/**
- * Represents a {@link Temporal} format; used in templates for formatting and
parsing with that format. This is
- * similar to Java's {@link DateTimeFormatter}, but made to fit the
requirements of FreeMarker. Also, it makes easier to
- * define formats that can't be represented with {@link DateTimeFormatter}.
+ * Represents a {@link Temporal} format; used in templates for formatting
{@link Temporal}-s, and parsing strings to
+ * {@link Temporal}-s. This is similar to Java's {@link DateTimeFormatter},
but made to fit the requirements of
+ * FreeMarker. Also, it makes it possible to define formats that can't be
represented with {@link DateTimeFormatter}.
+ *
+ * <p>{@link TemplateTemporalFormat} instances are usually created by a {@link
TemplateTemporalFormatFactory}.
*
* <p>
* Implementations need not be thread-safe if the {@link
TemplateTemporalFormatFactory} doesn't recycle them among
* different {@link Environment}-s. The code outside the {@link
TemplateTemporalFormatFactory} will not try to reuse
- * {@link TemplateTemporalFormat} instances in multiple {@link Environment}-s,
and {@link Environment}-s are
- * thread-local objects.
+ * {@link TemplateTemporalFormat} instances in multiple {@link Environment}-s,
and an {@link Environment} is only used
+ * in a single thread.
*
* @since 2.3.32
*/
public abstract class TemplateTemporalFormat extends TemplateValueFormat {
- public abstract String formatToPlainText(TemplateTemporalModel
temporalModel) throws TemplateValueFormatException, TemplateModelException;
+ public abstract String formatToPlainText(TemplateTemporalModel
temporalModel)
+ throws TemplateValueFormatException, TemplateModelException;
/**
- * Formats the model to markup instead of to plain text if the result
markup will be more than just plain text
+ * Formats the model to markup instead of to plain text, if the result
markup will be more than just plain text
* escaped, otherwise falls back to formatting to plain text. If the
markup result would be just the result of
* {@link #formatToPlainText(TemplateTemporalModel)} escaped, it must
return the {@link String} that
* {@link #formatToPlainText(TemplateTemporalModel)} does.
@@ -68,7 +71,7 @@ public abstract class TemplateTemporalFormat extends
TemplateValueFormat {
public abstract boolean isTimeZoneBound();
/**
- * Parsers a string to a {@link Temporal}, according to this format. Some
format implementations may throw
+ * Parser a string to a {@link Temporal}, according to this format. Some
format implementations may throw
* {@link ParsingNotSupportedException} here.
*
* @param s
diff --git a/src/main/java/freemarker/core/TemplateTemporalFormatFactory.java
b/src/main/java/freemarker/core/TemplateTemporalFormatFactory.java
index e6cc58f..3e8c8c6 100644
--- a/src/main/java/freemarker/core/TemplateTemporalFormatFactory.java
+++ b/src/main/java/freemarker/core/TemplateTemporalFormatFactory.java
@@ -22,11 +22,9 @@ import java.time.temporal.Temporal;
import java.util.Locale;
import java.util.TimeZone;
-import freemarker.template.Configuration;
-
/**
- * Factory for a certain kind of {@link Temporal} formatting ({@link
TemplateTemporalFormat}). Usually a singleton
- * (one-per-VM, or one-per-{@link Configuration}), and so must be thread-safe.
+ * Factory for a certain kind of {@link Temporal} formatting ({@link
TemplateTemporalFormat}).
+ * See more at {@link TemplateValueFormatFactory}.
*
* @see Configurable#setCustomTemporalFormats(java.util.Map)
*
@@ -62,7 +60,7 @@ public abstract class TemplateTemporalFormatFactory extends
TemplateValueFormatF
* The runtime environment from which the formatting was
called. This is mostly meant to be used for
* {@link Environment#setCustomState(Object, Object)}/{@link
Environment#getCustomState(Object)}. The
* result shouldn't depend on setting values in the {@link
Environment}, because changing settings
- * will not necessarily invalidate the result.
+ * will not necessarily invalidate the returned {@link
TemplateTemporalFormat}.
*
* @throws TemplateValueFormatException
* If any problem occurs while parsing/getting the format.
Notable subclasses:
@@ -71,8 +69,7 @@ public abstract class TemplateTemporalFormatFactory extends
TemplateValueFormatF
* not supported by this factory.
*/
public abstract TemplateTemporalFormat get(
- String params,
- Class<? extends Temporal> temporalClass, Locale locale, TimeZone
timeZone, Environment env)
+ String params, Class<? extends Temporal> temporalClass, Locale
locale, TimeZone timeZone, Environment env)
throws TemplateValueFormatException;
}
diff --git a/src/main/java/freemarker/core/TemplateValueFormatFactory.java
b/src/main/java/freemarker/core/TemplateValueFormatFactory.java
index 78a2ae7..a454e77 100644
--- a/src/main/java/freemarker/core/TemplateValueFormatFactory.java
+++ b/src/main/java/freemarker/core/TemplateValueFormatFactory.java
@@ -18,8 +18,12 @@
*/
package freemarker.core;
+import freemarker.template.Configuration;
+
/**
- * Superclass of all format factories.
+ * Superclass of all format factories. A format factory is an object that
creates instances of a certain kind of
+ * {@link TemplateValueFormat}. Generally, they are singletons (one per JVM,
or one per {@link Configuration}). They
+ * should be thread safe. They may encapsulate a cache and return cached
{@link TemplateValueFormat} instances.
*
* @since 2.3.24
*/
diff --git a/src/main/java/freemarker/template/utility/TemporalUtils.java
b/src/main/java/freemarker/template/utility/TemporalUtils.java
index fb82e85..3498ff5 100644
--- a/src/main/java/freemarker/template/utility/TemporalUtils.java
+++ b/src/main/java/freemarker/template/utility/TemporalUtils.java
@@ -431,6 +431,8 @@ public final class TemporalUtils {
* that FreeMarker directly supports. At least in Java 8 they are all
final anyway, but just in case this changes in
* a future Java version, use this method before using {@code ==}.
*
+ * @throws IllegalArgumentException If the temporal class is not currently
supported by FreeMarker.
+ *
* @since 2.3.32
*/
public static Class<? extends Temporal>
normalizeSupportedTemporalClass(Class<? extends Temporal> temporalClass) {
@@ -456,12 +458,30 @@ public final class TemporalUtils {
} else if (Year.class.isAssignableFrom(temporalClass)) {
return Year.class;
} else {
- return temporalClass;
+ throw new IllegalArgumentException("Unsupprted temporal class:
" + temporalClass.getName());
}
}
}
/**
+ * Tells if the temporal class is one that doesn't store, nor have an
implied time zone or offset.
+ *
+ * @throws IllegalArgumentException If the temporal class is not currently
supported by FreeMarker.
+ *
+ * @since 2.3.32
+ */
+ public static boolean isLocalTemporalClass(Class<? extends Temporal>
temporalClass) {
+ temporalClass = normalizeSupportedTemporalClass(temporalClass);
+ if (temporalClass == Instant.class
+ || temporalClass == OffsetDateTime.class
+ || temporalClass == ZonedDateTime.class
+ || temporalClass == OffsetTime.class) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Returns the FreeMarker configuration format setting name for a temporal
class.
*
* @throws IllegalArgumentException If {@link temporalClass} is not a
supported {@link Temporal} subclass.
@@ -495,5 +515,4 @@ public final class TemporalUtils {
throw new IllegalArgumentException("Unsupported temporal class: "
+ temporalClass.getName());
}
}
-
}
diff --git a/src/test/java/freemarker/core/AbstractTemporalFormatTest.java
b/src/test/java/freemarker/core/AbstractTemporalFormatTest.java
index 5f30bb3..4fcb177 100644
--- a/src/test/java/freemarker/core/AbstractTemporalFormatTest.java
+++ b/src/test/java/freemarker/core/AbstractTemporalFormatTest.java
@@ -20,6 +20,7 @@
package freemarker.core;
import static freemarker.template.utility.StringUtil.*;
+import static org.junit.Assert.*;
import java.io.IOException;
import java.io.UncheckedIOException;
@@ -40,11 +41,11 @@ import freemarker.template.utility.DateUtil;
*/
public abstract class AbstractTemporalFormatTest {
- static protected String formatTemporal(Consumer<Configurable> configurer,
Temporal... values) throws
+ static protected String formatTemporal(Consumer<Configurable>
configurator, Temporal... values) throws
TemplateException {
Configuration conf = new Configuration(Configuration.VERSION_2_3_32);
- configurer.accept(conf);
+ configurator.accept(conf);
Environment env = null;
try {
@@ -65,13 +66,13 @@ public abstract class AbstractTemporalFormatTest {
}
static protected void assertParsingResults(
- Consumer<Configurable> configurer,
+ Consumer<Configurable> configurator,
Object... stringsAndExpectedResults) throws TemplateException,
TemplateValueFormatException {
Configuration conf = new Configuration(Configuration.VERSION_2_3_32);
conf.setTimeZone(DateUtil.UTC);
conf.setLocale(Locale.US);
- configurer.accept(conf);
+ configurator.accept(conf);
Environment env = null;
try {
@@ -128,4 +129,33 @@ public abstract class AbstractTemporalFormatTest {
}
}
+ static protected void assertParsingFails(
+ Consumer<Configurable> configurator,
+ String parsed,
+ Class<? extends Temporal> temporalClass,
+ Consumer<TemplateValueFormatException> exceptionAssertions) throws
TemplateException,
+ TemplateValueFormatException {
+ Configuration conf = new Configuration(Configuration.VERSION_2_3_32);
+ conf.setTimeZone(DateUtil.UTC);
+ conf.setLocale(Locale.US);
+
+ configurator.accept(conf);
+
+ Environment env = null;
+ try {
+ env = new Template(null, "",
conf).createProcessingEnvironment(null, null);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ TemplateTemporalFormat templateTemporalFormat =
env.getTemplateTemporalFormat(temporalClass);
+
+ try {
+ templateTemporalFormat.parse(parsed);
+ fail("Parsing " + jQuote(parsed) + " with " +
templateTemporalFormat + " should have failed.");
+ } catch (TemplateValueFormatException e) {
+ exceptionAssertions.accept(e);
+ }
+ }
+
}
diff --git a/src/test/java/freemarker/core/TemporalFormatWithIsoFormatTest.java
b/src/test/java/freemarker/core/TemporalFormatWithIsoFormatTest.java
index f096970..27a4bf3 100644
--- a/src/test/java/freemarker/core/TemporalFormatWithIsoFormatTest.java
+++ b/src/test/java/freemarker/core/TemporalFormatWithIsoFormatTest.java
@@ -19,59 +19,72 @@
package freemarker.core;
+import static freemarker.template.utility.StringUtil.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.IOException;
+import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
+import java.time.Year;
+import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatterBuilder;
+import java.time.temporal.Temporal;
import java.util.Locale;
+import java.util.TimeZone;
import java.util.function.Consumer;
import org.junit.Test;
import freemarker.template.TemplateException;
+import freemarker.template.utility.DateUtil;
public class TemporalFormatWithIsoFormatTest extends
AbstractTemporalFormatTest {
- private static final Consumer<Configurable> ISO_DATE_TIME_CONFIGURER =
conf -> conf.setDateTimeFormat("iso");
- private static final Consumer<Configurable> ISO_DATE_CONFIGURER = conf ->
conf.setDateFormat("iso");
- private static final Consumer<Configurable> ISO_TIME_CONFIGURER = conf ->
conf.setTimeFormat("iso");
+ private static final Consumer<Configurable> ISO_DATE_CONFIGURATOR = conf
-> conf.setDateFormat("iso");
+ private static final Consumer<Configurable> ISO_TIME_CONFIGURATOR = conf
-> conf.setTimeFormat("iso");
+ private static final Consumer<Configurable> ISO_DATE_TIME_CONFIGURATOR =
conf -> conf.setDateTimeFormat("iso");
+
+ private static Consumer<Configurable> isoDateTimeConfigurator(TimeZone
timeZone) {
+ return conf -> { ISO_DATE_TIME_CONFIGURATOR.accept(conf);
conf.setTimeZone(timeZone); };
+ }
+ private static final Consumer<Configurable> ISO_YEAR_MONTH_CONFIGURATOR =
conf -> conf.setYearMonthFormat("iso");
+ private static final Consumer<Configurable> ISO_YEAR_CONFIGURATOR = conf
-> conf.setYearFormat("iso");
@Test
public void testFormatOffsetTime() throws TemplateException, IOException {
assertEquals(
"13:01:02Z",
formatTemporal(
- ISO_TIME_CONFIGURER,
+ ISO_TIME_CONFIGURATOR,
OffsetTime.of(LocalTime.of(13, 1, 2),
ZoneOffset.UTC)));
assertEquals(
"13:01:02+01:00",
formatTemporal(
- ISO_TIME_CONFIGURER,
+ ISO_TIME_CONFIGURATOR,
OffsetTime.of(LocalTime.of(13, 1, 2),
ZoneOffset.ofHours(1))));
assertEquals(
"13:00:00-02:30",
formatTemporal(
- ISO_TIME_CONFIGURER,
+ ISO_TIME_CONFIGURATOR,
OffsetTime.of(LocalTime.of(13, 0, 0),
ZoneOffset.ofHoursMinutesSeconds(-2, -30, 0))));
assertEquals(
"13:00:00.0123Z",
formatTemporal(
- ISO_TIME_CONFIGURER,
+ ISO_TIME_CONFIGURATOR,
OffsetTime.of(LocalTime.of(13, 0, 0, 12_300_000),
ZoneOffset.UTC)));
assertEquals(
- "13:00:00.3Z",
+ "04:51:52.3Z",
formatTemporal(
- ISO_TIME_CONFIGURER,
- OffsetTime.of(LocalTime.of(13, 0, 0, 300_000_000),
ZoneOffset.UTC)));
+ ISO_TIME_CONFIGURATOR,
+ OffsetTime.of(LocalTime.of(4, 51, 52, 300_000_000),
ZoneOffset.UTC)));
}
@Test
@@ -79,18 +92,18 @@ public class TemporalFormatWithIsoFormatTest extends
AbstractTemporalFormatTest
assertEquals(
"13:01:02",
formatTemporal(
- ISO_TIME_CONFIGURER,
+ ISO_TIME_CONFIGURATOR,
LocalTime.of(13, 1, 2)));
assertEquals(
"13:00:00.0123",
formatTemporal(
- ISO_TIME_CONFIGURER,
+ ISO_TIME_CONFIGURATOR,
LocalTime.of(13, 0, 0, 12_300_000)));
assertEquals(
- "13:00:00.3",
+ "04:51:52.3",
formatTemporal(
- ISO_TIME_CONFIGURER,
- LocalTime.of(13, 0, 0, 300_000_000)));
+ ISO_TIME_CONFIGURATOR,
+ LocalTime.of(4, 51, 52, 300_000_000)));
}
@Test
@@ -98,18 +111,18 @@ public class TemporalFormatWithIsoFormatTest extends
AbstractTemporalFormatTest
assertEquals(
"2021-12-11T13:01:02",
formatTemporal(
- ISO_DATE_TIME_CONFIGURER,
+ ISO_DATE_TIME_CONFIGURATOR,
LocalDateTime.of(2021, 12, 11, 13, 1, 2, 0)));
assertEquals(
"2021-12-11T13:01:02.0123",
formatTemporal(
- ISO_DATE_TIME_CONFIGURER,
+ ISO_DATE_TIME_CONFIGURATOR,
LocalDateTime.of(2021, 12, 11, 13, 1, 2, 12_300_000)));
assertEquals(
- "2021-12-11T13:01:02.3",
+ "2021-02-03T04:51:52.3",
formatTemporal(
- ISO_DATE_TIME_CONFIGURER,
- LocalDateTime.of(2021, 12, 11, 13, 1, 2,
300_000_000)));
+ ISO_DATE_TIME_CONFIGURATOR,
+ LocalDateTime.of(2021, 2, 3, 4, 51, 52, 300_000_000)));
}
@Test
@@ -117,28 +130,28 @@ public class TemporalFormatWithIsoFormatTest extends
AbstractTemporalFormatTest
assertEquals(
"2021-12-11T13:01:02Z",
formatTemporal(
- ISO_DATE_TIME_CONFIGURER,
+ ISO_DATE_TIME_CONFIGURATOR,
OffsetDateTime.of(2021, 12, 11, 13, 1, 2, 0,
ZoneOffset.UTC)));
assertEquals(
"2021-12-11T13:01:02+01:00",
formatTemporal(
- ISO_DATE_TIME_CONFIGURER,
+ ISO_DATE_TIME_CONFIGURATOR,
OffsetDateTime.of(2021, 12, 11, 13, 1, 2, 0,
ZoneOffset.ofHours(1))));
assertEquals(
"2021-12-11T13:01:02-02:30",
formatTemporal(
- ISO_DATE_TIME_CONFIGURER,
+ ISO_DATE_TIME_CONFIGURATOR,
OffsetDateTime.of(2021, 12, 11, 13, 1, 2, 0,
ZoneOffset.ofHoursMinutesSeconds(-2, -30, 0))));
assertEquals(
"2021-12-11T13:01:02.0123Z",
formatTemporal(
- ISO_DATE_TIME_CONFIGURER,
+ ISO_DATE_TIME_CONFIGURATOR,
OffsetDateTime.of(2021, 12, 11, 13, 1, 2, 12_300_000,
ZoneOffset.UTC)));
assertEquals(
- "2021-12-11T13:01:02.3Z",
+ "2021-02-03T04:51:52.3Z",
formatTemporal(
- ISO_DATE_TIME_CONFIGURER,
- OffsetDateTime.of(2021, 12, 11, 13, 1, 2, 300_000_000,
ZoneOffset.UTC)));
+ ISO_DATE_TIME_CONFIGURATOR,
+ OffsetDateTime.of(2021, 2, 3, 4, 51, 52, 300_000_000,
ZoneOffset.UTC)));
}
@Test
@@ -146,34 +159,68 @@ public class TemporalFormatWithIsoFormatTest extends
AbstractTemporalFormatTest
assertEquals(
"2021-12-11T13:01:02Z",
formatTemporal(
- ISO_DATE_TIME_CONFIGURER,
+ ISO_DATE_TIME_CONFIGURATOR,
ZonedDateTime.of(2021, 12, 11, 13, 1, 2, 0,
ZoneOffset.UTC)));
ZoneId zoneId = ZoneId.of("America/New_York");
assertEquals(
"2021-12-11T13:01:02-05:00",
formatTemporal(
- ISO_DATE_TIME_CONFIGURER,
+ ISO_DATE_TIME_CONFIGURATOR,
ZonedDateTime.of(2021, 12, 11, 13, 1, 2, 0, zoneId)));
assertEquals(
"2021-07-11T13:01:02-04:00",
formatTemporal(
- ISO_DATE_TIME_CONFIGURER,
+ ISO_DATE_TIME_CONFIGURATOR,
ZonedDateTime.of(2021, 7, 11, 13, 1, 2, 0, zoneId)));
assertEquals(
"2021-12-11T13:01:02-02:30",
formatTemporal(
- ISO_DATE_TIME_CONFIGURER,
+ ISO_DATE_TIME_CONFIGURATOR,
OffsetDateTime.of(2021, 12, 11, 13, 1, 2, 0,
ZoneOffset.ofHoursMinutesSeconds(-2, -30, 0))));
assertEquals(
"2021-12-11T13:01:02.0123Z",
formatTemporal(
- ISO_DATE_TIME_CONFIGURER,
+ ISO_DATE_TIME_CONFIGURATOR,
OffsetDateTime.of(2021, 12, 11, 13, 1, 2, 12_300_000,
ZoneOffset.UTC)));
assertEquals(
- "2021-12-11T13:01:02.3Z",
+ "2021-02-03T04:51:52.3Z",
+ formatTemporal(
+ ISO_DATE_TIME_CONFIGURATOR,
+ OffsetDateTime.of(2021, 2, 3, 4, 51, 52, 300_000_000,
ZoneOffset.UTC)));
+ }
+
+ @Test
+ public void testFormatInstant() throws TemplateException, IOException {
+ assertEquals(
+ "2021-12-11T13:01:02Z",
+ formatTemporal(
+ isoDateTimeConfigurator(DateUtil.UTC),
+ OffsetDateTime.of(2021, 12, 11, 13, 1, 2, 0,
ZoneOffset.UTC)
+ .toInstant()));
+ assertEquals(
+ "2021-12-11T13:01:02+01:00",
+ formatTemporal(
+
isoDateTimeConfigurator(TimeZone.getTimeZone("GMT+01")),
+ OffsetDateTime.of(2021, 12, 11, 13, 1, 2, 0,
ZoneOffset.ofHours(1))
+ .toInstant()));
+ assertEquals(
+ "2021-12-11T13:01:02-02:30",
+ formatTemporal(
+
isoDateTimeConfigurator(TimeZone.getTimeZone("GMT-02:30")),
+ OffsetDateTime.of(2021, 12, 11, 13, 1, 2, 0,
ZoneOffset.ofHoursMinutesSeconds(-2, -30, 0))
+ .toInstant()));
+ assertEquals(
+ "2021-12-11T13:01:02.0123Z",
+ formatTemporal(
+ isoDateTimeConfigurator(DateUtil.UTC),
+ OffsetDateTime.of(2021, 12, 11, 13, 1, 2, 12_300_000,
ZoneOffset.UTC)
+ .toInstant()));
+ assertEquals(
+ "2021-02-03T04:51:52.3Z",
formatTemporal(
- ISO_DATE_TIME_CONFIGURER,
- OffsetDateTime.of(2021, 12, 11, 13, 1, 2, 300_000_000,
ZoneOffset.UTC)));
+ isoDateTimeConfigurator(DateUtil.UTC),
+ OffsetDateTime.of(2021, 2, 3, 4, 51, 52, 300_000_000,
ZoneOffset.UTC)
+ .toInstant()));
}
@Test
@@ -181,22 +228,78 @@ public class TemporalFormatWithIsoFormatTest extends
AbstractTemporalFormatTest
assertEquals(
"2021-12-11",
formatTemporal(
- ISO_DATE_CONFIGURER,
+ ISO_DATE_CONFIGURATOR,
LocalDate.of(2021, 12, 11)));
}
@Test
+ public void testFormatYearMonth() throws TemplateException, IOException {
+ assertEquals(
+ "2021-12",
+ formatTemporal(
+ ISO_YEAR_MONTH_CONFIGURATOR,
+ YearMonth.of(2021, 12)));
+ assertEquals(
+ "1995-01",
+ formatTemporal(
+ ISO_YEAR_MONTH_CONFIGURATOR,
+ YearMonth.of(1995, 1)));
+ }
+
+ @Test
+ public void testFormatYear() throws TemplateException, IOException {
+ assertEquals(
+ "2021",
+ formatTemporal(
+ ISO_YEAR_CONFIGURATOR,
+ Year.of(2021)));
+ assertEquals(
+ "1995",
+ formatTemporal(
+ ISO_YEAR_CONFIGURATOR,
+ Year.of(1995)));
+ }
+
+ @Test
public void testParseOffsetDateTime() throws TemplateException,
TemplateValueFormatException {
+ testParseOffsetDateTimeAndInstant(OffsetDateTime.class);
+ }
+
+ @Test
+ public void testParseInstant() throws TemplateException,
TemplateValueFormatException {
+ testParseOffsetDateTimeAndInstant(Instant.class);
+ }
+
+ @Test
+ public void testParseZonedDateTime() throws TemplateException,
TemplateValueFormatException {
+ testParseOffsetDateTimeAndInstant(ZonedDateTime.class);
+ }
+
+ private Temporal convertToClass(OffsetDateTime offsetDateTime, Class<?
extends Temporal> temporalClass) {
+ if (temporalClass == OffsetDateTime.class) {
+ return offsetDateTime;
+ }
+ if (temporalClass == Instant.class) {
+ return offsetDateTime.toInstant();
+ }
+ if (temporalClass == ZonedDateTime.class) {
+ return offsetDateTime.toZonedDateTime();
+ }
+ throw new IllegalArgumentException();
+ }
+
+ private void testParseOffsetDateTimeAndInstant(Class<? extends Temporal>
temporalClass)
+ throws TemplateException, TemplateValueFormatException {
// ISO extended and ISO basic format:
- for (String s : new String[]{"2021-12-11T13:01:02.0123Z",
"20211211T130102.0123Z"}) {
+ for (String parsedString : new String[]{"2021-12-11T13:01:02.0123Z",
"20211211T130102.0123Z"}) {
assertParsingResults(
- ISO_DATE_TIME_CONFIGURER,
- s,
+ ISO_DATE_TIME_CONFIGURATOR,
+ parsedString,
OffsetDateTime.of(2021, 12, 11, 13, 1, 2, 12_300_000,
ZoneOffset.UTC)) ;
}
// Optional parts:
- for (String s : new String[] {
+ for (String parsedString : new String[] {
"2021-12-11T13:00:00.0+02:00",
"2021-12-11T13:00:00+02:00",
"2021-12-11T13:00+02",
@@ -207,30 +310,83 @@ public class TemporalFormatWithIsoFormatTest extends
AbstractTemporalFormatTest
"20211211T13+02",
}) {
assertParsingResults(
- ISO_DATE_TIME_CONFIGURER,
- s,
- OffsetDateTime.of(2021, 12, 11, 13, 0, 0, 0,
ZoneOffset.ofHours(2)));
+ ISO_DATE_TIME_CONFIGURATOR,
+ parsedString,
+ convertToClass(
+ OffsetDateTime.of(2021, 12, 11, 13, 0, 0, 0,
ZoneOffset.ofHours(2)),
+ temporalClass));
}
- // TODO Zone default
+ // Negative year:
+ for (String parsedString : new String[] {
+ "-1000-02-03T04Z",
+ "-10000203T04Z"
+ }) {
+ assertParsingResults(
+ ISO_DATE_TIME_CONFIGURATOR,
+ parsedString,
+ convertToClass(
+ OffsetDateTime.of(-1000, 2, 3, 4, 0, 0, 0,
ZoneOffset.UTC),
+ temporalClass));
+ }
- try {
+ // Hour 24:
+ for (String parsedString : new String[] {
+ "2020-01-02T24Z",
+ "2020-01-02T24:00Z",
+ "2020-01-02T24:00:00Z",
+ "2020-01-02T24:00:00.0Z",
+ "2020-01-02T24:00:00.0+00",
+ // For local temporals only: "2020-01-02T24:00:00",
+ "20200102T24Z",
+ "20200102T2400Z",
+ "20200102T240000Z",
+ "20200102T240000.0Z",
+ "20200102T240000.0+00",
+ // For local temporals only: "20200102T240000"
+ }) {
assertParsingResults(
- ISO_DATE_TIME_CONFIGURER,
- "2021-12-11",
- OffsetDateTime.of(2021, 12, 11, 13, 1, 2, 12_300_000,
ZoneOffset.UTC));
- fail("OffsetDateTime parsing should have failed");
- } catch (UnparsableValueException e) {
- assertThat(e.getMessage(), allOf(
- containsString("\"2021-12-11\""),
- containsString("OffsetDateTime"),
- containsString("\"T\"")
- ));
+ ISO_DATE_TIME_CONFIGURATOR,
+ parsedString,
+ convertToClass(
+ OffsetDateTime.of(2020, 1, 3, 0, 0, 0, 0,
ZoneOffset.UTC),
+ temporalClass));
+ }
+
+ // Unlike for the Java format, for ISO we require the string to
contain the offset for a non-local target type.
+ for (String parsedString : new String[] {
+ "2020-01-02T03", "2020-01-02T03:00", "2020-01-02T03:00:00",
+ "20200102T03", "20200102T0300", "20200102T030000"}) {
+ assertParsingFails(
+ ISO_DATE_TIME_CONFIGURATOR,
+ parsedString,
+ temporalClass,
+ e -> assertThat(e.getMessage(), allOf(
+ containsString(jQuote(parsedString)),
+ containsString("time zone offset"),
+
containsString(temporalClass.getSimpleName()))));
+ }
+
+ for (String parsedString : new String[] {
+ "2021-12-11", "20211211", "2021-12-11T", "2021-12-11T0Z",
+ "2021-12-11T25Z", "2022-02-29T23Z", "2021-13-11T23Z"}) {
+ assertParsingFails(
+ ISO_DATE_TIME_CONFIGURATOR,
+ parsedString,
+ temporalClass,
+ e -> {
+ assertThat(e.getMessage(), allOf(
+ containsString(jQuote(parsedString)),
+
containsString(temporalClass.getSimpleName())));
+ if (!parsedString.contains("T")) {
+ assertThat(e.getMessage(),
containsString("\"T\""));
+ }
+ });
}
}
@Test
- public void testParseZonedDateTime() throws TemplateException,
TemplateValueFormatException {
+ public void testParseOffsetTime() throws TemplateException,
TemplateValueFormatException {
// TODO [FREEMARKER-35]
}
@@ -240,23 +396,57 @@ public class TemporalFormatWithIsoFormatTest extends
AbstractTemporalFormatTest
}
@Test
- public void testParseInstance() throws TemplateException,
TemplateValueFormatException {
+ public void testParseLocalDate() throws TemplateException,
TemplateValueFormatException {
// TODO [FREEMARKER-35]
}
@Test
- public void testParseLocalDate() throws TemplateException,
TemplateValueFormatException {
+ public void testParseLocalTime() throws TemplateException,
TemplateValueFormatException {
// TODO [FREEMARKER-35]
}
@Test
- public void testParseOffsetTime() throws TemplateException,
TemplateValueFormatException {
- // TODO [FREEMARKER-35]
+ public void testParseYear() throws TemplateException,
TemplateValueFormatException {
+ assertParsingResults(ISO_YEAR_CONFIGURATOR, "2021", Year.of(2021));
+ assertParsingResults(ISO_YEAR_CONFIGURATOR, "1995", Year.of(1995));
+ assertParsingResults(ISO_YEAR_CONFIGURATOR, "95", Year.of(95));
+ assertParsingResults(ISO_YEAR_CONFIGURATOR, "-1000", Year.of(-1000));
+
+ assertParsingFails(
+ ISO_DATE_TIME_CONFIGURATOR,
+ "2021-01",
+ Year.class,
+ e -> {
+ assertThat(e.getMessage(), allOf(
+ containsString(jQuote("2021-01")),
+ containsString("Year")));
+ }
+ );
}
@Test
- public void testParseLocalTime() throws TemplateException,
TemplateValueFormatException {
- // TODO [FREEMARKER-35]
+ public void testParseYearMonth() throws TemplateException,
TemplateValueFormatException {
+ assertParsingResults(ISO_YEAR_MONTH_CONFIGURATOR, "2021-01",
YearMonth.of(2021, 1));
+ assertParsingResults(ISO_YEAR_MONTH_CONFIGURATOR, "202101",
YearMonth.of(2021, 1));
+ assertParsingResults(ISO_YEAR_MONTH_CONFIGURATOR, "1995-12",
YearMonth.of(1995, 12));
+ assertParsingResults(ISO_YEAR_MONTH_CONFIGURATOR, "199512",
YearMonth.of(1995, 12));
+ assertParsingResults(ISO_YEAR_MONTH_CONFIGURATOR, "95-12",
YearMonth.of(95, 12));
+ assertParsingResults(ISO_YEAR_MONTH_CONFIGURATOR, "9512",
YearMonth.of(95, 12));
+ assertParsingResults(ISO_YEAR_MONTH_CONFIGURATOR, "-1000-01",
YearMonth.of(-1000, 1));
+ assertParsingResults(ISO_YEAR_MONTH_CONFIGURATOR, "-100001",
YearMonth.of(-1000, 1));
+
+ for (String parsedString : new String[] {"2021", "2021-12-11",
"2021-13", "202113"}) {
+ assertParsingFails(
+ ISO_YEAR_MONTH_CONFIGURATOR,
+ parsedString,
+ YearMonth.class,
+ e -> {
+ assertThat(e.getMessage(), allOf(
+ containsString(jQuote(parsedString)),
+ containsString("YearMonth")));
+ }
+ );
+ }
}
@Test
@@ -282,14 +472,14 @@ public class TemporalFormatWithIsoFormatTest extends
AbstractTemporalFormatTest
.parse(stringWithYearOfEra, LocalDate::from));
}
- String output = formatTemporal(ISO_DATE_CONFIGURER, localDate);
+ String output = formatTemporal(ISO_DATE_CONFIGURATOR, localDate);
assertEquals(iso8601String, output);
- assertParsingResults(ISO_DATE_CONFIGURER, iso8601String,
localDate);
+ assertParsingResults(ISO_DATE_CONFIGURATOR, iso8601String,
localDate);
}
}
@Test
- public void testParseLocaleHasNoEffect() throws TemplateException,
TemplateValueFormatException {
+ public void testLocaleHasNoEffect() throws TemplateException,
TemplateValueFormatException {
for (Locale locale : new Locale[] {
Locale.CHINA,
Locale.FRANCE,
diff --git
a/src/test/java/freemarker/core/TemporalFormatWithJavaFormatTest.java
b/src/test/java/freemarker/core/TemporalFormatWithJavaFormatTest.java
index 0107341..56fe2ab 100644
--- a/src/test/java/freemarker/core/TemporalFormatWithJavaFormatTest.java
+++ b/src/test/java/freemarker/core/TemporalFormatWithJavaFormatTest.java
@@ -195,10 +195,15 @@ public class TemporalFormatWithJavaFormatTest extends
AbstractTemporalFormatTest
TimeZone cfgTimeZone = TimeZone.getTimeZone(cfgZoneId);
for (boolean winter : new boolean[] {true, false}) {
- String stringToParse = winter ? "2020-12-10 13:14" : "2020-07-10
13:14";
- LocalDateTime localDateTime = winter
- ? LocalDateTime.of(2020, 12, 10, 13, 14)
- : LocalDateTime.of(2020, 07, 10, 13, 14);
+ final String stringToParse;
+ final LocalDateTime localDateTime;
+ if (winter) {
+ stringToParse = "2020-12-10 13:14";
+ localDateTime = LocalDateTime.of(2020, 12, 10, 13, 14);
+ } else {
+ stringToParse = "2020-07-10 13:14";
+ localDateTime = LocalDateTime.of(2020, 07, 10, 13, 14);
+ }
{
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime,
cfgZoneId);