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/12/15 17:16:42 UTC

[freemarker] branch FREEMARKER-35 updated (89e934d -> 6476732)

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

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


    from 89e934d  [FREEMARKER-35] JavaDoc fixes
     new 4571e34  [FREEMARKER-35] Removed new settings that deal with the format of Temporal-s that could be treated as date, time, or date-time types. For those, use the same settings as for java.util.Date-s instead. (Will need more work though, as pattern syntax of SimpleDateFormat and DateTimeFormatter somewhat differs.)
     new 6476732  [FREEMARKER-35] Added automatic adjustment of the format style for OffsetTime, if the time zone has DST, and the style doesn't show the offset.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/main/java/freemarker/core/Configurable.java    | 437 ++++-----------------
 src/main/java/freemarker/core/Environment.java     |   4 -
 .../core/JavaTemplateTemporalFormat.java           | 102 +++--
 src/main/java/freemarker/core/PropertySetting.java |  16 +-
 .../freemarker/core/TemplateConfiguration.java     |  49 ---
 .../java/freemarker/core/_CoreTemporalUtils.java   |  25 +-
 .../freemarker/core/CoercionToTextualTest.java     |   1 -
 .../java/freemarker/core/CoreTemporalUtilTest.java |   4 +-
 .../freemarker/core/TemporalErrorMessagesTest.java |  17 +-
 .../java/freemarker/core/TemporalFormatTest.java   |  21 +-
 .../java/freemarker/core/TemporalFormatTest2.java  |   4 +-
 .../test/templatesuite/templates/temporal.ftl      |  15 +-
 12 files changed, 182 insertions(+), 513 deletions(-)

[freemarker] 02/02: [FREEMARKER-35] Added automatic adjustment of the format style for OffsetTime, if the time zone has DST, and the style doesn't show the offset.

Posted by dd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 6476732d962c888212e5c2084d178d1a46e329c9
Author: ddekany <dd...@apache.org>
AuthorDate: Wed Dec 15 18:10:25 2021 +0100

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

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

[freemarker] 01/02: [FREEMARKER-35] Removed new settings that deal with the format of Temporal-s that could be treated as date, time, or date-time types. For those, use the same settings as for java.util.Date-s instead. (Will need more work though, as pattern syntax of SimpleDateFormat and DateTimeFormatter somewhat differs.)

Posted by dd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 4571e34876d0db0f9da3aad8f9ccf12dd578e34d
Author: ddekany <dd...@apache.org>
AuthorDate: Wed Dec 15 18:08:58 2021 +0100

    [FREEMARKER-35] Removed new settings that deal with the format of Temporal-s that could be treated as date, time, or date-time types. For those, use the same settings as for java.util.Date-s instead. (Will need more work though, as pattern syntax of SimpleDateFormat and DateTimeFormatter somewhat differs.)
---
 src/main/java/freemarker/core/Configurable.java    | 437 ++++-----------------
 src/main/java/freemarker/core/Environment.java     |   4 -
 .../core/JavaTemplateTemporalFormat.java           |   9 +-
 src/main/java/freemarker/core/PropertySetting.java |  16 +-
 .../freemarker/core/TemplateConfiguration.java     |  49 ---
 .../java/freemarker/core/_CoreTemporalUtils.java   |  25 +-
 .../freemarker/core/CoercionToTextualTest.java     |   1 -
 .../java/freemarker/core/CoreTemporalUtilTest.java |   4 +-
 .../freemarker/core/TemporalErrorMessagesTest.java |  17 +-
 .../java/freemarker/core/TemporalFormatTest.java   |  21 +-
 .../java/freemarker/core/TemporalFormatTest2.java  |   4 +-
 .../test/templatesuite/templates/temporal.ftl      |  13 +-
 12 files changed, 118 insertions(+), 482 deletions(-)

diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index c62abfd..385ee87 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -33,11 +33,13 @@ import java.time.OffsetTime;
 import java.time.Year;
 import java.time.YearMonth;
 import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
 import java.time.format.FormatStyle;
 import java.time.temporal.Temporal;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -68,6 +70,7 @@ import freemarker.ext.beans.MemberAccessPolicy;
 import freemarker.template.AttemptExceptionReporter;
 import freemarker.template.Configuration;
 import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.DefaultObjectWrapperBuilder;
 import freemarker.template.ObjectWrapper;
 import freemarker.template.SimpleObjectWrapper;
 import freemarker.template.Template;
