You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2021/10/27 22:12:59 UTC

[freemarker] branch FREEMARKER-35 updated: [FREEMARKER-35]

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 9268063  [FREEMARKER-35]
9268063 is described below

commit 9268063760c95c77d0f131e41b4ba00d6b5d5d4e
Author: ddekany <dd...@apache.org>
AuthorDate: Thu Oct 28 00:10:32 2021 +0200

    [FREEMARKER-35]
    
    - Added missing Configurable related functionality: setters, is...Set methods, TemplateConfiguration apply and merge method updates
    - Some code cleanup, including javaDocs
---
 src/main/java/freemarker/core/Configurable.java    | 183 +++++++++++++++++++--
 .../ISOLikeTemplateTemporalTemporalFormat.java     |   6 +
 .../core/ISOTemplateTemporalFormatFactory.java     |  17 +-
 .../core/JavaTemplateTemporalFormat.java           |  13 +-
 .../freemarker/core/TemplateConfiguration.java     |  63 +++++++
 .../freemarker/core/TemplateDateFormatFactory.java |   2 +-
 .../core/TemplateNumberFormatFactory.java          |   2 +-
 .../freemarker/core/TemplateTemporalFormat.java    |  13 +-
 .../core/TemplateTemporalFormatFactory.java        |  15 +-
 .../core/ToStringTemplateTemporalFormat.java       |   8 +-
 .../ToStringTemplateTemporalFormatFactory.java     |   7 +
 .../core/UnformattableTemporalTypeException.java   |   2 +-
 .../core/XSTemplateTemporalFormatFactory.java      |  57 +------
 .../freemarker/core/TemplateConfigurationTest.java |  37 ++++-
 14 files changed, 330 insertions(+), 95 deletions(-)

diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index adb513d..c1ffce9 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -173,6 +173,10 @@ public class Configurable {
     public static final String OFFSET_TIME_FORMAT_KEY_CAMEL_CASE = "offsetTimeFormat";
     public static final String OFFSET_TIME_FORMAT_KEY = OFFSET_TIME_FORMAT_KEY_SNAKE_CASE;
 
+    public static final String ZONED_DATE_TIME_FORMAT_KEY_SNAKE_CASE = "zoned_date_time_format";
+    public static final String ZONED_DATE_TIME_FORMAT_KEY_CAMEL_CASE = "zonedDateTimeFormat";
+    public static final String ZONED_DATE_TIME_FORMAT_KEY = ZONED_DATE_TIME_FORMAT_KEY_SNAKE_CASE;
+
     public static final String YEAR_FORMAT_KEY_SNAKE_CASE = "year_format";
     public static final String YEAR_FORMAT_KEY_CAMEL_CASE = "yearFormat";
     public static final String YEAR_FORMAT_KEY = YEAR_FORMAT_KEY_SNAKE_CASE;
@@ -181,10 +185,6 @@ public class Configurable {
     public static final String YEAR_MONTH_FORMAT_KEY_CAMEL_CASE = "yearMonthFormat";
     public static final String YEAR_MONTH_FORMAT_KEY = YEAR_MONTH_FORMAT_KEY_SNAKE_CASE;
 
-    public static final String ZONED_DATE_TIME_FORMAT_KEY_SNAKE_CASE = "zoned_date_time_format";
-    public static final String ZONED_DATE_TIME_FORMAT_KEY_CAMEL_CASE = "zonedDateTimeFormat";
-    public static final String ZONED_DATE_TIME_FORMAT_KEY = ZONED_DATE_TIME_FORMAT_KEY_SNAKE_CASE;
-
     /** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
     public static final String TIME_ZONE_KEY_SNAKE_CASE = "time_zone";
     /** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
@@ -442,9 +442,9 @@ public class Configurable {
     private String localTimeFormat;
     private String offsetDateTimeFormat;
     private String offsetTimeFormat;
+    private String zonedDateTimeFormat;
     private String yearFormat;
     private String yearMonthFormat;
-    private String zonedDateTimeFormat;
     private TimeZone timeZone;
     private TimeZone sqlDataAndTimeTimeZone;
     private boolean sqlDataAndTimeTimeZoneSet;
@@ -535,15 +535,15 @@ public class Configurable {
         offsetTimeFormat = "";
         properties.setProperty(OFFSET_TIME_FORMAT_KEY, offsetTimeFormat);
 
+        zonedDateTimeFormat = "";
+        properties.setProperty(ZONED_DATE_TIME_FORMAT_KEY, zonedDateTimeFormat);
+
         yearFormat = "";
         properties.setProperty(YEAR_FORMAT_KEY, yearFormat);
 
         yearMonthFormat = "";
         properties.setProperty(YEAR_MONTH_FORMAT_KEY, yearMonthFormat);
 
-        zonedDateTimeFormat = "";
-        properties.setProperty(ZONED_DATE_TIME_FORMAT_KEY, zonedDateTimeFormat);
-
         classicCompatible = Integer.valueOf(0);
         properties.setProperty(CLASSIC_COMPATIBLE_KEY, classicCompatible.toString());
         
@@ -1369,40 +1369,199 @@ public class Configurable {
         return dateTimeFormat != null;
     }
 
+    /**
+     * Sets the format used to convert {@link java.time.Instant}-s to string-s, also the format that
+     * {@code someString?instant} will use to parse strings.
+     * <p>Defaults to TODO [FREEMARKER-35].
+     * @since 2.3.32
+     */
+    public void setInstantFormat(String instantFormat) {
+        this.instantFormat = instantFormat;
+    }
+
+    /**
+     * Getter pair of {@link #setInstantFormat(String)}.
+     * @since 2.3.32
+     */
     public String getInstantFormat() {
-       return instantFormat == null ? parent.getInstantFormat() : instantFormat;
+        return instantFormat == null ? parent.getInstantFormat() : instantFormat;
     }
 
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *
+     * @since 2.3.32
+     */
+    public boolean isInstantFormatSet() {
+        return instantFormat != null;
+    }
+
+    public void setLocalDateFormat(String localDateFormat) {
+        this.localDateFormat = localDateFormat;
+    }
+
+    /**
+     * Getter pair of {@link #setLocalDateFormat(String)}.
+     * @since 2.3.32
+     */
     public String getLocalDateFormat() {
         return localDateFormat == null ? parent.getLocalDateFormat() : localDateFormat;
     }
 
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *
+     * @since 2.3.32
+     */
+    public boolean isLocalDateFormatSet() {
+        return localDateFormat != null;
+    }
+
+    public void setLocalDateTimeFormat(String localDateTimeFormat) {
+        this.localDateTimeFormat = localDateTimeFormat;
+    }
+
+    /**
+     * Getter pair of {@link #setLocalDateTimeFormat(String)}.
+     * @since 2.3.32
+     */
     public String getLocalDateTimeFormat() {
         return localDateTimeFormat == null ? parent.getLocalDateTimeFormat() : localDateTimeFormat;
     }
 
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *
+     * @since 2.3.32
+     */
+    public boolean isLocalDateTimeFormatSet() {
+        return localDateTimeFormat != null;
+    }
+
+    public void setLocalTimeFormat(String localTimeFormat) {
+        this.localTimeFormat = localTimeFormat;
+    }
+
+    /**
+     * Getter pair of {@link #setLocalTimeFormat(String)}.
+     * @since 2.3.32
+     */
     public String getLocalTimeFormat() {
         return localTimeFormat == null ? parent.getLocalTimeFormat() : localTimeFormat;
     }
 
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *
+     * @since 2.3.32
+     */
+    public boolean isLocalTimeFormatSet() {
+        return localTimeFormat != null;
+    }
+
+    public void setOffsetDateTimeFormat(String offsetDateTimeFormat) {
+        this.offsetDateTimeFormat = offsetDateTimeFormat;
+    }
+
+    /**
+     * Getter pair of {@link #setOffsetDateTimeFormat(String)}.
+     * @since 2.3.32
+     */
     public String getOffsetDateTimeFormat() {
         return offsetDateTimeFormat == null ? parent.getOffsetDateTimeFormat() : offsetDateTimeFormat;
     }
 
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *
+     * @since 2.3.32
+     */
+    public boolean isOffsetDateTimeFormatSet() {
+        return offsetDateTimeFormat != null;
+    }
+
+    public void setOffsetTimeFormat(String offsetTimeFormat) {
+        this.offsetTimeFormat = offsetTimeFormat;
+    }
+
+    /**
+     * Getter pair of {@link #setOffsetTimeFormat(String)}.
+     * @since 2.3.32
+     */
     public String getOffsetTimeFormat() {
         return offsetTimeFormat == null ? parent.getOffsetTimeFormat() : offsetTimeFormat;
     }
 
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *
+     * @since 2.3.32
+     */
+    public boolean isOffsetTimeFormatSet() {
+        return offsetTimeFormat != null;
+    }
+
+    public void setZonedDateTimeFormat(String zonedDateTimeFormat) {
+        this.zonedDateTimeFormat = zonedDateTimeFormat;
+    }
+
+    /**
+     * Getter pair of {@link #setZonedDateTimeFormat(String)}.
+     * @since 2.3.32
+     */
+    public String getZonedDateTimeFormat() {
+        return zonedDateTimeFormat == null ? parent.getZonedDateTimeFormat() : zonedDateTimeFormat;
+    }
+
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *
+     * @since 2.3.32
+     */
+    public boolean isZonedDateTimeFormatSet() {
+        return zonedDateTimeFormat != null;
+    }
+
+    public void setYearFormat(String yearFormat) {
+        this.yearFormat = yearFormat;
+    }
+
+    /**
+     * Getter pair of {@link #setYearFormat(String)}.
+     * @since 2.3.32
+     */
     public String getYearFormat() {
         return yearFormat == null ? parent.getYearFormat() : yearFormat;
     }
 
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *
+     * @since 2.3.32
+     */
+    public boolean isYearFormatSet() {
+        return yearFormat != null;
+    }
+
+    public void setYearMonthFormat(String yearMonthFormat) {
+        this.yearMonthFormat = yearMonthFormat;
+    }
+
+    /**
+     * Getter pair of {@link #setYearMonthFormat(String)}.
+     * @since 2.3.32
+     */
     public String getYearMonthFormat() {
         return yearMonthFormat == null ? parent.getYearMonthFormat() : yearMonthFormat;
     }
 
-    public String getZonedDateTimeFormat() {
-        return zonedDateTimeFormat == null ? parent.getZonedDateTimeFormat() : zonedDateTimeFormat;
+    /**
+     * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+     *
+     * @since 2.3.32
+     */
+    public boolean isYearMonthFormatSet() {
+        return yearMonthFormat != null;
     }
 
     /**
@@ -1417,7 +1576,7 @@ public class Configurable {
      * @throws NullPointerException If {@link temporalClass} was {@code null}
      * @throws IllegalArgumentException If {@link temporalClass} is not a supported {@link Temporal} subclass.
      *
-     * @since 2.3.31
+     * @since 2.3.32
      */
     public String getTemporalFormat(Class<? extends Temporal> temporalClass) {
         Objects.requireNonNull(temporalClass);
diff --git a/src/main/java/freemarker/core/ISOLikeTemplateTemporalTemporalFormat.java b/src/main/java/freemarker/core/ISOLikeTemplateTemporalTemporalFormat.java
index 87eb859..fc500e0 100644
--- a/src/main/java/freemarker/core/ISOLikeTemplateTemporalTemporalFormat.java
+++ b/src/main/java/freemarker/core/ISOLikeTemplateTemporalTemporalFormat.java
@@ -30,6 +30,12 @@ import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateTemporalModel;
 
 // TODO [FREEMARKER-35] These should support parameters similar to {@link ISOTemplateDateFormat},
+
+/**
+ * See {@link ISOTemplateTemporalFormatFactory}, and {@link XSTemplateTemporalFormatFactory}.
+ *
+ * @since 2.3.32
+ */
 final class ISOLikeTemplateTemporalTemporalFormat extends TemplateTemporalFormat {
     private final DateTimeFormatter dateTimeFormatter;
     private final boolean instantConversion;
diff --git a/src/main/java/freemarker/core/ISOTemplateTemporalFormatFactory.java b/src/main/java/freemarker/core/ISOTemplateTemporalFormatFactory.java
index e932d3f..50edb2c 100644
--- a/src/main/java/freemarker/core/ISOTemplateTemporalFormatFactory.java
+++ b/src/main/java/freemarker/core/ISOTemplateTemporalFormatFactory.java
@@ -31,6 +31,9 @@ import java.time.temporal.Temporal;
 import java.util.Locale;
 import java.util.TimeZone;
 
+/**
+ * Format factory related to {@link someJava8Temporal?string.iso}, {@link someJava8Temporal?string.iso_...}, etc.
+ */
 class ISOTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
 
     static final ISOTemplateTemporalFormatFactory INSTANCE = new ISOTemplateTemporalFormatFactory();
@@ -39,14 +42,13 @@ class ISOTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
         // Not meant to be called from outside
     }
 
-    private static final DateTimeFormatter ISO8601_DATE_FORMAT = new DateTimeFormatterBuilder()
+    static final DateTimeFormatter ISO8601_DATE_FORMAT = new DateTimeFormatterBuilder()
             .append(DateTimeFormatter.ISO_LOCAL_DATE)
             .toFormatter()
             .withLocale(Locale.US);
 
-    private static final DateTimeFormatter ISO8601_DATE_TIME_FORMAT = new DateTimeFormatterBuilder()
+    static final DateTimeFormatter ISO8601_DATE_TIME_FORMAT = new DateTimeFormatterBuilder()
             .append(DateTimeFormatter.ISO_LOCAL_DATE)
-            .optionalStart()
             .appendLiteral('T')
             .appendValue(ChronoField.HOUR_OF_DAY, 2)
             .appendLiteral(":")
@@ -57,11 +59,10 @@ class ISOTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
             .optionalStart()
             .appendOffsetId()
             .optionalEnd()
-            .optionalEnd()
             .toFormatter()
             .withLocale(Locale.US);
 
-    private static final DateTimeFormatter ISO8601_TIME_FORMAT = new DateTimeFormatterBuilder()
+    static final DateTimeFormatter ISO8601_TIME_FORMAT = new DateTimeFormatterBuilder()
             .appendValue(ChronoField.HOUR_OF_DAY, 2)
             .appendLiteral(":")
             .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
@@ -74,7 +75,7 @@ class ISOTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
             .toFormatter()
             .withLocale(Locale.US);
 
-    private static final DateTimeFormatter ISO8601_YEARMONTH_FORMAT = new DateTimeFormatterBuilder()
+    static final DateTimeFormatter ISO8601_YEARMONTH_FORMAT = new DateTimeFormatterBuilder()
             .appendValue(ChronoField.YEAR)
             .appendLiteral("-")
             .appendValue(ChronoField.MONTH_OF_YEAR, 2)
@@ -91,7 +92,7 @@ class ISOTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
             TemplateValueFormatException {
         if (!params.isEmpty()) {
             // TODO [FREEMARKER-35]
-            throw new InvalidFormatParametersException("xs currently doesn't support parameters");
+            throw new InvalidFormatParametersException("iso currently doesn't support parameters for Java 8 temporal types");
         }
 
         return getISOFormatter(temporalClass, timeZone);
@@ -104,7 +105,7 @@ class ISOTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
             dateTimeFormatter = ISO8601_TIME_FORMAT;
             description = "ISO 8601 (subset) time";
         } else if (temporalClass == Year.class) {
-            dateTimeFormatter = ISO8601_YEAR_FORMAT; // Same as ISO
+            dateTimeFormatter = ISO8601_YEAR_FORMAT;
             description = "ISO 8601 (subset) year";
         } else if (temporalClass == YearMonth.class) {
             dateTimeFormatter = ISO8601_YEARMONTH_FORMAT;
diff --git a/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java b/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
index da966fb..5482826 100644
--- a/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
@@ -40,6 +40,11 @@ import freemarker.template.TemplateTemporalModel;
 import freemarker.template.utility.ClassUtil;
 import freemarker.template.utility.StringUtil;
 
+/**
+ * See {@link JavaTemplateTemporalFormatFactory}.
+ *
+ * @since 2.3.32
+ */
 class JavaTemplateTemporalFormat extends TemplateTemporalFormat {
 
     enum FormatTimeConversion {
@@ -47,7 +52,13 @@ class JavaTemplateTemporalFormat extends TemplateTemporalFormat {
         SET_ZONE_FROM_OFFSET
     }
 
-    private static final Pattern FORMAT_STYLE_PATTERN = Pattern.compile("(short|medium|long|full)(?:_(short|medium|long|full))?");
+    static final String SHORT = "short";
+    static final String MEDIUM = "medium";
+    static final String LONG = "long";
+    static final String FULL = "full";
+    private static final String ANY_FORMAT_STYLE = "(" + SHORT + "|" + MEDIUM + "|" + LONG + "|" + FULL + ")";
+    private static final Pattern FORMAT_STYLE_PATTERN = Pattern.compile(
+            ANY_FORMAT_STYLE + "(?:_" + ANY_FORMAT_STYLE + ")?");
 
     private final DateTimeFormatter dateTimeFormatter;
     private final ZoneId zoneId;
diff --git a/src/main/java/freemarker/core/TemplateConfiguration.java b/src/main/java/freemarker/core/TemplateConfiguration.java
index 92cc671..cd8410f 100644
--- a/src/main/java/freemarker/core/TemplateConfiguration.java
+++ b/src/main/java/freemarker/core/TemplateConfiguration.java
@@ -190,6 +190,33 @@ public final class TemplateConfiguration extends Configurable implements ParserC
         if (tc.isDateTimeFormatSet()) {
             setDateTimeFormat(tc.getDateTimeFormat());
         }
+        if (tc.isInstantFormatSet()) {
+            setInstantFormat(tc.getInstantFormat());
+        }
+        if (tc.isLocalDateFormatSet()) {
+            setLocalDateFormat(tc.getLocalDateFormat());
+        }
+        if (tc.isLocalDateTimeFormatSet()) {
+            setLocalDateTimeFormat(tc.getLocalDateTimeFormat());
+        }
+        if (tc.isLocalTimeFormatSet()) {
+            setLocalTimeFormat(tc.getLocalTimeFormat());
+        }
+        if (tc.isOffsetDateTimeFormatSet()) {
+            setOffsetDateTimeFormat(tc.getOffsetDateTimeFormat());
+        }
+        if (tc.isOffsetTimeFormatSet()) {
+            setOffsetTimeFormat(tc.getOffsetTimeFormat());
+        }
+        if (tc.isZonedDateTimeFormatSet()) {
+            setZonedDateTimeFormat(tc.getZonedDateTimeFormat());
+        }
+        if (tc.isYearFormatSet()) {
+            setYearFormat(tc.getYearFormat());
+        }
+        if (tc.isYearMonthFormatSet()) {
+            setYearMonthFormat(tc.getYearMonthFormat());
+        }
         if (tc.isEncodingSet()) {
             setEncoding(tc.getEncoding());
         }
@@ -332,6 +359,33 @@ public final class TemplateConfiguration extends Configurable implements ParserC
         if (isDateTimeFormatSet() && !template.isDateTimeFormatSet()) {
             template.setDateTimeFormat(getDateTimeFormat());
         }
+        if (isInstantFormatSet() && !template.isInstantFormatSet()) {
+            template.setInstantFormat(getInstantFormat());
+        }
+        if (isLocalDateFormatSet() && !template.isLocalDateFormatSet()) {
+            template.setLocalDateFormat(getLocalDateFormat());
+        }
+        if (isLocalTimeFormatSet() && !template.isLocalTimeFormatSet()) {
+            template.setLocalTimeFormat(getLocalTimeFormat());
+        }
+        if (isLocalDateTimeFormatSet() && !template.isLocalDateTimeFormatSet()) {
+            template.setLocalDateTimeFormat(getLocalDateTimeFormat());
+        }
+        if (isOffsetTimeFormatSet() && !template.isOffsetTimeFormatSet()) {
+            template.setOffsetTimeFormat(getOffsetTimeFormat());
+        }
+        if (isOffsetDateTimeFormatSet() && !template.isOffsetDateTimeFormatSet()) {
+            template.setOffsetDateTimeFormat(getOffsetDateTimeFormat());
+        }
+        if (isZonedDateTimeFormatSet() && !template.isZonedDateTimeFormatSet()) {
+            template.setZonedDateTimeFormat(getZonedDateTimeFormat());
+        }
+        if (isYearFormatSet() && !template.isYearFormatSet()) {
+            template.setYearFormat(getYearFormat());
+        }
+        if (isYearMonthFormatSet() && !template.isYearMonthFormatSet()) {
+            template.setYearMonthFormat(getYearMonthFormat());
+        }
         if (isEncodingSet() && template.getEncoding() == null) {
             template.setEncoding(getEncoding());
         }
@@ -679,6 +733,15 @@ public final class TemplateConfiguration extends Configurable implements ParserC
                 || isCustomNumberFormatsSet()
                 || isDateFormatSet()
                 || isDateTimeFormatSet()
+                || isInstantFormatSet()
+                || isLocalDateFormatSet()
+                || isLocalTimeFormatSet()
+                || isLocalDateTimeFormatSet()
+                || isOffsetTimeFormatSet()
+                || isOffsetDateTimeFormatSet()
+                || isZonedDateTimeFormatSet()
+                || isYearFormatSet()
+                || isYearMonthFormatSet()
                 || isLazyImportsSet()
                 || isLazyAutoImportsSet()
                 || isLocaleSet()
diff --git a/src/main/java/freemarker/core/TemplateDateFormatFactory.java b/src/main/java/freemarker/core/TemplateDateFormatFactory.java
index 38cae8b..81faa19 100644
--- a/src/main/java/freemarker/core/TemplateDateFormatFactory.java
+++ b/src/main/java/freemarker/core/TemplateDateFormatFactory.java
@@ -76,7 +76,7 @@ public abstract class TemplateDateFormatFactory extends TemplateValueFormatFacto
      * @param env
      *            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}, as changing other setting
+     *            result shouldn't depend on setting values in the {@link Environment}, because changing settings
      *            will not necessarily invalidate the result.
      * 
      * @throws TemplateValueFormatException
diff --git a/src/main/java/freemarker/core/TemplateNumberFormatFactory.java b/src/main/java/freemarker/core/TemplateNumberFormatFactory.java
index 684fef2..4180bea 100644
--- a/src/main/java/freemarker/core/TemplateNumberFormatFactory.java
+++ b/src/main/java/freemarker/core/TemplateNumberFormatFactory.java
@@ -53,7 +53,7 @@ public abstract class TemplateNumberFormatFactory extends TemplateValueFormatFac
      * @param env
      *            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}, as changing other setting
+     *            result shouldn't depend on setting values in the {@link Environment}, because changing settings
      *            will not necessarily invalidate the result.
      *            
      * @throws TemplateValueFormatException
diff --git a/src/main/java/freemarker/core/TemplateTemporalFormat.java b/src/main/java/freemarker/core/TemplateTemporalFormat.java
index f41fb0c..39116a6 100644
--- a/src/main/java/freemarker/core/TemplateTemporalFormat.java
+++ b/src/main/java/freemarker/core/TemplateTemporalFormat.java
@@ -30,22 +30,25 @@ import freemarker.template.TemplateTemporalModel;
  *
  * <p>
  * Implementations need not be thread-safe if the {@link TemplateTemporalFormatFactory} doesn't recycle them among
- * different {@link Environment}-s. As far as FreeMarker's concerned, instances are bound to a single
- * {@link Environment}, and {@link Environment}-s are thread-local objects.
+ * 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.
  *
- * @since 2.3.31
+ * @since 2.3.32
  */
 public abstract class TemplateTemporalFormat extends TemplateValueFormat {
 
     public abstract String format(TemplateTemporalModel temporalModel) throws TemplateValueFormatException, TemplateModelException;
 
     /**
-     * Tells if this formatter should be re-created if the locale changes.
+     * Tells if the same formatter can be used regardless of the desired locale (so for example after a
+     * {@link Environment#getLocale()} change we can keep using the old instance).
      */
     public abstract boolean isLocaleBound();
 
     /**
-     * Tells if this formatter should be re-created if the time zone changes.
+     * Tells if the same formatter can be used regardless of the desired time zone (so for example after a
+     * {@link Environment#getTimeZone()} change we can keep using the old instance).
      */
     public abstract boolean isTimeZoneBound();
 
diff --git a/src/main/java/freemarker/core/TemplateTemporalFormatFactory.java b/src/main/java/freemarker/core/TemplateTemporalFormatFactory.java
index ae3971b..885ea7b 100644
--- a/src/main/java/freemarker/core/TemplateTemporalFormatFactory.java
+++ b/src/main/java/freemarker/core/TemplateTemporalFormatFactory.java
@@ -18,22 +18,19 @@
  */
 package freemarker.core;
 
-import java.text.SimpleDateFormat;
 import java.time.temporal.Temporal;
-import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
 
 import freemarker.template.Configuration;
-import freemarker.template.TemplateDateModel;
 
 /**
  * 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.
+ * (one-per-VM, or one-per-{@link Configuration}), and so must be thread-safe.
  * 
  * TODO [FREEMARKER-35] @see Configurable#setCustomTemporalFormats(java.util.Map)
  * 
- * @since 2.3.24
+ * @since 2.3.32
  */
 public abstract class TemplateTemporalFormatFactory extends TemplateValueFormatFactory {
     
@@ -49,7 +46,7 @@ public abstract class TemplateTemporalFormatFactory extends TemplateValueFormatF
      * 
      * @param params
      *            The string that further describes how the format should look. For example, when the
-     *            {@link Configurable#getInstantFormat()} ()} instantFormat} is {@code "@fooBar 1, 2"}, then it will be
+     *            {@link Configurable#getInstantFormat() instantFormat} is {@code "@fooBar 1, 2"}, then it will be
      *            {@code "1, 2"} (and {@code "@fooBar"} selects the factory). The format of this string is up to the
      *            {@link TemplateTemporalFormatFactory} implementation. Not {@code null}, often an empty string.
      * @param temporalClass
@@ -57,14 +54,14 @@ public abstract class TemplateTemporalFormatFactory extends TemplateValueFormatF
      *            {@link UnformattableTemporalTypeException} exception.
      * @param locale
      *            The locale to format for. Not {@code null}. The resulting format should be bound to this locale
-     *            forever (i.e. locale changes in the {@link Environment} must not be followed).
+     *            forever. That is, the result of {@link Environment#getLocale()} must not be taken into account.
      * @param timeZone
      *            The time zone to format for. Not {@code null}. The resulting format must be bound to this time zone
-     *            forever (i.e. time zone changes in the {@link Environment} must not be followed).
+     *            forever. That is, the result of {@link Environment#getTimeZone()} must not be taken into account.
      * @param env
      *            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}, as changing other setting
+     *            result shouldn't depend on setting values in the {@link Environment}, because changing settings
      *            will not necessarily invalidate the result.
      * 
      * @throws TemplateValueFormatException
diff --git a/src/main/java/freemarker/core/ToStringTemplateTemporalFormat.java b/src/main/java/freemarker/core/ToStringTemplateTemporalFormat.java
index 3bd6d64..4e3dd5b 100644
--- a/src/main/java/freemarker/core/ToStringTemplateTemporalFormat.java
+++ b/src/main/java/freemarker/core/ToStringTemplateTemporalFormat.java
@@ -22,12 +22,18 @@ package freemarker.core;
 import java.time.Instant;
 import java.time.ZoneId;
 import java.time.temporal.Temporal;
-import java.util.Objects;
 import java.util.TimeZone;
 
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateTemporalModel;
 
+/**
+ * See {@link ToStringTemplateTemporalFormatFactory}.
+ *
+ * @Deprected TODO [FREEMARKER-35] I guess we shouldn't need this.
+ *
+ * @since 2.3.32
+ */
 class ToStringTemplateTemporalFormat extends TemplateTemporalFormat {
 
     private final ZoneId timeZone;
diff --git a/src/main/java/freemarker/core/ToStringTemplateTemporalFormatFactory.java b/src/main/java/freemarker/core/ToStringTemplateTemporalFormatFactory.java
index 951471e..a2e53df 100644
--- a/src/main/java/freemarker/core/ToStringTemplateTemporalFormatFactory.java
+++ b/src/main/java/freemarker/core/ToStringTemplateTemporalFormatFactory.java
@@ -23,6 +23,13 @@ import java.time.temporal.Temporal;
 import java.util.Locale;
 import java.util.TimeZone;
 
+/**
+ * Gives a {@link TemplateTemporalFormat} that simply calls {@link Object#toString()}
+ *
+ * @Deprected TODO [FREEMARKER-35] I guess we shouldn't need this.
+ *
+ * @since 2.3.32
+ */
 class ToStringTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
 
     static final ToStringTemplateTemporalFormatFactory INSTANCE = new ToStringTemplateTemporalFormatFactory();
diff --git a/src/main/java/freemarker/core/UnformattableTemporalTypeException.java b/src/main/java/freemarker/core/UnformattableTemporalTypeException.java
index a4f377a..34a75db 100644
--- a/src/main/java/freemarker/core/UnformattableTemporalTypeException.java
+++ b/src/main/java/freemarker/core/UnformattableTemporalTypeException.java
@@ -27,7 +27,7 @@ import freemarker.template.TemplateTemporalModel;
  * Thrown when a {@link TemplateTemporalModel} can't be formatted because the {@link TemplateTemporalFormatFactory}
  * doesn't support it.
  *
- * @since 2.3.31
+ * @since 2.3.32
  */
 public final class UnformattableTemporalTypeException extends UnformattableValueException {
 
diff --git a/src/main/java/freemarker/core/XSTemplateTemporalFormatFactory.java b/src/main/java/freemarker/core/XSTemplateTemporalFormatFactory.java
index 8749c74..f41c0c5 100644
--- a/src/main/java/freemarker/core/XSTemplateTemporalFormatFactory.java
+++ b/src/main/java/freemarker/core/XSTemplateTemporalFormatFactory.java
@@ -27,12 +27,13 @@ import java.time.OffsetTime;
 import java.time.Year;
 import java.time.YearMonth;
 import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeFormatterBuilder;
-import java.time.temporal.ChronoField;
 import java.time.temporal.Temporal;
 import java.util.Locale;
 import java.util.TimeZone;
 
+/**
+ * Format factory related to {@link someJava8Temporal?string.xs}, {@link someJava8Temporal?string.xs_...}, etc.
+ */
 class XSTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
 
     static final XSTemplateTemporalFormatFactory INSTANCE = new XSTemplateTemporalFormatFactory();
@@ -41,52 +42,12 @@ class XSTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
         // Not meant to be called from outside
     }
 
-    private static final DateTimeFormatter XSD_DATE_FORMAT = new DateTimeFormatterBuilder()
-            .append(DateTimeFormatter.ISO_LOCAL_DATE)
-            .toFormatter()
-            .withLocale(Locale.US);
-
-    private static final DateTimeFormatter XSD_DATE_TIME_FORMAT = new DateTimeFormatterBuilder()
-            .append(DateTimeFormatter.ISO_LOCAL_DATE)
-            .appendLiteral('T')
-            .appendValue(ChronoField.HOUR_OF_DAY, 2)
-            .appendLiteral(":")
-            .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
-            .appendLiteral(":")
-            .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
-            .appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true)
-            .optionalStart()
-            .appendOffsetId()
-            .optionalEnd()
-            .toFormatter()
-            .withLocale(Locale.US);
-
-    private static final DateTimeFormatter XSD_TIME_FORMAT = new DateTimeFormatterBuilder()
-            .appendValue(ChronoField.HOUR_OF_DAY, 2)
-            .appendLiteral(":")
-            .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
-            .appendLiteral(":")
-            .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
-            .appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true)
-            .optionalStart()
-            .appendOffsetId()
-            .optionalEnd()
-            .toFormatter()
-            .withLocale(Locale.US);
-
-    private static final DateTimeFormatter XSD_YEARMONTH_FORMAT = new DateTimeFormatterBuilder()
-            .appendValue(ChronoField.YEAR)
-            .appendLiteral("-")
-            .appendValue(ChronoField.MONTH_OF_YEAR, 2)
-            .toFormatter()
-            .withLocale(Locale.US);
-
     @Override
     public TemplateTemporalFormat get(String params, Class<? extends Temporal> temporalClass, Locale locale, TimeZone timeZone, Environment env) throws
             TemplateValueFormatException {
         if (!params.isEmpty()) {
             // TODO [FREEMARKER-35]
-            throw new InvalidFormatParametersException("xs currently doesn't support parameters");
+            throw new InvalidFormatParametersException("xs currently doesn't support parameters for Java 8 temporal types");
         }
 
         return getXSFormatter(temporalClass, timeZone);
@@ -96,16 +57,16 @@ class XSTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
         final DateTimeFormatter dateTimeFormatter;
         final String description;
         if (temporalClass == LocalTime.class || temporalClass == OffsetTime.class) {
-            dateTimeFormatter = XSD_TIME_FORMAT;
+            dateTimeFormatter = ISO8601_TIME_FORMAT;
             description = "W3C XML Schema time";
         } else if (temporalClass == Year.class) {
-            dateTimeFormatter = ISO8601_YEAR_FORMAT; // Same as ISO
+            dateTimeFormatter = ISO8601_YEAR_FORMAT;
             description = "W3C XML Schema year";
         } else if (temporalClass == YearMonth.class) {
-            dateTimeFormatter = XSD_YEARMONTH_FORMAT;
+            dateTimeFormatter = ISO8601_YEARMONTH_FORMAT;
             description = "W3C XML Schema year-month";
         } else if (temporalClass == LocalDate.class) {
-            dateTimeFormatter = XSD_DATE_FORMAT;
+            dateTimeFormatter = ISO8601_DATE_FORMAT;
             description = "W3C XML Schema date";
         } else {
             Class<? extends Temporal> normTemporalClass =
@@ -113,7 +74,7 @@ class XSTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
             if (normTemporalClass != temporalClass) {
                 return getXSFormatter(normTemporalClass, timeZone);
             } else {
-                dateTimeFormatter = XSD_DATE_TIME_FORMAT;
+                dateTimeFormatter = ISO8601_DATE_TIME_FORMAT;
                 description = "W3C XML Schema date-time";
             }
         }
diff --git a/src/test/java/freemarker/core/TemplateConfigurationTest.java b/src/test/java/freemarker/core/TemplateConfigurationTest.java
index e48c38a..57ed326 100644
--- a/src/test/java/freemarker/core/TemplateConfigurationTest.java
+++ b/src/test/java/freemarker/core/TemplateConfigurationTest.java
@@ -163,6 +163,16 @@ public class TemplateConfigurationTest {
         SETTING_ASSIGNMENTS.put("classicCompatibleAsInt", 2);
         SETTING_ASSIGNMENTS.put("dateFormat", "yyyy-#DDD");
         SETTING_ASSIGNMENTS.put("dateTimeFormat", "yyyy-#DDD-@HH:mm");
+        SETTING_ASSIGNMENTS.put("instantFormat", "iso");
+        SETTING_ASSIGNMENTS.put("localDateFormat", "iso");
+        SETTING_ASSIGNMENTS.put("localTimeFormat", "iso");
+        SETTING_ASSIGNMENTS.put("localDateTimeFormat", "iso");
+        SETTING_ASSIGNMENTS.put("offsetTimeFormat", "iso");
+        SETTING_ASSIGNMENTS.put("offsetDateTimeFormat", "iso");
+        SETTING_ASSIGNMENTS.put("zonedTimeFormat", "iso");
+        SETTING_ASSIGNMENTS.put("zonedDateTimeFormat", "iso");
+        SETTING_ASSIGNMENTS.put("yearFormat", "yyyy");
+        SETTING_ASSIGNMENTS.put("yearMonthFormat", "yyyy-MM");
         SETTING_ASSIGNMENTS.put("locale", NON_DEFAULT_LOCALE);
         SETTING_ASSIGNMENTS.put("logTemplateExceptions", false);
         SETTING_ASSIGNMENTS.put("wrapUncheckedExceptions", true);
@@ -245,7 +255,6 @@ public class TemplateConfigurationTest {
 
     static {
         IGNORED_PROP_NAMES = new HashSet();
-        IGNORED_PROP_NAMES.add("class");
         IGNORED_PROP_NAMES.add("strictBeanModels");
         IGNORED_PROP_NAMES.add("parentConfiguration");
         IGNORED_PROP_NAMES.add("settings");
@@ -258,7 +267,8 @@ public class TemplateConfigurationTest {
         try {
             for (PropertyDescriptor propDesc : Introspector.getBeanInfo(Configurable.class).getPropertyDescriptors()) {
                 String propName = propDesc.getName();
-                if (!IGNORED_PROP_NAMES.contains(propName)) {
+                if (!IGNORED_PROP_NAMES.contains(propName) && propDesc.getWriteMethod() != null
+                        && propDesc.getWriteMethod().getDeclaringClass() != Object.class) {
                     CONFIGURABLE_PROP_NAMES.add(propName);
                 }
             }
@@ -541,16 +551,20 @@ public class TemplateConfigurationTest {
         for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(false, true)) {
             TemplateConfiguration tc = new TemplateConfiguration();
             tc.setParentConfiguration(DEFAULT_CFG);
-    
-            Object newValue = SETTING_ASSIGNMENTS.get(pd.getName());
+
+            String propName = pd.getName();
+            if (!SETTING_ASSIGNMENTS.containsKey(propName)) {
+                throw new AssertionError("SETTING_ASSIGNMENTS doesn't yet contain \"" + propName + "\"");
+            }
+            Object newValue = SETTING_ASSIGNMENTS.get(propName);
             pd.getWriteMethod().invoke(tc, newValue);
             
             Template t = new Template(null, "", DEFAULT_CFG);
             Method tReaderMethod = t.getClass().getMethod(pd.getReadMethod().getName());
             
-            assertNotEquals("For \"" + pd.getName() + "\"", newValue, tReaderMethod.invoke(t));
+            assertNotEquals("For \"" + propName + "\"", newValue, tReaderMethod.invoke(t));
             tc.apply(t);
-            assertEquals("For \"" + pd.getName() + "\"", newValue, tReaderMethod.invoke(t));
+            assertEquals("For \"" + propName + "\"", newValue, tReaderMethod.invoke(t));
         }
     }
     
@@ -705,6 +719,9 @@ public class TemplateConfigurationTest {
             TemplateConfiguration tc = new TemplateConfiguration();
 
             String propName = propDesc.getName();
+            if (!SETTING_ASSIGNMENTS.containsKey(propName)) {
+                throw new AssertionError("SETTING_ASSIGNMENTS doesn't yet contain \"" + propName + "\"");
+            }
             Object value = SETTING_ASSIGNMENTS.get(propName);
             propDesc.getWriteMethod().invoke(tc, value);
             
@@ -905,8 +922,12 @@ public class TemplateConfigurationTest {
         for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(true, true)) {
             TemplateConfiguration tc = new TemplateConfiguration();
             checkAllIsSetFalseExcept(tc, null);
-            pd.getWriteMethod().invoke(tc, SETTING_ASSIGNMENTS.get(pd.getName()));
-            checkAllIsSetFalseExcept(tc, pd.getName());
+            String propName = pd.getName();
+            if (!SETTING_ASSIGNMENTS.containsKey(propName)) {
+                throw new AssertionError("SETTING_ASSIGNMENTS doesn't yet contain \"" + propName + "\"");
+            }
+            pd.getWriteMethod().invoke(tc, SETTING_ASSIGNMENTS.get(propName));
+            checkAllIsSetFalseExcept(tc, propName);
         }
     }