I had been using Joda Time for date-time manipulation in a Java EE application in which a string representation of date-time submitted by the associated client had been converted using the following conversion routine before submitting it to a database i.e. in the getAsObject()
method in a JSF converter.
org.joda.time.format.DateTimeFormatter formatter = org.joda.time.format.DateTimeFormat.forPattern("dd-MMM-yyyy hh:mm:ss a Z").withZone(DateTimeZone.UTC);
DateTime dateTime = formatter.parseDateTime("05-Jan-2016 03:04:44 PM +0530");
System.out.println(formatter.print(dateTime));
The local time zone given is 5 hours and 30 minutes ahead of UTC
/ GMT
. Therefore, the conversion to UTC
should deduct 5 hours and 30 minutes from the date-time given which happens correctly using Joda Time. It displays the following output as expected.
05-Jan-2016 09:34:44 AM +0000
► The time zone offset +0530
in place of +05:30
has been taken because it is dependent upon <p:calendar>
which submits a zone offset in this format. It does not seem possible to change this behaviour of <p:calendar>
(This question itself would not have been needed otherwise).
The same thing is however broken, if attempted using the Java Time API in Java 8.
java.time.format.DateTimeFormatter formatter = java.time.format.DateTimeFormatter.ofPattern("dd-MMM-yyyy hh:mm:ss a Z").withZone(ZoneOffset.UTC);
ZonedDateTime dateTime = ZonedDateTime.parse("05-Jan-2016 03:04:44 PM +0530", formatter);
System.out.println(formatter.format(dateTime));
It unexpectedly displays the following incorrect output.
05-Jan-2016 03:04:44 PM +0000
Obviously, the date-time converted is not according to UTC
in which it is supposed to convert.
It requires the following changes to be adopted for it to work correctly.
java.time.format.DateTimeFormatter formatter = java.time.format.DateTimeFormatter.ofPattern("dd-MMM-yyyy hh:mm:ss a z").withZone(ZoneOffset.UTC);
ZonedDateTime dateTime = ZonedDateTime.parse("05-Jan-2016 03:04:44 PM +05:30", formatter);
System.out.println(formatter.format(dateTime));
Which in turn displays the following.
05-Jan-2016 09:34:44 AM Z
Z
has been replaced with z
and +0530
has been replaced with +05:30
.
Why these two APIs have a different behaviour in this regard has been ignored wholeheartedly in this question.
What middle way approach can be considered for <p:calendar>
and Java Time in Java 8 to work consistently and coherently though <p:calendar>
internally uses SimpleDateFormat
along with java.util.Date
?
The unsuccessful test scenario in JSF.
The converter :
@FacesConverter("dateTimeConverter")
public class DateTimeConverter implements Converter {
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.isEmpty()) {
return null;
}
try {
return ZonedDateTime.parse(value, DateTimeFormatter.ofPattern("dd-MMM-yyyy hh:mm:ss a Z").withZone(ZoneOffset.UTC));
} catch (IllegalArgumentException | DateTimeException e) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, null, "Message"), e);
}
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) {
return "";
}
if (!(value instanceof ZonedDateTime)) {
throw new ConverterException("Message");
}
return DateTimeFormatter.ofPattern("dd-MMM-yyyy hh:mm:ss a z").withZone(ZoneId.of("Asia/Kolkata")).format(((ZonedDateTime) value));
// According to a time zone of a specific user.
}
}
XHTML having <p:calendar>
.
<p:calendar id="dateTime"
timeZone="Asia/Kolkata"
pattern="dd-MMM-yyyy hh:mm:ss a Z"
value="#{bean.dateTime}"
showOn="button"
required="true"
showButtonPanel="true"
navigator="true">
<f:converter converterId="dateTimeConverter"/>
</p:calendar>
<p:message for="dateTime"/>
<p:commandButton value="Submit" update="display" actionListener="#{bean.action}"/><br/><br/>
<h:outputText id="display" value="#{bean.dateTime}">
<f:converter converterId="dateTimeConverter"/>
</h:outputText>
The time zone is fully transparently dependent upon the user's current time zone.
The bean having nothing other than a single property.
@ManagedBean
@ViewScoped
public class Bean implements Serializable {
private ZonedDateTime dateTime; // Getter and setter.
private static final long serialVersionUID = 1L;
public Bean() {}
public void action() {
// Do something.
}
}
This will work in an unexpected way as demonstrated in the second last example / middle in the first three code snippets.
Specifically, if you enter 05-Jan-2016 12:00:00 AM +0530
, it will redisplay 05-Jan-2016 05:30:00 AM IST
because the original conversion of 05-Jan-2016 12:00:00 AM +0530
to UTC
in the converter fails.
Conversion from a local time zone whose offset is +05:30
to UTC
and then conversion from UTC
back to that time zone must obviously redisplay the same date-time as inputted through the calendar component which is the rudimentary functionality of the converter given.
Update:
The JPA converter converting to and from java.sql.Timestamp
and java.time.ZonedDateTime
.
import java.sql.Timestamp;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
@Converter(autoApply = true)
public final class JodaDateTimeConverter implements AttributeConverter<ZonedDateTime, Timestamp> {
@Override
public Timestamp convertToDatabaseColumn(ZonedDateTime dateTime) {
return dateTime == null ? null : Timestamp.from(dateTime.toInstant());
}
@Override
public ZonedDateTime convertToEntityAttribute(Timestamp timestamp) {
return timestamp == null ? null : ZonedDateTime.ofInstant(timestamp.toInstant(), ZoneOffset.UTC);
}
}
See Question&Answers more detail:
os