You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2022/02/06 11:23:46 UTC

[struts] 01/01: WW-5016 Adds support for LocalDate and adjusts tests to use the new Java 8 API

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

lukaszlenart pushed a commit to branch WW-5016-uses-proper-format
in repository https://gitbox.apache.org/repos/asf/struts.git

commit 804e1546f149174f6e88d69b77bd3fa9e8b9a76d
Author: Lukasz Lenart <lu...@apache.org>
AuthorDate: Sun Feb 6 12:23:39 2022 +0100

    WW-5016 Adds support for LocalDate and adjusts tests to use the new Java 8 API
---
 .../java/org/apache/struts2/components/Date.java   |  44 ++--
 .../apache/struts2/views/jsp/ui/DateTagTest.java   | 228 +++++++++++++--------
 2 files changed, 165 insertions(+), 107 deletions(-)

diff --git a/core/src/main/java/org/apache/struts2/components/Date.java b/core/src/main/java/org/apache/struts2/components/Date.java
index 5f65d9c..ec9bb19 100644
--- a/core/src/main/java/org/apache/struts2/components/Date.java
+++ b/core/src/main/java/org/apache/struts2/components/Date.java
@@ -29,6 +29,7 @@ import org.apache.struts2.views.annotations.StrutsTagAttribute;
 import java.io.IOException;
 import java.io.Writer;
 import java.time.Instant;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
@@ -40,7 +41,7 @@ import java.util.List;
 
 /**
  * <!-- START SNIPPET: javadoc -->
- *
+ * <p>
  * Format Date object in different ways.
  * <p>
  * The date tag will allow you to format a Date in a quick and easy way.
@@ -59,6 +60,12 @@ import java.util.List;
  * </p>
  *
  * <p>
+ * <b>Note</b>: Since Struts 2.6 a new Java 8 API has been used to format the Date, it's based on
+ * <a href="https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html">DateTimeFormatter</a>
+ * which uses a bit different patterns.
+ * </p>
+ *
+ * <p>
  * Configurable attributes are:
  * </p>
  *
@@ -130,8 +137,8 @@ import java.util.List;
  *      <td>if one is not found DateFormat.MEDIUM format will be used</td>
  *   </tr>
  * </table>
- *
- *
+ * <p>
+ * <p>
  * <!-- END SNIPPET: javadoc -->
  *
  * <p><b>Examples</b></p>
@@ -145,9 +152,8 @@ import java.util.List;
  * </pre>
  *
  * <code>Date</code>
- *
  */
-@StrutsTag(name="date", tldBodyContent="empty", tldTagClass="org.apache.struts2.views.jsp.DateTag", description="Render a formatted date.")
+@StrutsTag(name = "date", tldBodyContent = "empty", tldTagClass = "org.apache.struts2.views.jsp.DateTag", description = "Render a formatted date.")
 public class Date extends ContextBean {
 
     private static final Logger LOG = LogManager.getLogger(Date.class);
@@ -292,6 +298,8 @@ public class Date extends ContextBean {
             date = Instant.ofEpochMilli((long) dateObject).atZone(tz);
         } else if (dateObject instanceof LocalDateTime) {
             date = ((LocalDateTime) dateObject).atZone(tz);
+        } else if (dateObject instanceof LocalDate) {
+            date = ((LocalDate) dateObject).atStartOfDay(tz);
         } else if (dateObject instanceof Instant) {
             date = ((Instant) dateObject).atZone(tz);
         } else {
@@ -300,18 +308,18 @@ public class Date extends ContextBean {
                 String developerNotification = "";
                 if (tp != null) {
                     developerNotification = findProviderInStack().getText(
-                            "devmode.notification",
-                            "Developer Notification:\n{0}",
-                            new String[]{
-                                    "Expression [" + name + "] passed to <s:date/> tag which was evaluated to [" + dateObject + "]("
-                                            + (dateObject != null ? dateObject.getClass() : "null") + ") isn't supported!"
-                            }
+                        "devmode.notification",
+                        "Developer Notification:\n{0}",
+                        new String[]{
+                            "Expression [" + name + "] passed to <s:date/> tag which was evaluated to [" + dateObject + "]("
+                                + (dateObject != null ? dateObject.getClass() : "null") + ") isn't supported!"
+                        }
                     );
                 }
                 LOG.warn(developerNotification);
             } else {
                 LOG.debug("Expression [{}] passed to <s:date/> tag which was evaluated to [{}]({}) isn't supported!",
-                        name, dateObject, (dateObject != null ? dateObject.getClass() : "null"));
+                    name, dateObject, (dateObject != null ? dateObject.getClass() : "null"));
             }
         }
 
