在 MySQL 和 Glassfish 中保存 ZonedDateTime

save ZonedDateTime in MySQL and Glassfish

这是我在本论坛的第一个问题,请耐心等待。

Oracle 说 "ZonedDateTime is a date and time with a fully qualified time zone. This can resolve an offset at any point in time. The rule of thumb is that if you want to represent a date and time without relying on the context of a specific server, you should use ZonedDateTime." 这正是我想要做的,因为应用程序正在处理全局交互,但 MySQL 似乎只将 DATETIME 保存为 TIMESTAMP,但它显然将其保存为 UTC以便它可以转换为任何时区。我们将 运行 所在的服务器将在多个时区 运行,我们不知道哪一个 运行,因为云提供商将动态移动它们根据需求和维护。

因此,在此应用程序中维护 date/time/zone 似乎非常适合新的 ZonedDateTime 构造,但我在试图使 PrimeFaces 和其他组件代码之间的遗留 Date 之间的一切保持直接时让自己一再感到困惑仍然交付并且 MySQL 想要处理最终将在 2038 年老化的时间戳。

我们不想使用任何外部日期库,例如 Joda 或 Apache。

我的问题相当直截了当,但答案对我来说似乎难以捉摸,而且细微差别似乎很多:将 java ZonedDateTime 保存到 [=25= 的最佳做法是什么? ] 数据库,将其读回,以便用户可以通过 java 即时计算在全球范围内执行工作,这对本地用户来说是正确的,并且无论 Glassfish 服务器或 MySQL 可能每个服务器都处于不同的时区并且每天都在不同的时区?

我认为这让我们震惊的方式是:MySQL 在将日期保存为时间戳时将日期保存为 UTC,所以只要我这样做,在哪里都没有关系 MySQL生活。

Glassfish 可以通过查询服务器告诉您它所在的位置,但它也可以为家庭办公室设置 属性,这样您就可以在服务器所在的任何地方保持一致的操作基础。您可以在 web.xml

<context-param>
    <param-name>GLASSFISH_HOME_TIME_ZONE</param-name>
    <param-value>America/New_York</param-value>
</context-param>

数据 bean 需要完成大部分工作,以便它与所有数据使用中的数据保持一致。未更新到 ZonedDateTime 或仅部分更新的组件库的问题通常会使用 getter 调用数据,因此使用重载应该允许组件库找到它喜欢的特定方法。我创建了一个看起来像这样的数据 bean:

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 );
    }
}

从 MySQL 获取日期作为时间戳是将其作为 UTC 时间点获取,它看起来像这样:

ResultSet resultSet = preparedSelectQuoteSql.executeQuery()) {
    while (resultSet.next()) {
        quoteBean = new QuoteBean(
            resultSet.getTimestamp("MySQLDateColumn")
        );
    }
}

将它 inserted/updated 从 ZonedDateTime 变成 MySQL 变成一个时间戳,MySQL 将自动转换为 UTC,这样我们就可以让 MySQL 生活在我们想要的任何地方生活和回读同一个瞬间:

if ( insertValue instanceof ZonedDateTime ) {
    if ( insertValue != null ) {
        Timestamp convertedDate = Timestamp.from( ((ZonedDateTime) insertValue).toInstant() );
        preparedStatement.setTimestamp( paramNumber, convertedDate );
    } else {
        preparedStatement.setNull ( paramNumber, Types.TIMESTAMP );
    }
}

我认为这行得通,但欢迎批评。