@@ -157,34 +160,6 @@ public class Configurable {
     /** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
     public static final String CUSTOM_TEMPORAL_FORMATS_KEY = CUSTOM_TEMPORAL_FORMATS_KEY_SNAKE_CASE;
 
-    public static final String INSTANT_FORMAT_KEY_SNAKE_CASE = "instant_format";
-    public static final String INSTANT_FORMAT_KEY_CAMEL_CASE = "instantFormat";
-    public static final String INSTANT_FORMAT_KEY = INSTANT_FORMAT_KEY_SNAKE_CASE;
-
-    public static final String LOCAL_DATE_FORMAT_KEY_SNAKE_CASE = "local_date_format";
-    public static final String LOCAL_DATE_FORMAT_KEY_CAMEL_CASE = "localDateFormat";
-    public static final String LOCAL_DATE_FORMAT_KEY = LOCAL_DATE_FORMAT_KEY_SNAKE_CASE;
-
-    public static final String LOCAL_DATE_TIME_FORMAT_KEY_SNAKE_CASE = "local_date_time_format";
-    public static final String LOCAL_DATE_TIME_FORMAT_KEY_CAMEL_CASE = "localDateTimeFormat";
-    public static final String LOCAL_DATE_TIME_FORMAT_KEY = LOCAL_DATE_TIME_FORMAT_KEY_SNAKE_CASE;
-
-    public static final String LOCAL_TIME_FORMAT_KEY_SNAKE_CASE = "local_time_format";
-    public static final String LOCAL_TIME_FORMAT_KEY_CAMEL_CASE = "localTimeFormat";
-    public static final String LOCAL_TIME_FORMAT_KEY = LOCAL_TIME_FORMAT_KEY_SNAKE_CASE;
-
-    public static final String OFFSET_DATE_TIME_FORMAT_KEY_SNAKE_CASE = "offset_date_time_format";
-    public static final String OFFSET_DATE_TIME_FORMAT_KEY_CAMEL_CASE = "offsetDateTimeFormat";
-    public static final String OFFSET_DATE_TIME_FORMAT_KEY = OFFSET_DATE_TIME_FORMAT_KEY_SNAKE_CASE;
-
-    public static final String OFFSET_TIME_FORMAT_KEY_SNAKE_CASE = "offset_time_format";
-    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;
@@ -366,19 +341,13 @@ public class Configurable {
         CUSTOM_TEMPORAL_FORMATS_KEY_SNAKE_CASE,
         DATE_FORMAT_KEY_SNAKE_CASE,
         DATETIME_FORMAT_KEY_SNAKE_CASE,
-        INSTANT_FORMAT_KEY_SNAKE_CASE,
         LAZY_AUTO_IMPORTS_KEY_SNAKE_CASE,
         LAZY_IMPORTS_KEY_SNAKE_CASE,
-        LOCAL_DATE_FORMAT_KEY_SNAKE_CASE,
-        LOCAL_DATE_TIME_FORMAT_KEY_SNAKE_CASE,
-        LOCAL_TIME_FORMAT_KEY_SNAKE_CASE,
         LOCALE_KEY_SNAKE_CASE,
         LOG_TEMPLATE_EXCEPTIONS_KEY_SNAKE_CASE,
         NEW_BUILTIN_CLASS_RESOLVER_KEY_SNAKE_CASE,
         NUMBER_FORMAT_KEY_SNAKE_CASE,
         OBJECT_WRAPPER_KEY_SNAKE_CASE,
-        OFFSET_DATE_TIME_FORMAT_KEY_SNAKE_CASE,
-        OFFSET_TIME_FORMAT_KEY_SNAKE_CASE,
         OUTPUT_ENCODING_KEY_SNAKE_CASE,
         SHOW_ERROR_TIPS_KEY_SNAKE_CASE,
         SQL_DATE_AND_TIME_TIME_ZONE_KEY_SNAKE_CASE,
@@ -390,8 +359,7 @@ public class Configurable {
         URL_ESCAPING_CHARSET_KEY_SNAKE_CASE,
         WRAP_UNCHECKED_EXCEPTIONS_KEY_SNAKE_CASE,
         YEAR_FORMAT_KEY_SNAKE_CASE,
-        YEAR_MONTH_FORMAT_KEY_SNAKE_CASE,
-        ZONED_DATE_TIME_FORMAT_KEY_SNAKE_CASE
+        YEAR_MONTH_FORMAT_KEY_SNAKE_CASE
     };
 
     private static final String[] SETTING_NAMES_CAMEL_CASE = new String[] {
@@ -409,19 +377,13 @@ public class Configurable {
         CUSTOM_TEMPORAL_FORMATS_KEY_CAMEL_CASE,
         DATE_FORMAT_KEY_CAMEL_CASE,
         DATETIME_FORMAT_KEY_CAMEL_CASE,
-        INSTANT_FORMAT_KEY_CAMEL_CASE,
         LAZY_AUTO_IMPORTS_KEY_CAMEL_CASE,
         LAZY_IMPORTS_KEY_CAMEL_CASE,
-        LOCAL_DATE_FORMAT_KEY_CAMEL_CASE,
-        LOCAL_DATE_TIME_FORMAT_KEY_CAMEL_CASE,
-        LOCAL_TIME_FORMAT_KEY_CAMEL_CASE,
         LOCALE_KEY_CAMEL_CASE,
         LOG_TEMPLATE_EXCEPTIONS_KEY_CAMEL_CASE,
         NEW_BUILTIN_CLASS_RESOLVER_KEY_CAMEL_CASE,
         NUMBER_FORMAT_KEY_CAMEL_CASE,
         OBJECT_WRAPPER_KEY_CAMEL_CASE,
-        OFFSET_DATE_TIME_FORMAT_KEY_CAMEL_CASE,
-        OFFSET_TIME_FORMAT_KEY_CAMEL_CASE,
         OUTPUT_ENCODING_KEY_CAMEL_CASE,
         SHOW_ERROR_TIPS_KEY_CAMEL_CASE,
         SQL_DATE_AND_TIME_TIME_ZONE_KEY_CAMEL_CASE,
@@ -433,8 +395,7 @@ public class Configurable {
         URL_ESCAPING_CHARSET_KEY_CAMEL_CASE,
         WRAP_UNCHECKED_EXCEPTIONS_KEY_CAMEL_CASE,
         YEAR_FORMAT_KEY_CAMEL_CASE,
-        YEAR_MONTH_FORMAT_KEY_CAMEL_CASE,
-        ZONED_DATE_TIME_FORMAT_KEY_CAMEL_CASE
+        YEAR_MONTH_FORMAT_KEY_CAMEL_CASE
     };
 
     private Configurable parent;
@@ -446,13 +407,6 @@ public class Configurable {
     private String timeFormat;
     private String dateFormat;
     private String dateTimeFormat;
-    private String instantFormat;
-    private String localDateFormat;
-    private String localDateTimeFormat;
-    private String localTimeFormat;
-    private String offsetDateTimeFormat;
-    private String offsetTimeFormat;
-    private String zonedDateTimeFormat;
     private String yearFormat;
     private String yearMonthFormat;
     private TimeZone timeZone;
@@ -528,27 +482,6 @@ public class Configurable {
         dateTimeFormat = "";
         properties.setProperty(DATETIME_FORMAT_KEY, dateTimeFormat);
         
-        instantFormat = JavaTemplateTemporalFormat.MEDIUM;
-        properties.setProperty(INSTANT_FORMAT_KEY, instantFormat);
-
-        localDateFormat = JavaTemplateTemporalFormat.MEDIUM;
-        properties.setProperty(LOCAL_DATE_FORMAT_KEY, localDateFormat);
-
-        localDateTimeFormat = JavaTemplateTemporalFormat.MEDIUM;
-        properties.setProperty(LOCAL_DATE_TIME_FORMAT_KEY, localDateTimeFormat);
-
-        localTimeFormat = JavaTemplateTemporalFormat.MEDIUM;
-        properties.setProperty(LOCAL_TIME_FORMAT_KEY, localTimeFormat);
-
-        offsetDateTimeFormat = JavaTemplateTemporalFormat.MEDIUM;
-        properties.setProperty(OFFSET_DATE_TIME_FORMAT_KEY, offsetDateTimeFormat);
-
-        offsetTimeFormat = JavaTemplateTemporalFormat.LONG;
-        properties.setProperty(OFFSET_TIME_FORMAT_KEY, offsetTimeFormat);
-
-        zonedDateTimeFormat = JavaTemplateTemporalFormat.MEDIUM;
-        properties.setProperty(ZONED_DATE_TIME_FORMAT_KEY, zonedDateTimeFormat);
-
         yearFormat = "iso";
         properties.setProperty(YEAR_FORMAT_KEY, yearFormat);
 
@@ -1225,6 +1158,25 @@ public class Configurable {
      * <p>For the possible values see {@link #setDateTimeFormat(String)}.
      *
      * <p>Defaults to {@code ""}, which is equivalent to {@code "medium"}.
+     *
+     * <p>If temporal support is enabled (see {@link Configuration#setIncompatibleImprovements(Version)} at 2.3.32, and
+     * {@link DefaultObjectWrapperBuilder#setTemporalSupport(boolean)}) this is also used for these {@link Temporal}
+     * classes: {@link LocalTime}, {@link OffsetTime}.
+     *
+     * <p>Note that to format {@link OffsetTime}-s, the format <em>should show the offset</em>, unless you
+     * are sure that the {@link #setTimeZone(TimeZone) timeZone} setting will be a time zone that never used daylight
+     * 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):
+     * <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.
+     *   </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>
+     * </ul>
      */
     public void setTimeFormat(String timeFormat) {
         NullArgumentException.check("timeFormat", timeFormat);
@@ -1348,11 +1300,19 @@ public class Configurable {
      *       format.
      *       
      *   <li><p>{@code "short"}, {@code "medium"}, {@code "long"}, or {@code "full"}, which that has locale-dependent
-     *       meaning defined by the Java platform (see in the documentation of {@link java.text.DateFormat}).
+     *       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
      *       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).
+     *
      *   <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()}
      *       is at least 2.3.24, or there's any custom formats defined (even if custom number format). The format of
@@ -1363,6 +1323,14 @@ public class Configurable {
      * </ul> 
      * 
      * <p>Defaults to {@code ""}, which is equivalent to {@code "medium_medium"}.
+     *
+     * <p>If temporal support is enabled (see {@link Configuration#setIncompatibleImprovements(Version)} at 2.3.32, and
+     * {@link DefaultObjectWrapperBuilder#setTemporalSupport(boolean)}) this is also used for these {@link Temporal}
+     * classes: {@link Instance}, {@link LocalDateTime}, {@link OffsetDateTime}, {@link ZonedDateTime}.
+     * For non-{@code Local} {@link Temporal}-s FreeMarker will detect if the format doesn't show the offset or zone (as
+     * is typically the case for the {@code "medium"} format), and then before formatting it will convert the value to
+     * the time zone specified in the {@link #setTimeZone(TimeZone) timeZone} setting of FreeMarker, or when parsing
+     * a string it will assume that it uses that time zone.
      */
     public void setDateTimeFormat(String dateTimeFormat) {
         NullArgumentException.check("dateTimeFormat", dateTimeFormat);
@@ -1387,264 +1355,6 @@ public class Configurable {
     }
 
     /**
-     * Sets the format used to convert {@link java.time.Instant}-s to strings, also the format that
-     * {@code someString?instant} will use to parse strings.
-     *
-     * <p>Defaults to {@code "medium"}, which means {@link FormatStyle#MEDIUM}.
-     *
-     * @param instantFormat
-     *     See the similar parameter of {@link #setZonedDateTimeFormat(String)};
-     *     {@code iso}/{@code xs} will show the time offset.
-     *
-     * @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;
-    }
-
-    /**
-     * 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;
-    }
-
-    /**
-     * Sets the format used to convert {@link java.time.LocalDate}-s to strings, also the format that
-     * {@code someString?local_date} will use to parse strings.
-     *
-     * <p>Defaults to {@code "medium"}, which means {@link FormatStyle#MEDIUM}.
-     *
-     * @param localDateFormat
-     *     See the similar parameter of {@link #setZonedDateTimeFormat(String)};
-     *     {@code iso}/{@code xs} will not show the time part.
-     *
-     * @since 2.3.32
-     */
-    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;
-    }
-
-    /**
-     * Sets the format used to convert {@link java.time.LocalDateTime}-s to strings, also the format that
-     * {@code someString?local_date_time} will use to parse strings.
-     *
-     * <p>Defaults to {@code "medium"}, which means {@link FormatStyle#MEDIUM}.
-     *
-     * @param localDateTimeFormat
-     *     See the similar parameter of {@link #setZonedDateTimeFormat(String)};
-     *     {@code iso}/{@code xs} will not show an offset.
-     *
-     * @since 2.3.32
-     */
-    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;
-    }
-
-    /**
-     * Sets the format used to convert {@link java.time.LocalTime}-s to strings, also the format that
-     * {@code someString?local_time} will use to parse strings.
-     *
-     * <p>Defaults to {@code "medium"}, which means {@link FormatStyle#MEDIUM}.
-     *
-     * @param localTimeFormat
-     *     See the similar parameter of {@link #setZonedDateTimeFormat(String)};
-     *     {@code iso}/{@code xs} will not show the time offset.
-     *
-     * @since 2.3.32
-     */
-    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;
-    }
-
-    /**
-     * Sets the format used to convert {@link java.time.OffsetDateTime}-s to strings, also the format that
-     * {@code someString?offset_date_time} will use to parse strings. FreeMarker will detect if the format doesn't
-     * show the offset (as is typically the case for the {@code "medium"} format), and then it will convert the value to
-     * the time zone specified in the {@link #setTimeZone(TimeZone) timeZone} setting of FreeMarker.
-     *
-     * <p>Defaults to {@code "medium"}, which means {@link FormatStyle#MEDIUM}, which usually doesn't show the time
-     * offset; see the parameter JavaDoc for more.
-     *
-     * @param offsetDateTimeFormat
-     *     See the similar parameter of {@link #setZonedDateTimeFormat(String)}.
-     *
-     * @since 2.3.32
-     */
-    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;
-    }
-
-    /**
-     * Sets the format used to convert {@link java.time.OffsetTime}-s to strings, also the format that
-     * {@code someString?offset_time} will use to parse strings. The format <b>should show the offset</b>, unless you
-     * are sure that {@link #setTimeZone(TimeZone) timeZone} setting will be a zone that has no daylight 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 if a daylight saving is possible.
-     *
-     * <p>Defaults to {@code "long"}, which means {@link FormatStyle#LONG}, which usually show the time offset; see the
-     * parameter JavaDoc for more.
-     *
-     * @param offsetTimeFormat
-     *     See the similar parameter of {@link #setZonedDateTimeFormat(String)}, but it <b>must show the offset</b>
-     *     (see earlier why).
-     *
-     * @since 2.3.32
-     */
-    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;
-    }
-
-    /**
-     * Sets the format used to convert {@link java.time.ZonedDateTime}-s to strings, also the format that
-     * {@code someString?offset_date_time} will use to parse strings. FreeMarker will detect if the format doesn't
-     * show the zone or offset (as is typically the case for the {@code "medium"} format), and then it will convert the
-     * value to the time zone specified in the {@link #setTimeZone(TimeZone) timeZone} setting of FreeMarker.
-     *
-     * <p>Defaults to {@code "medium"}, which means {@link FormatStyle#MEDIUM}, which usually doesn't show the time
-     * zone; see the parameter JavaDoc for more.
-     *
-     * @param zonedDateTimeFormat
-     *     One of:
-     *     <ul>
-     *         <li>{@code "iso"}: ISO-8601 format (like {@code 2021-09-29T13:00:05.2})
-     *         <li>{@code "xs"}: XSD format (same as ISO-8601, but parsing is more restrictive)
-     *         <li>{@code "short"}, {@code "medium"}, {@code "long"}, {@code "full"}, or two of these connected with
-     *             an {@code "_"}: Refers to the {@link FormatStyle} constants. When in a pair, as in
-     *             {@code "medium_long"}, the 1st style refers to the date part, and the 2nd style to the time part.
-     *             Java doesn't specify what these styles actually mean. However, experience with Java 8 shows
-     *             that "short" and "medium" will not show the time zone or time offset (which then triggers the zone
-     *             conversion mentioned earlier), and will show months with numbers, while "long" and "full" will show
-     *             the zone and/or offset, and shows months with their names. (Also "long" and "full" before Java 9
-     *             fails for {@link LocalDateTime} and {@link LocalTime}, because of bug JDK-8085887.)
-     *          <li>Anything that starts with {@code "@"} followed by a letter is interpreted as a custom temporal
-     *              format ({@link #setCustomTemporalFormats(Map)}).
-     *     </ul>
-     *
-     * @since 2.3.32
-     */
-    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;
-    }
-
-    /**
      * Sets the format used to convert {@link java.time.Year}-s to strings, also the format that
      * {@code someString?local_time} will use to parse strings.
      *
@@ -1729,26 +1439,24 @@ public class Configurable {
      */
     public String getTemporalFormat(Class<? extends Temporal> temporalClass) {
         Objects.requireNonNull(temporalClass);
-        if (temporalClass == Instant.class) {
-            return getInstantFormat();
+        // The temporal classes are final (for now at least), so we can use == operator instead of instanceof.
+        if (temporalClass == Instant.class
+                || temporalClass == LocalDateTime.class
+                || temporalClass == ZonedDateTime.class
+                || temporalClass == OffsetDateTime.class) {
+            return getDateTimeFormat();
         } else if (temporalClass == LocalDate.class) {
-            return getLocalDateFormat();
-        } else if (temporalClass == LocalDateTime.class) {
-            return getLocalDateTimeFormat();
-        } else if (temporalClass == LocalTime.class) {
-            return getLocalTimeFormat();
-        } else if (temporalClass == OffsetDateTime.class) {
-            return getOffsetDateTimeFormat();
-        } else if (temporalClass == OffsetTime.class) {
-            return getOffsetTimeFormat();
-        } else if (temporalClass == ZonedDateTime.class) {
-            return getZonedDateTimeFormat();
+            return getDateFormat();
+        } else if (temporalClass == LocalTime.class || temporalClass == OffsetTime.class) {
+            return getTimeFormat();
         } else if (temporalClass == Year.class) {
             return getYearFormat();
         } else if (temporalClass == YearMonth.class) {
             return getYearMonthFormat();
         } else {
-            Class<? extends Temporal> normTemporalClass = _CoreTemporalUtils.normalizeSupportedTemporalClass(temporalClass);
+            // Handle the unlikely situation that in some future Java version we can have subclasses.
+            Class<? extends Temporal> normTemporalClass =
+                    _CoreTemporalUtils.normalizeSupportedTemporalClass(temporalClass);
             if (normTemporalClass == temporalClass) {
                 throw new IllegalArgumentException("There's no temporal format setting for this class: "
                         + temporalClass.getName());
@@ -1792,7 +1500,13 @@ public class Configurable {
     /**
      * Associates names with formatter factories, which then can be referred by the {@link #setDateTimeFormat(String)
      * date_format}, {@link #setDateTimeFormat(String) time_format}, and {@link #setDateTimeFormat(String)
-     * datetime_format} settings with values starting with <code>@<i>name</i></code>. Beware, if you specify any custom
+     * datetime_format} settings with values starting with <code>@<i>name</i></code>.
+     *
+     * <p>It's important that the formats you set here will be only used when formatting {@link Date}-s, not when
+     * formatting {@link Temporal}-s. For the later, use {@link #setCustomTemporalFormats(Map)}. Ideally, you set the
+     * same custom formatter names with both methods.
+     *
+     * <p>Note that if you specify any custom
      * formats here, an initial {@code @} followed by a letter will have special meaning in number/date/time/datetime
      * format strings, even if {@link Configuration#getIncompatibleImprovements() incompatible_improvements} is less
      * than 2.3.24 (starting with {@link Configuration#getIncompatibleImprovements() incompatible_improvements} 2.3.24
@@ -1801,6 +1515,8 @@ public class Configurable {
      * @param customDateFormats
      *            Can't be {@code null}. The name must start with an UNICODE letter, and can only contain UNICODE
      *            letters and digits.
+     *
+     * @see #setCustomTemporalFormats(Map)
      * 
      * @since 2.3.24
      */
@@ -1871,14 +1587,19 @@ public class Configurable {
     }
 
     /**
-     * Associates names with formatter factories, which then can be referred by the various temporal format settings
-     * (like {@link #setLocalDateTimeFormat(String) local_date_time_format},
-     * {@link #setLocalDateFormat(String) local_date_format}, {@link #setLocalTimeFormat(String) local_time_format},
-     * and so on) a value starting with <code>@<i>name</i></code>.
+     * Associates names with {@link Temporal} formatter factories, which then can be referred by the
+     * {@link #setDateTimeFormat(String) date_time_format}, {@link #setDateFormat(String) date_format}, and
+     * {@link #setTimeFormat(String) time_format} settings, with values starting with <code>@<i>name</i></code>.
+     *
+     * <p>It's important that the formats you set here will be only used when formatting {@link Temporal}-s, not when
+     * formatting {@link Date}-s. For the later, use {@link #setCustomDateFormats(Map)}. Ideally, you set the same
+     * custom formatter names with both methods.
      *
      * @param customTemporalFormats
      *            Can't be {@code null}. The name must start with an UNICODE letter, and can only contain UNICODE
      *            letters and digits.
+     *            
+     * @see #setCustomDateFormats(Map) 
      *
      * @since 2.3.32
      */
@@ -3236,24 +2957,10 @@ public class Configurable {
                 setDateFormat(value);
             } else if (DATETIME_FORMAT_KEY_SNAKE_CASE.equals(name) || DATETIME_FORMAT_KEY_CAMEL_CASE.equals(name)) {
                 setDateTimeFormat(value);
-            } else if (INSTANT_FORMAT_KEY_SNAKE_CASE.equals(name) || INSTANT_FORMAT_KEY_CAMEL_CASE.equals(name)) {
-                this.instantFormat = value;
-            } else if (LOCAL_DATE_FORMAT_KEY_SNAKE_CASE.equals(name) || LOCAL_DATE_FORMAT_KEY_CAMEL_CASE.equals(name)) {
-                this.localDateFormat = value;
-            } else if (LOCAL_DATE_TIME_FORMAT_KEY_SNAKE_CASE.equals(name) || LOCAL_DATE_TIME_FORMAT_KEY_CAMEL_CASE.equals(name)) {
-                this.localDateTimeFormat = value;
-            } else if (LOCAL_TIME_FORMAT_KEY_SNAKE_CASE.equals(name) || LOCAL_TIME_FORMAT_KEY_CAMEL_CASE.equals(name)) {
-                this.localTimeFormat = value;
-            } else if (OFFSET_DATE_TIME_FORMAT_KEY_SNAKE_CASE.equals(name) || OFFSET_DATE_TIME_FORMAT_KEY_CAMEL_CASE.equals(name)) {
-                this.offsetDateTimeFormat = value;
-            } else if (OFFSET_TIME_FORMAT_KEY_SNAKE_CASE.equals(name) || OFFSET_TIME_FORMAT_KEY_CAMEL_CASE.equals(name)) {
-                this.offsetTimeFormat = value;
             } else if (YEAR_FORMAT_KEY_SNAKE_CASE.equals(name) || YEAR_FORMAT_KEY_CAMEL_CASE.equals(name)) {
                 this.yearFormat = value;
             } else if (YEAR_MONTH_FORMAT_KEY_SNAKE_CASE.equals(name) || YEAR_MONTH_FORMAT_KEY_CAMEL_CASE.equals(name)) {
                 this.yearMonthFormat = value;
-            } else if (ZONED_DATE_TIME_FORMAT_KEY_SNAKE_CASE.equals(name) || ZONED_DATE_TIME_FORMAT_KEY_CAMEL_CASE.equals(name)) {
-                this.zonedDateTimeFormat = value;
             } else if (CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE.equals(name)
                     || CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE.equals(name)) {
                 Map map = (Map) _ObjectBuilderSettingEvaluator.eval(
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index 1208e79..5a80b2a 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -2449,10 +2449,6 @@ public final class Environment extends Configurable {
                 throw new UndefinedCustomFormatException(
                         "No custom temporal format was defined with name " + StringUtil.jQuote(name));
             }
-        } else if (formatStringLen == 0) {
-            // TODO [FREEMARKER-35] This is not right, but for now we mimic what TemporalUtils did
-            formatParams = formatString;
-            formatFactory = ToStringTemplateTemporalFormatFactory.INSTANCE;
         } else {
             formatParams = formatString;
             formatFactory = JavaTemplateTemporalFormatFactory.INSTANCE;
diff --git a/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java b/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
index dbb03be..f07fbcc 100644
--- a/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
@@ -61,8 +61,10 @@ class JavaTemplateTemporalFormat extends TemplateTemporalFormat {
     static final String LONG = "long";
     static final String FULL = "full";
     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.
     private static final Pattern FORMAT_STYLE_PATTERN = Pattern.compile(
-            ANY_FORMAT_STYLE + "(?:_" + ANY_FORMAT_STYLE + ")?");
+            "(?:" + ANY_FORMAT_STYLE + "(?:_" + ANY_FORMAT_STYLE + ")?)?");
 
     private final DateTimeFormatter dateTimeFormatter;
     private final ZoneId zoneId;
@@ -80,7 +82,10 @@ class JavaTemplateTemporalFormat extends TemplateTemporalFormat {
 
         DateTimeFormatter dateTimeFormatter;
         if (isFormatStyleString) {
-            FormatStyle datePartFormatStyle = FormatStyle.valueOf(formatStylePatternMatcher.group(1).toUpperCase(Locale.ROOT));
+            String group1 = formatStylePatternMatcher.group(1);
+            FormatStyle datePartFormatStyle = group1 != null
+                    ? FormatStyle.valueOf(group1.toUpperCase(Locale.ROOT))
+                    : FormatStyle.MEDIUM;
             String group2 = formatStylePatternMatcher.group(2);
             FormatStyle timePartFormatStyle = group2 != null
                     ? FormatStyle.valueOf(group2.toUpperCase(Locale.ROOT))
diff --git a/src/main/java/freemarker/core/PropertySetting.java b/src/main/java/freemarker/core/PropertySetting.java
index 2970b84..365662f 100644
--- a/src/main/java/freemarker/core/PropertySetting.java
+++ b/src/main/java/freemarker/core/PropertySetting.java
@@ -49,21 +49,9 @@ final class PropertySetting extends TemplateElement {
             Configurable.DATE_FORMAT_KEY_SNAKE_CASE,
             Configurable.DATETIME_FORMAT_KEY_CAMEL_CASE,
             Configurable.DATETIME_FORMAT_KEY_SNAKE_CASE,
-            Configurable.INSTANT_FORMAT_KEY_CAMEL_CASE,
-            Configurable.INSTANT_FORMAT_KEY_SNAKE_CASE,
-            Configurable.LOCAL_DATE_FORMAT_KEY_CAMEL_CASE,
-            Configurable.LOCAL_DATE_TIME_FORMAT_KEY_CAMEL_CASE,
-            Configurable.LOCAL_TIME_FORMAT_KEY_CAMEL_CASE,
-            Configurable.LOCAL_DATE_FORMAT_KEY_SNAKE_CASE,
-            Configurable.LOCAL_DATE_TIME_FORMAT_KEY_SNAKE_CASE,
-            Configurable.LOCAL_TIME_FORMAT_KEY_SNAKE_CASE,
             Configurable.LOCALE_KEY,
             Configurable.NUMBER_FORMAT_KEY_CAMEL_CASE,
             Configurable.NUMBER_FORMAT_KEY_SNAKE_CASE,
-            Configurable.OFFSET_DATE_TIME_FORMAT_KEY_CAMEL_CASE,
-            Configurable.OFFSET_TIME_FORMAT_KEY_CAMEL_CASE,
-            Configurable.OFFSET_DATE_TIME_FORMAT_KEY_SNAKE_CASE,
-            Configurable.OFFSET_TIME_FORMAT_KEY_SNAKE_CASE,
             Configurable.OUTPUT_ENCODING_KEY_CAMEL_CASE,
             Configurable.OUTPUT_ENCODING_KEY_SNAKE_CASE,
             Configurable.SQL_DATE_AND_TIME_TIME_ZONE_KEY_CAMEL_CASE,
@@ -77,9 +65,7 @@ final class PropertySetting extends TemplateElement {
             Configurable.YEAR_FORMAT_KEY_CAMEL_CASE,
             Configurable.YEAR_MONTH_FORMAT_KEY_CAMEL_CASE,
             Configurable.YEAR_FORMAT_KEY_SNAKE_CASE,
-            Configurable.YEAR_MONTH_FORMAT_KEY_SNAKE_CASE,
-            Configurable.ZONED_DATE_TIME_FORMAT_KEY_CAMEL_CASE,
-            Configurable.ZONED_DATE_TIME_FORMAT_KEY_SNAKE_CASE
+            Configurable.YEAR_MONTH_FORMAT_KEY_SNAKE_CASE
     };
 
 
diff --git a/src/main/java/freemarker/core/TemplateConfiguration.java b/src/main/java/freemarker/core/TemplateConfiguration.java
index 7e41d7f..f80d728 100644
--- a/src/main/java/freemarker/core/TemplateConfiguration.java
+++ b/src/main/java/freemarker/core/TemplateConfiguration.java
@@ -193,27 +193,6 @@ 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());
         }
@@ -366,27 +345,6 @@ 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());
         }
@@ -741,13 +699,6 @@ public final class TemplateConfiguration extends Configurable implements ParserC
                 || isCustomNumberFormatsSet()
                 || isDateFormatSet()
                 || isDateTimeFormatSet()
-                || isInstantFormatSet()
-                || isLocalDateFormatSet()
-                || isLocalTimeFormatSet()
-                || isLocalDateTimeFormatSet()
-                || isOffsetTimeFormatSet()
-                || isOffsetDateTimeFormatSet()
-                || isZonedDateTimeFormatSet()
                 || isYearFormatSet()
                 || isYearMonthFormatSet()
                 || isLazyImportsSet()
diff --git a/src/main/java/freemarker/core/_CoreTemporalUtils.java b/src/main/java/freemarker/core/_CoreTemporalUtils.java
index f95906c..ca1032b 100644
--- a/src/main/java/freemarker/core/_CoreTemporalUtils.java
+++ b/src/main/java/freemarker/core/_CoreTemporalUtils.java
@@ -28,12 +28,10 @@ import java.time.OffsetDateTime;
 import java.time.OffsetTime;
 import java.time.Year;
 import java.time.YearMonth;
-import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.time.temporal.Temporal;
 import java.util.Arrays;
 import java.util.List;
-import java.util.stream.Stream;
 
 import freemarker.template.Configuration;
 
@@ -105,20 +103,15 @@ public class _CoreTemporalUtils {
      */
     public static String temporalClassToFormatSettingName(Class<? extends Temporal> temporalClass) {
         temporalClass = normalizeSupportedTemporalClass(temporalClass);
-        if (temporalClass == Instant.class) {
-            return Configuration.INSTANT_FORMAT_KEY;
+        if (temporalClass == Instant.class
+                || temporalClass == LocalDateTime.class
+                || temporalClass == ZonedDateTime.class
+                || temporalClass == OffsetDateTime.class) {
+            return Configuration.DATETIME_FORMAT_KEY;
         } else if (temporalClass == LocalDate.class) {
-            return Configuration.LOCAL_DATE_FORMAT_KEY;
-        } else if (temporalClass == LocalDateTime.class) {
-            return Configuration.LOCAL_DATE_TIME_FORMAT_KEY;
-        } else if (temporalClass == LocalTime.class) {
-            return Configuration.LOCAL_TIME_FORMAT_KEY;
-        } else if (temporalClass == OffsetDateTime.class) {
-            return Configuration.OFFSET_DATE_TIME_FORMAT_KEY;
-        } else if (temporalClass == OffsetTime.class) {
-            return Configuration.OFFSET_TIME_FORMAT_KEY;
-        } else if (temporalClass == ZonedDateTime.class) {
-            return Configuration.ZONED_DATE_TIME_FORMAT_KEY;
+            return Configuration.DATE_FORMAT_KEY;
+        } else if (temporalClass == LocalTime.class || temporalClass == OffsetTime.class) {
+            return Configuration.TIME_FORMAT_KEY;
         } else if (temporalClass == YearMonth.class) {
             return Configuration.YEAR_MONTH_FORMAT_KEY;
         } else if (temporalClass == Year.class) {
@@ -127,5 +120,5 @@ public class _CoreTemporalUtils {
             throw new IllegalArgumentException("Unsupported temporal class: " + temporalClass.getName());
         }
     }
-    
+
 }
diff --git a/src/test/java/freemarker/core/CoercionToTextualTest.java b/src/test/java/freemarker/core/CoercionToTextualTest.java
index efa015a..1f121a8 100644
--- a/src/test/java/freemarker/core/CoercionToTextualTest.java
+++ b/src/test/java/freemarker/core/CoercionToTextualTest.java
@@ -138,7 +138,6 @@ public class CoercionToTextualTest extends TemplateTest {
         cfg.setCustomTemporalFormats(Collections.singletonMap("HI", HTMLISOTemplateTemporalFormatFactory.INSTANCE));
         cfg.setNumberFormat("@G 3");
         cfg.setDateTimeFormat("@HI");
-        cfg.setInstantFormat("@HI");
         cfg.setBooleanFormat("y,n");
         cfg.setTimeZone(DateUtil.UTC);
 
diff --git a/src/test/java/freemarker/core/CoreTemporalUtilTest.java b/src/test/java/freemarker/core/CoreTemporalUtilTest.java
index 2eab1f5..36fd589 100644
--- a/src/test/java/freemarker/core/CoreTemporalUtilTest.java
+++ b/src/test/java/freemarker/core/CoreTemporalUtilTest.java
@@ -19,6 +19,7 @@
 
 package freemarker.core;
 
+import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
 import java.time.chrono.ChronoLocalDate;
@@ -62,8 +63,9 @@ public class CoreTemporalUtilTest {
 
         Set<String> uniqueSettingNames = new HashSet<>();
         for (Class<? extends Temporal> supportedTemporalClass : _CoreTemporalUtils.SUPPORTED_TEMPORAL_CLASSES) {
-            assertTrue(uniqueSettingNames.add(_CoreTemporalUtils.temporalClassToFormatSettingName(supportedTemporalClass)));
+            uniqueSettingNames.add(_CoreTemporalUtils.temporalClassToFormatSettingName(supportedTemporalClass));
         }
+        assertThat(uniqueSettingNames.size(), equalTo(_CoreTemporalUtils.SUPPORTED_TEMPORAL_CLASSES.size() - 4));
         assertTrue(uniqueSettingNames.stream().allMatch(it -> cfg.getSettingNames(false).contains(it)));
 
         try {
diff --git a/src/test/java/freemarker/core/TemporalErrorMessagesTest.java b/src/test/java/freemarker/core/TemporalErrorMessagesTest.java
index 1448d25..201062d 100644
--- a/src/test/java/freemarker/core/TemporalErrorMessagesTest.java
+++ b/src/test/java/freemarker/core/TemporalErrorMessagesTest.java
@@ -20,6 +20,7 @@
 package freemarker.core;
 
 import java.time.LocalTime;
+import java.time.YearMonth;
 
 import org.junit.Test;
 
@@ -42,19 +43,19 @@ public class TemporalErrorMessagesTest extends TemplateTest {
 
     @Test
     public void testDefaultFormatStringBadFormatString() throws TemplateException {
-        getConfiguration().setSetting("local_time_format", "ABCDEF");
-        addToDataModel("t", LocalTime.now());
-        assertErrorContains("${t}", "local_time_format", "ABCDEF");
-        assertErrorContains("${t?string}", "local_time_format", "ABCDEF");
+        getConfiguration().setSetting("year_month_format", "ABCDEF");
+        addToDataModel("t", YearMonth.now());
+        assertErrorContains("${t}", "year_month", "ABCDEF");
+        assertErrorContains("${t?string}", "year_month", "ABCDEF");
     }
 
     @Test
     public void testDefaultFormatStringIncompatibleFormatString() throws TemplateException {
-        getConfiguration().setSetting("local_time_format", "yyyy-HH");
-        addToDataModel("t", LocalTime.now());
+        getConfiguration().setSetting("year_month_format", "yyyy-mm"); // Deliberately wrong: "mm" is minutes
+        addToDataModel("t", YearMonth.now());
         // TODO [FREEMARKER-35] Should contain "local_time_format" too
-        assertErrorContains("${t}", "Failed to format temporal value", "yyyy-HH", "YearOfEra");
-        assertErrorContains("${t?string}", "Failed to format temporal value", "yyyy-HH", "YearOfEra");
+        assertErrorContains("${t}", "Failed to format temporal value", "yyyy-mm", "MinuteOfHour");
+        assertErrorContains("${t?string}", "Failed to format temporal value", "yyyy-mm", "MinuteOfHour");
     }
 
 }
diff --git a/src/test/java/freemarker/core/TemporalFormatTest.java b/src/test/java/freemarker/core/TemporalFormatTest.java
index 3e0e3ed..83268e0 100644
--- a/src/test/java/freemarker/core/TemporalFormatTest.java
+++ b/src/test/java/freemarker/core/TemporalFormatTest.java
@@ -62,7 +62,7 @@ public class TemporalFormatTest {
                 "11:00",
                 formatTemporal(
                         conf -> {
-                            conf.setOffsetTimeFormat("HH:mm");
+                            conf.setTimeFormat("HH:mm");
                             conf.setTimeZone(zoneWithoutDST);
                         },
                         offsetTime));
@@ -72,7 +72,7 @@ public class TemporalFormatTest {
                     "11:00",
                     formatTemporal(
                             conf -> {
-                                conf.setOffsetTimeFormat("HH:mm");
+                                conf.setTimeFormat("HH:mm");
                                 conf.setTimeZone(zoneWithDST);
                             },
                             offsetTime));
@@ -85,7 +85,7 @@ public class TemporalFormatTest {
                 "10:00+01",
                 formatTemporal(
                         conf -> {
-                            conf.setOffsetTimeFormat("HH:mmX");
+                            conf.setTimeFormat("HH:mmX");
                             conf.setTimeZone(zoneWithDST);
                         },
                         offsetTime));
@@ -94,7 +94,7 @@ public class TemporalFormatTest {
                 "10:00+01",
                 formatTemporal(
                         conf -> {
-                            conf.setOffsetTimeFormat("HH:mmX");
+                            conf.setTimeFormat("HH:mmX");
                             conf.setTimeZone(zoneWithoutDST);
                         },
                         offsetTime));
@@ -128,9 +128,7 @@ public class TemporalFormatTest {
                         + "2021-12-30 10:30, 2021-12-30 08:30, 2021-12-30 15:30",
                 formatTemporal(
                         conf -> {
-                            conf.setLocalDateTimeFormat("yyyy-MM-dd HH:mm");
-                            conf.setOffsetDateTimeFormat("yyyy-MM-dd HH:mm");
-                            conf.setZonedDateTimeFormat("yyyy-MM-dd HH:mm");
+                            conf.setDateTimeFormat("yyyy-MM-dd HH:mm");
                             conf.setTimeZone(gbZone);
                         },
                         summerLocalDateTime, summerOffsetDateTime, summerZonedDateTime,
@@ -140,9 +138,7 @@ public class TemporalFormatTest {
                         + "2021-12-30 10:30, 2021-12-30 08:30, 2021-12-30 15:30",
                 formatTemporal(
                         conf -> {
-                            conf.setLocalDateTimeFormat("yyyy-MM-dd HH:mm");
-                            conf.setOffsetDateTimeFormat("yyyy-MM-dd HH:mm");
-                            conf.setZonedDateTimeFormat("yyyy-MM-dd HH:mm");
+                            conf.setDateTimeFormat("yyyy-MM-dd HH:mm");
                             conf.setTimeZone(DateUtil.UTC);
                         },
                         summerLocalDateTime, summerOffsetDateTime, summerZonedDateTime,
@@ -154,8 +150,7 @@ public class TemporalFormatTest {
                         + "2021-12-30 10:30+02, 2021-12-30 10:30-05",
                 formatTemporal(
                         conf -> {
-                            conf.setOffsetDateTimeFormat("yyyy-MM-dd HH:mmX");
-                            conf.setZonedDateTimeFormat("yyyy-MM-dd HH:mmX");
+                            conf.setDateTimeFormat("yyyy-MM-dd HH:mmX");
                             conf.setTimeZone(gbZone);
                         },
                         summerOffsetDateTime, summerZonedDateTime,
@@ -167,7 +162,7 @@ public class TemporalFormatTest {
         try {
             formatTemporal(
                     conf -> {
-                        conf.setLocalDateTimeFormat("yyyy-MM-dd HH:mmX");
+                        conf.setDateTimeFormat("yyyy-MM-dd HH:mmX");
                     },
                     LocalDateTime.of(2021, 10, 30, 1, 2));
             fail();
diff --git a/src/test/java/freemarker/core/TemporalFormatTest2.java b/src/test/java/freemarker/core/TemporalFormatTest2.java
index c519c68..1329579 100644
--- a/src/test/java/freemarker/core/TemporalFormatTest2.java
+++ b/src/test/java/freemarker/core/TemporalFormatTest2.java
@@ -61,12 +61,12 @@ public class TemporalFormatTest2 extends TemplateTest {
                 "${d?string.@epoch} ${d?string.@epoch} <#setting locale='de_DE'>${d?string.@epoch}",
                 "123456789 123456789 123456789");
 
-        getConfiguration().setOffsetDateTimeFormat("@epoch");
+        getConfiguration().setDateTimeFormat("@epoch");
         assertOutput(
                 "${d} ${d?string} <#setting locale='de_DE'>${d}",
                 "123456789 123456789 123456789");
 
-        getConfiguration().setOffsetDateTimeFormat("@htmlIso");
+        getConfiguration().setDateTimeFormat("@htmlIso");
         assertOutput(
                 "${d} ${d?string} <#setting locale='de_DE'>${d}",
                 "1970-01-02<span class='T'>T</span>10:17:36.789Z "
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/temporal.ftl b/src/test/resources/freemarker/test/templatesuite/templates/temporal.ftl
index cdadba8..db30dcf 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/temporal.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/temporal.ftl
@@ -16,6 +16,7 @@
   specific language governing permissions and limitations
   under the License.
 -->
+<@assertEquals expected="Apr 5, 2003 7:07:08 AM" actual=dateTime?string />
 <@assertEquals expected="Apr 5, 2003 7:07:08 AM" actual=instant?string />
 <@assertEquals expected="Apr 5, 2003 6:07:08 AM" actual=localDateTime?string />
 <@assertEquals expected="Apr 5, 2003" actual=localDate?string />
@@ -117,19 +118,19 @@
 
 
 <#setting locale="en_US">
-<#setting instantFormat="yyyy MMM dd HH:mm:ss">
+<#setting datetimeFormat="yyyy MMM dd HH:mm:ss">
 <@assertEquals expected="2003 Apr 05 01:07:08" actual=instant?string />
-<#setting localDateTimeFormat="yyyy MMM dd HH:mm:ss">
+<#setting datetimeFormat="yyyy MMM dd HH:mm:ss">
 <@assertEquals expected="2003 Apr 05 06:07:08" actual=localDateTime?string />
-<#setting localDateFormat="yyyy MMM dd">
+<#setting dateFormat="yyyy MMM dd">
 <@assertEquals expected="2003 Apr 05" actual=localDate?string />
-<#setting localDateTimeFormat="HH:mm:ss">
+<#setting datetimeFormat="HH:mm:ss">
 <@assertEquals expected="6:07:08 AM" actual=localTime?string />
-<#setting offsetDateTimeFormat="yyyy MMM dd HH:mm:ss">
+<#setting datetimeFormat="yyyy MMM dd HH:mm:ss">
 <@assertEquals expected="2003 Apr 05 01:07:08" actual=offsetDateTime?string />
 <#setting yearFormat="yyyy">
 <@assertEquals expected="2003" actual=year?string />
 <#setting yearMonthFormat="yyyy MMM">
 <@assertEquals expected="2003 Apr" actual=yearMonth?string />
-<#setting zonedDateTimeFormat="yyyy MMM dd HH:mm:ss">
+<#setting datetimeFormat="yyyy MMM dd HH:mm:ss">
 <@assertEquals expected="2003 Apr 05 01:07:08" actual=zonedDateTime?string />