@@ -338,11 +346,11 @@ public class Date extends ContextBean {
                         // returned string is the same as input =
                         // DATETAG_PROPERTY
                         if (globalFormat != null
-                                && !DATETAG_PROPERTY.equals(globalFormat)) {
+                            && !DATETAG_PROPERTY.equals(globalFormat)) {
                             dtf = DateTimeFormatter.ofPattern(globalFormat, ActionContext.getContext().getLocale());
                         } else {
                             dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
-                                    .withLocale(ActionContext.getContext().getLocale());
+                                .withLocale(ActionContext.getContext().getLocale());
                         }
                     } else {
                         dtf = DateTimeFormatter.ofPattern(format, ActionContext.getContext().getLocale());
@@ -378,17 +386,17 @@ public class Date extends ContextBean {
         return tz;
     }
 
-    @StrutsTagAttribute(description="Date or DateTime format pattern", rtexprvalue=false)
+    @StrutsTagAttribute(description = "Date or DateTime format pattern")
     public void setFormat(String format) {
         this.format = format;
     }
 
-    @StrutsTagAttribute(description="Whether to print out the date nicely", type="Boolean", defaultValue="false")
+    @StrutsTagAttribute(description = "Whether to print out the date nicely", type = "Boolean", defaultValue = "false")
     public void setNice(boolean nice) {
         this.nice = nice;
     }
 
-    @StrutsTagAttribute(description = "The specific timezone in which to format the date", required = false)
+    @StrutsTagAttribute(description = "The specific timezone in which to format the date")
     public void setTimezone(String timezone) {
         this.timezone = timezone;
     }
@@ -400,7 +408,7 @@ public class Date extends ContextBean {
         return name;
     }
 
-    @StrutsTagAttribute(description="The date value to format", required=true)
+    @StrutsTagAttribute(description = "The date value to format", required = true)
     public void setName(String name) {
         this.name = name;
     }
diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/DateTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/DateTagTest.java
index 6f6e31e..05e267e 100644
--- a/core/src/test/java/org/apache/struts2/views/jsp/ui/DateTagTest.java
+++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/DateTagTest.java
@@ -19,35 +19,33 @@
 package org.apache.struts2.views.jsp.ui;
 
 import com.opensymphony.xwork2.ActionContext;
-
 import org.apache.struts2.TestAction;
+import org.apache.struts2.components.Component;
+import org.apache.struts2.components.DateTextField;
 import org.apache.struts2.views.jsp.AbstractTagTest;
 import org.apache.struts2.views.jsp.DateTag;
 
+import javax.servlet.jsp.JspException;
 import java.text.DateFormat;
-import java.text.SimpleDateFormat;
 import java.time.Instant;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.Calendar;
 import java.util.Date;
-import java.util.TimeZone;
-import org.apache.struts2.components.Component;
-import org.apache.struts2.components.DateTextField;
 
 /**
  * Unit test for {@link org.apache.struts2.components.Date}.
- *
  */
 public class DateTagTest extends AbstractTagTest {
 
     private DateTag tag;
 
-    public void testCustomFormat() throws Exception {
+    public void testCustomFormatForDateTime() throws Exception {
         String format = "yyyy/MM/dd hh:mm:ss";
-        Date now = new Date();
-        String formatted = new SimpleDateFormat(format).format(now);
+        LocalDateTime now = LocalDateTime.now();
+        String formatted = DateTimeFormatter.ofPattern(format).format(now);
         context.put("myDate", now);
 
         tag.setName("myDate");
@@ -62,13 +60,55 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
+    }
+
+    public void testCustomFormatForLong() throws Exception {
+        String format = "yyyy/MM/dd";
+        long now = new Date().getTime();
+        String formatted = DateTimeFormatter.ofPattern(format).format(Instant.ofEpochMilli(now).atZone(ZoneId.systemDefault()));
+        context.put("myDate", now);
+
+        tag.setName("myDate");
+        tag.setNice(false);
+        tag.setFormat(format);
+        tag.doStartTag();
+        tag.doEndTag();
+        assertEquals(formatted, writer.toString());
+
+        // Basic sanity check of clearTagStateForTagPoolingServers() behaviour for Struts Tags after doEndTag().
+        DateTag freshTag = new DateTag();
+        freshTag.setPageContext(pageContext);
+        assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
+                "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
+    }
+
+    public void testCustomFormatForDate() throws Exception {
+        String format = "yyyy/MM/dd";
+        LocalDate now = LocalDate.now();
+        String formatted = DateTimeFormatter.ofPattern(format).format(now);
+        context.put("myDate", now);
+
+        tag.setName("myDate");
+        tag.setNice(false);
+        tag.setFormat(format);
+        tag.doStartTag();
+        tag.doEndTag();
+        assertEquals(formatted, writer.toString());
+
+        // Basic sanity check of clearTagStateForTagPoolingServers() behaviour for Struts Tags after doEndTag().
+        DateTag freshTag = new DateTag();
+        freshTag.setPageContext(pageContext);
+        assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
+                "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testCustomFormat_clearTagStateSet() throws Exception {
         String format = "yyyy/MM/dd hh:mm:ss";
-        Date now = new Date();
-        String formatted = new SimpleDateFormat(format).format(now);
+        LocalDateTime now = LocalDateTime.now();
+        String formatted = DateTimeFormatter.ofPattern(format).format(now);
         context.put("myDate", now);
 
         tag.setPerformClearTagStateForTagPoolingServers(true);  // Explicitly request tag state clearing.
@@ -86,13 +126,13 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testCustomGlobalFormatFormat() throws Exception {
         String format = "yyyy/MM/dd hh:mm:ss";
-        Date now = new Date();
-        String formatted = new SimpleDateFormat(format).format(now);
+        LocalDateTime now = LocalDateTime.now();
+        String formatted = DateTimeFormatter.ofPattern(format).format(now);
         context.put("myDate", now);
 
         ((TestAction) action).setText(org.apache.struts2.components.Date.DATETAG_PROPERTY, format);
@@ -106,10 +146,8 @@ public class DateTagTest extends AbstractTagTest {
 
     public void testCustomFormatWithTimezone() throws Exception {
         String format = "yyyy/MM/dd hh:mm:ss";
-        Date now = Calendar.getInstance(TimeZone.getTimeZone("GMT+1")).getTime();
-        SimpleDateFormat sdf = new SimpleDateFormat(format);
-        sdf.setTimeZone(TimeZone.getTimeZone("GMT+1"));
-        String formatted = sdf.format(now);
+        LocalDateTime now = LocalDateTime.now(ZoneId.of("GMT+1"));
+        String formatted = DateTimeFormatter.ofPattern(format).format(now);
         context.put("myDate", now);
 
         tag.setName("myDate");
@@ -126,15 +164,13 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testCustomFormatWithTimezone_clearTagStateSet() throws Exception {
         String format = "yyyy/MM/dd hh:mm:ss";
-        Date now = Calendar.getInstance(TimeZone.getTimeZone("GMT+1")).getTime();
-        SimpleDateFormat sdf = new SimpleDateFormat(format);
-        sdf.setTimeZone(TimeZone.getTimeZone("GMT+1"));
-        String formatted = sdf.format(now);
+        LocalDateTime now = LocalDateTime.now(ZoneId.of("GMT+1"));
+        String formatted = DateTimeFormatter.ofPattern(format).format(now);
         context.put("myDate", now);
 
         tag.setPerformClearTagStateForTagPoolingServers(true);  // Explicitly request tag state clearing.
@@ -153,15 +189,13 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testCustomFormatWithTimezoneAsExpression() throws Exception {
         String format = "yyyy/MM/dd hh:mm:ss";
-        Date now = Calendar.getInstance(TimeZone.getTimeZone("GMT+2")).getTime();
-        SimpleDateFormat sdf = new SimpleDateFormat(format);
-        sdf.setTimeZone(TimeZone.getTimeZone("GMT+2"));
-        String formatted = sdf.format(now);
+        LocalDateTime now = LocalDateTime.now(ZoneId.of("GMT+2"));
+        String formatted = DateTimeFormatter.ofPattern(format).format(now);
         context.put("myDate", now);
         context.put("myTimezone", "GMT+2");
 
@@ -178,15 +212,13 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testCustomFormatWithTimezoneAsExpression_clearTagStateSet() throws Exception {
         String format = "yyyy/MM/dd hh:mm:ss";
-        Date now = Calendar.getInstance(TimeZone.getTimeZone("GMT+2")).getTime();
-        SimpleDateFormat sdf = new SimpleDateFormat(format);
-        sdf.setTimeZone(TimeZone.getTimeZone("GMT+2"));
-        String formatted = sdf.format(now);
+        LocalDateTime now = LocalDateTime.now(ZoneId.of("GMT+2"));
+        String formatted = DateTimeFormatter.ofPattern(format).format(now);
         context.put("myDate", now);
         context.put("myTimezone", "GMT+2");
 
@@ -206,13 +238,13 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testCustomFormatCalendar() throws Exception {
         String format = "yyyy/MM/dd hh:mm:ss";
         Calendar calendar = Calendar.getInstance();
-        String formatted = new SimpleDateFormat(format).format(calendar.getTime());
+        String formatted = DateTimeFormatter.ofPattern(format).format(calendar.toInstant().atZone(ZoneId.systemDefault()));
         context.put("myDate", calendar);
 
         tag.setName("myDate");
@@ -227,13 +259,13 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testCustomFormatCalendar_clearTagStateSet() throws Exception {
         String format = "yyyy/MM/dd hh:mm:ss";
         Calendar calendar = Calendar.getInstance();
-        String formatted = new SimpleDateFormat(format).format(calendar.getTime());
+        String formatted = DateTimeFormatter.ofPattern(format).format(calendar.toInstant().atZone(ZoneId.systemDefault()));
         context.put("myDate", calendar);
 
         tag.setPerformClearTagStateForTagPoolingServers(true);  // Explicitly request tag state clearing.
@@ -251,13 +283,13 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testCustomFormatLong() throws Exception {
         String format = "yyyy/MM/dd hh:mm:ss";
         Date date = new Date();
-        String formatted = new SimpleDateFormat(format).format(date);
+        String formatted = DateTimeFormatter.ofPattern(format).format(date.toInstant().atZone(ZoneId.systemDefault()));
         // long
         context.put("myDate", date.getTime());
 
@@ -273,13 +305,13 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testCustomFormatLong_clearTagStateSet() throws Exception {
         String format = "yyyy/MM/dd hh:mm:ss";
         Date date = new Date();
-        String formatted = new SimpleDateFormat(format).format(date);
+        String formatted = DateTimeFormatter.ofPattern(format).format(date.toInstant().atZone(ZoneId.systemDefault()));
         // long
         context.put("myDate", date.getTime());
 
@@ -298,7 +330,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testCustomFormatLocalDateTime() throws Exception {
@@ -332,7 +364,7 @@ public class DateTagTest extends AbstractTagTest {
     public void testDefaultFormat() throws Exception {
         Date now = new Date();
         String formatted = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM,
-                ActionContext.getContext().getLocale()).format(now);
+            ActionContext.getContext().getLocale()).format(now);
 
         context.put("myDate", now);
         tag.setName("myDate");
@@ -346,13 +378,13 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testDefaultFormat_clearTagStateSet() throws Exception {
         Date now = new Date();
         String formatted = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM,
-                ActionContext.getContext().getLocale()).format(now);
+            ActionContext.getContext().getLocale()).format(now);
 
         context.put("myDate", now);
         tag.setPerformClearTagStateForTagPoolingServers(true);  // Explicitly request tag state clearing.
@@ -369,13 +401,13 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testCustomFormatAndComponent() throws Exception {
         String format = "yyyy/MM/dd hh:mm:ss";
-        Date now = new Date();
-        String formatted = new SimpleDateFormat(format).format(now);
+        LocalDateTime now = LocalDateTime.now();
+        String formatted = DateTimeFormatter.ofPattern(format).format(now);
         context.put("myDate", now);
 
         tag.setName("myDate");
@@ -388,7 +420,7 @@ public class DateTagTest extends AbstractTagTest {
         org.apache.struts2.components.Date component = (org.apache.struts2.components.Date) tag.getComponent();
         assertEquals("myDate", component.getName());
         assertEquals(format, component.getFormat());
-        assertEquals(false, component.isNice());
+        assertFalse(component.isNice());
 
         tag.doEndTag();
 
@@ -399,13 +431,13 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testCustomFormatAndComponent_clearTagStateSet() throws Exception {
         String format = "yyyy/MM/dd hh:mm:ss";
-        Date now = new Date();
-        String formatted = new SimpleDateFormat(format).format(now);
+        LocalDateTime now = LocalDateTime.now();
+        String formatted = DateTimeFormatter.ofPattern(format).format(now);
         context.put("myDate", now);
 
         tag.setPerformClearTagStateForTagPoolingServers(true);  // Explicitly request tag state clearing.
@@ -420,7 +452,7 @@ public class DateTagTest extends AbstractTagTest {
         org.apache.struts2.components.Date component = (org.apache.struts2.components.Date) tag.getComponent();
         assertEquals("myDate", component.getName());
         assertEquals(format, component.getFormat());
-        assertEquals(false, component.isNice());
+        assertFalse(component.isNice());
 
         tag.doEndTag();
 
@@ -432,13 +464,13 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testSetId() throws Exception {
         String format = "yyyy/MM/dd hh:mm:ss";
-        Date now = new Date();
-        String formatted = new SimpleDateFormat(format).format(now);
+        LocalDateTime now = LocalDateTime.now();
+        String formatted = DateTimeFormatter.ofPattern(format).format(now);
         context.put("myDate", now);
 
         tag.setName("myDate");
@@ -454,13 +486,13 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testSetId_clearTagStateSet() throws Exception {
         String format = "yyyy/MM/dd hh:mm:ss";
-        Date now = new Date();
-        String formatted = new SimpleDateFormat(format).format(now);
+        LocalDateTime now = LocalDateTime.now();
+        String formatted = DateTimeFormatter.ofPattern(format).format(now);
         context.put("myDate", now);
 
         tag.setPerformClearTagStateForTagPoolingServers(true);  // Explicitly request tag state clearing.
@@ -479,7 +511,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testFutureNiceHour() throws Exception {
@@ -501,7 +533,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testFutureNiceHour_clearTagStateSet() throws Exception {
@@ -526,7 +558,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testPastNiceHour() throws Exception {
@@ -548,7 +580,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testPastNiceHour_clearTagStateSet() throws Exception {
@@ -573,7 +605,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testFutureNiceHourMinSec() throws Exception {
@@ -596,7 +628,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testFutureNiceHourMinSec_clearTagStateSet() throws Exception {
@@ -622,7 +654,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testPastNiceHourMin() throws Exception {
@@ -645,7 +677,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testPastNiceHourMin_clearTagStateSet() throws Exception {
@@ -671,7 +703,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testFutureLessOneMin() throws Exception {
@@ -693,7 +725,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testFutureLessOneMin_clearTagStateSet() throws Exception {
@@ -718,7 +750,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testFutureLessOneHour() throws Exception {
@@ -740,7 +772,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testFutureLessOneHour_clearTagStateSet() throws Exception {
@@ -765,7 +797,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testFutureLessOneYear() throws Exception {
@@ -787,7 +819,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testFutureLessOneYear_clearTagStateSet() throws Exception {
@@ -812,7 +844,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testFutureTwoYears() throws Exception {
@@ -838,7 +870,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testFutureTwoYears_clearTagStateSet() throws Exception {
@@ -867,7 +899,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testNoDateObjectInContext() throws Exception {
@@ -884,7 +916,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     public void testNoDateObjectInContext_clearTagStateSet() throws Exception {
@@ -904,7 +936,7 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(tag, freshTag));
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     /**
@@ -912,10 +944,8 @@ public class DateTagTest extends AbstractTagTest {
      * since that tag does not have its own unit tests, and it also appears to be
      * a broken tag.  The code coverage tests can be moved if the tag is fixed, or
      * removed if the tag is dropped.
-     * 
-     * @throws Exception 
      */
-    public void testDateTextFieldTag_artificialCoverageTest() throws Exception {
+    public void testDateTextFieldTag_artificialCoverageTest() throws JspException {
         final String format = "yyyy/MM/dd hh:mm:ss";
         DateTextFieldTag dateTextFieldTag = createDateTextFieldTag();
         dateTextFieldTag.setFormat(format);
@@ -939,20 +969,40 @@ public class DateTagTest extends AbstractTagTest {
         freshTag.setPageContext(pageContext);
         assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set.  " +
                 "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
-                strutsBodyTagsAreReflectionEqual(dateTextFieldTag, freshTag));
+            strutsBodyTagsAreReflectionEqual(dateTextFieldTag, freshTag));
+    }
+
+    public void testNewJava8Format() throws Exception {
+        String format = "EEEE MMMM dd, hh:mm a";
+        LocalDateTime now = LocalDateTime.now();
+        String formatted = DateTimeFormatter.ofPattern(format, ActionContext.getContext().getLocale()).format(now);
+        context.put("myDate", now);
+
+        tag.setName("myDate");
+        tag.setNice(false);
+        tag.setFormat(format);
+        tag.doStartTag();
+        tag.doEndTag();
+        assertEquals(formatted, writer.toString());
+
+        // Basic sanity check of clearTagStateForTagPoolingServers() behaviour for Struts Tags after doEndTag().
+        DateTag freshTag = new DateTag();
+        freshTag.setPageContext(pageContext);
+        assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set.  " +
+                "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
+            strutsBodyTagsAreReflectionEqual(tag, freshTag));
     }
 
     /**
      * Utility method to create a new {@link DateTextFieldTag} instance for code coverage tests.
-     * 
+     * <p>
      * Note: There is no datetextfield.ftl template for the tag, so it does not appear that it can
-     *       actually be used in practice.  We can perform basic coverage tests from within this
-     *       unit test class until the {@link DateTextFieldTag} is fixed or removed.
-     * 
+     * actually be used in practice.  We can perform basic coverage tests from within this
+     * unit test class until the {@link DateTextFieldTag} is fixed or removed.
+     *
      * @return a basic {@link DateTextFieldTag} instance
-     * @throws Exception 
      */
-    private DateTextFieldTag createDateTextFieldTag() throws Exception {
+    private DateTextFieldTag createDateTextFieldTag() {
         DateTextFieldTag tag = new DateTextFieldTag();
         tag.setPageContext(pageContext);
         tag.setName("myDate");