I want to format an Instant using a predefined format of Java. I can do this in Java:
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, request.getLocale());
// this will hopefully format a Date to 12.09.2018 02:10
And I want this to be done in Thymeleaf using my Instant type:
<div th:text="${#temporals.format(work.indexTime)}"></div>
<!-- this will print "2018-09-12T02:10:06Z" -->
But how can I tell Thymeleaf to use the DateFormat.SHORT settings?
EDIT:
My current workaround is this:
Controller:
DateTimeFormatter dateFormatter = DateTimeFormatter
.ofLocalizedDateTime(FormatStyle.SHORT)
.withLocale(request.getLocale())
.withZone(ZoneId.systemDefault());
Template:
<div th:text="${dateFormatter.format(work.indexTime)}"></div>
You can simply specify SHORT as the format.
<div th:text="${#temporals.format(work.indexTime, 'SHORT')}"></div>
From the README:
/*
* Format date with the specified pattern
* SHORT, MEDIUM, LONG and FULL can also be specified to used the default java.time.format.FormatStyle patterns
* Also works with arrays, lists or sets
*/
Yeah, you can set that up in thymeleaf, but it's pretty verbose... this works for me:
<th:block th:with="clazz=${T(java.time.format.DateTimeFormatter)},
style=${T(java.time.format.FormatStyle).SHORT},
zone=${T(java.time.ZoneId).systemDefault()},
formatter=${clazz.ofLocalizedDateTime(style).withLocale(#locale).withZone(zone)}">
<span th:text="${formatter.format(work.indexTime)}" />
</th:block>
You could also add a default converter from Instant to String and use the double bracket syntax when outputting an Instant:
Context:
public class Context extends WebMvcConfigurerAdapter {
#Override
public void addFormatters(FormatterRegistry r) {
DateTimeFormatter dateFormatter = DateTimeFormatter
.ofLocalizedDateTime(FormatStyle.SHORT)
.withLocale(LocaleContextHolder.getLocale())
.withZone(ZoneId.systemDefault());
r.addConverter(new Converter<Instant, String>() {
#Override
public String convert(Instant s) {
return s != null ? dateFormatter.format(s) : "";
}
});
}
}
HTML:
<div th:text="${{work.indexTime}}" />
Related
I am using Java 8, after following documentation:
How to Use Tables - Using an Editor to Validate User-Entered Text
I'd like to setup a specialized formatter when editing a column in my JTable. This column contains java.time.LocalTime instances.
JTable table;
...
table.setDefaultEditor(LocalTime.class, new LocalTimeEditor());
Where LocalTimeEditor is defined by (tentatively):
public class LocalTimeEditor extends DefaultCellEditor {
JFormattedTextField ftf;
public LocalTimeEditor() {
super(new JFormattedTextField());
ftf = (JFormattedTextField) getComponent();
// Set up the editor for the LocalTime cells.
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
ftf.setFormatterFactory(new DefaultFormatterFactory(dateFormatter));
But this leads to the following compilation error:
The constructor DefaultFormatterFactory(DateTimeFormatter) is undefined
I'd like to stay away from a solution involving SimpleDateFormat (+DateFormatter) as explained here or here, since java.util.Date should be considered legacy (see old code here).
Is there a solution to integrate DateTimeFormatter with JFormattedTextField, or am I blocked by:
JDK-8034818 - JFormattedTextField does not accept DateTimeFormatter as formatter
I'd also like to stay away from MaskFormatter, since it does not allow easy error handling for something like: "25:70:90".
As per the argument of DefaultFormatterFactor, I created a new JFormattedTextField.AbstractFormatter
class JTFormater extends JFormattedTextField.AbstractFormatter{
final DateTimeFormatter formatter;
public JTFormater(DateTimeFormatter formatter){
this.formatter = formatter;
}
#Override
public Object stringToValue(String text) throws ParseException {
return formatter.parse(text);
}
#Override
public String valueToString(Object value) throws ParseException {
if(value instanceof TemporalAccessor){
return formatter.format((TemporalAccessor) value);
} else{
throw new ParseException("not a valid type at", 0);
}
}
}
From this I could parse and display LocalTime's, although in my implementation it is pretty clumsy.
You can use LGoodDatePicker's TimeTableEditor: https://github.com/LGoodDatePicker/LGoodDatePicker/blob/master/Project/src/main/java/com/github/lgooddatepicker/tableeditors/TimeTableEditor.java
I want to pass a LocalDateTime object to thymeleaf with a specific format (yyyy/MM/dd HH:mm) and later receive it back into my controller class.
I want to use an customEditor / initbinder to do the convertion.
/**
* Custom Initbinder makes LocalDateTime working with javascript
*/
#InitBinder
public void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
binder.registerCustomEditor(LocalDateTime.class, "reservationtime", new LocalDateTimeEditor());
}
public class LocalDateTimeEditor extends PropertyEditorSupport {
// Converts a String to a LocalDateTime (when submitting form)
#Override
public void setAsText(String text) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm");
LocalDateTime localDateTime = LocalDateTime.parse(text, formatter);
this.setValue(localDateTime);
}
// Converts a LocalDateTime to a String (when displaying form)
#Override
public String getAsText() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm");
String time = ((LocalDateTime)getValue()).format(formatter);
return time;
}
}
While spring uses my initbinder when it receives the data from the form, thymeleaf though seems to prefer the .toString() method over my initbinder and my getAsText() method never gets called.
My view:
<input type="text" th:name="${reservationtime}" id="reservationtime" class="form-control"
th:value="${reservationtime}"/>
I find the initbinder "way" quite good in terms of code readability. So I would like to keep using the initbinder. Is it possible to tell thymeleaf to use my initbinder or any other good workaround?
remove the parameter"reservationtime", may resolve the issue :
binder.registerCustomEditor(LocalDateTime.class, new LocalDateTimeEditor());
And Then, the converter will be used for ALL LocalDateTime fields
Here's a simple value bean annotated with Spring's new (as of 3.0) convenience #DateTimeFormat annotation (which as I understand replaces the pre-3.0 need for custom PropertyEditors as per this SO question):
import java.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;
public class Widget {
private String name;
#DateTimeFormat(pattern = "MM/dd/yyyy")
private LocalDate created;
// getters/setters excluded
}
When biding the values from a form submission to this widget, the date format works flawlessly. That is, only date strings in the MM/dd/yyyy format will convert successfully to actual LocalDate objects. Great, we're halfway there.
However, I would also like to be able to also display the created LocalDate property in a JSP view in the same MM/dd/yyyy format using JSP EL like so (assuming my spring controller added a widget attribute to the model):
${widget.created}
Unfortunately, this will only display the default toString format of LocalDate (in yyyy-MM-dd format). I understand that if I use spring's form tags the date displays as desired:
<form:form commandName="widget">
Widget created: <form:input path="created"/>
</form:form>
But I'd like to simply display the formatted date string without using the spring form tags. Or even JSTL's fmt:formatDate tag.
Coming from Struts2, the HttpServletRequest was wrapped in a StrutsRequestWrapper which enabled EL expressions like this to actually interrogate the OGNL value stack. So I'm wondering if spring provide something similar to this for allowing converters to execute?
EDIT
I also realize that when using spring's eval tag the date will display according the pattern defined in the #DateTimeFormat annotation:
<%# taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<spring:eval expression="widget.created"/>
Interestingly, when using a custom PropertyEditor to format the date, this tag does NOT invoke that PropertyEditor's getAsText method and therefore defaults to the DateFormat.SHORT as described in the docs. In any event, I'd still like to know if there is a way to achieve the date formatting without having to use a tag--only using standard JSP EL.
You may use the tag to provide you these kind of formattings, such as money, data, time, and many others.
You may add on you JSP the reference:
<%# taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
And use the formatting as:
<fmt:formatDate pattern="yyyy-MM-dd" value="${now}" />
Follows below a reference:
http://www.tutorialspoint.com/jsp/jstl_format_formatdate_tag.htm
To precise Eduardo answer:
<%# taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<fmt:formatDate pattern="MM/dd/yyyy" value="${widget.created}" />
I also prefer to not do any formatting via tags. I realise this may not be the solution you are looking for and are looking for a way to do this via spring annotations. Nevertheless, In the past I've used the following work around:
Create a new getter with the following signature:
public String getCreatedDateDisplay
(You can alter the name of the getter if you prefer.)
Within the getter, format the created date attribute as desired using a formatter such as SimpleDateFormat.
Then you can call the following from your JSP
${widget.createDateDisplay}
I was dispirited to learn that spring developers have decided not to integrate Unified EL (the expression language used in JSP 2.1+) with Spring EL stating:
neither JSP nor JSF have a strong position in terms of our development focus anymore.
But taking inspiration from the JIRA ticket cited, I created a custom ELResolver which, if the resolved value is a java.time.LocalDate or java.time.LocalDateTime, will attempt to pull the #DateTimeFormat pattern value in order to format the returned String value.
Here's the ELResolver (along with the ServletContextListener used to bootstrap it):
public class DateTimeFormatAwareElResolver extends ELResolver implements ServletContextListener {
private final ThreadLocal<Boolean> isGetValueInProgress = new ThreadLocal<>();
#Override
public void contextInitialized(ServletContextEvent event) {
JspFactory.getDefaultFactory().getJspApplicationContext(event.getServletContext()).addELResolver(this);
}
#Override
public void contextDestroyed(ServletContextEvent sce) {}
#Override
public Object getValue(ELContext context, Object base, Object property) {
try {
if (Boolean.TRUE.equals(isGetValueInProgress.get())) {
return null;
}
isGetValueInProgress.set(Boolean.TRUE);
Object value = context.getELResolver().getValue(context, base, property);
if (value != null && isFormattableDate(value)) {
String pattern = getDateTimeFormatPatternOrNull(base, property.toString());
if (pattern != null) {
return format(value, DateTimeFormatter.ofPattern(pattern));
}
}
return value;
}
finally {
isGetValueInProgress.remove();
}
}
private boolean isFormattableDate(Object value) {
return value instanceof LocalDate || value instanceof LocalDateTime;
}
private String format(Object localDateOrLocalDateTime, DateTimeFormatter formatter) {
if (localDateOrLocalDateTime instanceof LocalDate) {
return ((LocalDate)localDateOrLocalDateTime).format(formatter);
}
return ((LocalDateTime)localDateOrLocalDateTime).format(formatter);
}
private String getDateTimeFormatPatternOrNull(Object base, String property) {
DateTimeFormat dateTimeFormat = getDateTimeFormatAnnotation(base, property);
if (dateTimeFormat != null) {
return dateTimeFormat.pattern();
}
return null;
}
private DateTimeFormat getDateTimeFormatAnnotation(Object base, String property) {
DateTimeFormat dtf = getDateTimeFormatFieldAnnotation(base, property);
return dtf != null ? dtf : getDateTimeFormatMethodAnnotation(base, property);
}
private DateTimeFormat getDateTimeFormatFieldAnnotation(Object base, String property) {
try {
if (base != null && property != null) {
Field field = base.getClass().getDeclaredField(property);
return field.getAnnotation(DateTimeFormat.class);
}
}
catch (NoSuchFieldException | SecurityException ignore) {
}
return null;
}
private DateTimeFormat getDateTimeFormatMethodAnnotation(Object base, String property) {
try {
if (base != null && property != null) {
Method method = base.getClass().getMethod("get" + StringUtils.capitalize(property));
return method.getAnnotation(DateTimeFormat.class);
}
}
catch (NoSuchMethodException ignore) {
}
return null;
}
#Override
public Class<?> getType(ELContext context, Object base, Object property) {
return null;
}
#Override
public void setValue(ELContext context, Object base, Object property, Object value) {
}
#Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
return true;
}
#Override
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
return null;
}
#Override
public Class<?> getCommonPropertyType(ELContext context, Object base) {
return null;
}
}
Register the ELResolver in web.xml:
<listener>
<listener-class>com.company.el.DateTimeFormatAwareElResolver</listener-class>
</listener>
And now when I have ${widget.created} in my jsp, the value displayed will be formatted according to the #DateTimeFormat annotation!
Additionally, if the LocalDate or LocalDateTime object is needed by the jsp (and not just the formatted String representation), you can still access the object itself using direct method invocation like: ${widget.getCreated()}
In my Spring application have jsp and form.
demo.jsp have one field <form:input path="fromDate"/>
And in my DemoForm have field private Date fromDate;,
When we store the value Null value storing...
My Question Is their any direct tag for Store the date in my spring supplied jsp tag.
other wise give me other alternate way..
You need to define the custom editor for the Date in your controller. Please try below code.
#InitBinder
public void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
DateFormat df = new SimpleDateFormat("dd-MM-yyyy");
df.setLenient(false);
CustomDateEditor editor = new CustomDateEditor(df, true); // second argument 'allowEmpty' is set to true to allow null/empty values.
binder.registerCustomEditor(Date.class, editor);
}
I'm consuming a REST webservice and directly using the JAXB objects in my view. One has a date as a XMLGregorianCalendar like this:
#XmlAttribute(name = "record")
#XmlSchemaType(name = "dateTime")
protected XMLGregorianCalendar record;
While trying to use a standard converter
<h:outputText value="#{bean.value.record}" >
<f:convertDateTime pattern="dd.MM.yy" />
</h:outputText>
I get the error message (translated into english) in my JSF2 environment (JBoss-7.1.1-Final)
javax.faces.convert.ConverterException: fSelection:dtSelection:0:j_idt42:
Converting of '2012-07-25T20:15:00' into string not possible.
It seems, the type XMLGregorianCalendar is not supported by the default converter. I'm wondering if a JSF converter for this date type is available, because this requirement does not seem to be that unusual ...
Edit Ravi provided a functional example of a custom converter, but this seems to be to unflexible:
the pattern is hardcoded
no support for the user local
The value should be of type java.util.Date.
So get the Date object from the XMLGregorianCalendar like this:
record.toGregorianCalendar().getTime();
UPDATE:
You can use like this:
<h:outputText value="#{bean.value.record.toGregorianCalendar().time}" >
<f:convertDateTime pattern="dd.MM.yy" />
</h:outputText>
This should actually work but since you said you are getting an IllegalAccessException, I am not sure for the exact reason.
Alternatively, you can also write your own converter if you would like to, the converter will look like this:
And if you want to use the same attributes that you would use with a dateTimeConverter, then you need to pass them as attributes to the component and extend DateTimeConverter like this:
#FacesConverter("com.examples.Date")
public class XMLGregorianCalConverter extends DateTimeConverter {
private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone("GMT");
private String dateStyle = "default";
private Locale locale = null;
private String pattern = null;
private String timeStyle = "default";
private TimeZone timeZone = DEFAULT_TIME_ZONE;
private String type = "date";
#Override
public Object getAsObject(FacesContext context, UIComponent component, String newValue) {
// TODO Auto-generated method stub
return null;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
Map<String, Object> attributes = component.getAttributes();
if(attributes.containsKey("pattern")){
pattern = (String) attributes.get("pattern");
}
setPattern(pattern);
if(attributes.containsKey("locale")){
locale = (Locale) attributes.get("locale");
}
setLocale(locale);
if(attributes.containsKey("timeZone")){
timeZone = (TimeZone) attributes.get("timeZone");
}
setTimeZone(timeZone);
if(attributes.containsKey("dateStyle")){
dateStyle = (String) attributes.get("dateStyle");
}
setDateStyle(dateStyle);
if(attributes.containsKey("timeStyle")){
timeStyle = (String) attributes.get("timeStyle");
}
setTimeStyle(timeStyle);
if(attributes.containsKey("type")){
type = (String) attributes.get("type");
}
setType(type);
XMLGregorianCalendar xmlGregCal = (XMLGregorianCalendar) value;
Date date = xmlGregCal.toGregorianCalendar().getTime();
return super.getAsString(context, component, date);
}
}
and use on your page like this:
<h:outputText value="#{bean.value.record}" >
<f:converter converterId="com.examples.Date" />
<f:attribute name="pattern" value="dd.MM.yy" />
</h:outputText>
Code inspired/copied from this question: JSF convertDateTime with timezone in datatable
You can register a custom XML Adapter to convert from XMLGregorianCalendar to either Calendar or Date along these lines: How do I customise date/time bindings using JAXWS and APT?