I think the way this has shaken out us this: MySQL saves the date as UTC when it saves as a Timestamp so as long as I do that, it does not matter where MySQL lives.
Glassfish can tell you where it lives by inquiring the server, but it can also set a property for the home office which gives you a base of operations that is consistent wherever the server lives. You can do that in the web.xml
<context-param>
<param-name>GLASSFISH_HOME_TIME_ZONE</param-name>
<param-value>America/New_York</param-value>
</context-param>
The data bean needs to do most of the work so that it is consistent with the data across all of the data uses. Problems with the component libraries that are either not updated to the ZonedDateTime or only partially updated will generally call the data with a getter, so using overloading should allow the component libraries to find the specific method it prefers. I created a data bean that looks something like this:
public class DataBean {
private final ZoneId GLASSFISH_HOME_TIME_ZONE = ZoneId.of(FacesContext.getCurrentInstance().getExternalContext().getInitParameter( "GLASSFISH_HOME_TIME_ZONE"));
private ZonedDateTime dateToUseInGlassfish = null;
public DataBean (
Timestamp dateFromMySQL)
{
if ( dateFromMySQL == null ) {
this.dateToUseInGlassfish = null;
} else {
this.dateToUseInGlassfish = LocalDateTime.ofInstant(dateFromMySQL.toInstant(), GLASSFISH_HOME_TIME_ZONE ).atZone( GLASSFISH_HOME_TIME_ZONE );
}
}
/** Formatter for Date/Time */
private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy ' at ' h:mm a z");
/** Formatter for Date only */
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy");
/** Get the date string formatted with date and time */
public String getDateToUseInGlassfishDateTimeFormatted() {
if ( dateToUseInGlassfish == null ) { return null; }
String formattedDate = dateTimeFormatter.format( dateToUseInGlassfish );
return formattedDate;
}
/** Get the date string formatted with date only */
public String getgetDateToUseInGlassfishDateFormatted() {
if ( dateToUseInGlassfish == null) { return null; }
String formattedDate = dateFormatter.format( dateToUseInGlassfish );
return formattedDate;
}
/** Get the date ZDT formatted (for calculations) */
public ZonedDateTime getgetDateToUseInGlassfish() {
return dateToUseInGlassfish;
}
/** Get the date as Date (for component libraries that automatically fetch then throw up with ZDT) */
public Date getDateToUseInGlassfishDate() {
if ( dateToUseInGlassfish == null) { return null; }
return Date.from( dateToUseInGlassfish.toInstant());
}
/** Set the date from ZDT (results from calculations stored in bean) */
public void setDateToUseInGlassfish( ZonedDateTime dateToUseInGlassfish ) {
this.dateToUseInGlassfish = dateToUseInGlassfish;
}
/** Set the date from Date with an automatic convert to ZDT */
public void setDateToUseInGlassfish( Date dateToUseInGlassfish ) {
if (dateToUseInGlassfish == null) {
this.dateToUseInGlassfish = null;
} else {
this.dateToUseInGlassfish = LocalDateTime.ofInstant( Instant.ofEpochMilli( dateToUseInGlassfish.getTime()), GLASSFISH_HOME_TIME_ZONE ).atZone( GLASSFISH_HOME_TIME_ZONE );
}
}
Getting the date as a Timestamp from MySQL is getting it as a UTC point in time and it looks something like this :
ResultSet resultSet = preparedSelectQuoteSql.executeQuery()) {
while (resultSet.next()) {
quoteBean = new QuoteBean(
resultSet.getTimestamp("MySQLDateColumn")
);
}
}
Getting it inserted/updated into MySQL from the ZonedDateTime into a timestamp that MySQL will automatically convert to UTC so that we can let MySQL live anywhere we want it to live and read back the same Instant in time:
if ( insertValue instanceof ZonedDateTime ) {
if ( insertValue != null ) {
Timestamp convertedDate = Timestamp.from( ((ZonedDateTime) insertValue).toInstant() );
preparedStatement.setTimestamp( paramNumber, convertedDate );
} else {
preparedStatement.setNull ( paramNumber, Types.TIMESTAMP );
}
}
I think that this works, but I WELCOME criticism.