我需要能够存储没有时间成分的日期(年/月/日)。这是日期的抽象概念,例如生日 - 我需要表示一年中的日期而不是特定的时刻。
我正在使用Java从一些输入文本中解析日期,并且需要存储在MySQL数据库中。无论数据库、应用程序或任何客户端位于哪个时区,它们都应该看到相同的年/月/日。
我的应用程序将在与数据库服务器具有不同系统时区的计算机上运行,并且我无法控制这两者。有谁有一个优雅的解决方案来确保我正确存储日期?
我可以想到这些解决方案,但似乎都不是很好:
java.time
这确实是
java.util
日期时间 API 的问题,直到 JSR-310 合并到 Java SE 8 中为止。java.util.Date
对象不是像 现代日期时间类型 那样的真正的日期时间对象;相反,它表示自称为“纪元”的标准基准时间(即 January 1, 1970, 00:00:00 GMT
)(或 UTC)以来的毫秒数。当您打印 java.util.Date
的对象时,其 toString
方法返回 JVM 时区中的日期时间,根据该毫秒值计算。
您描述的问题已通过 现代日期时间 API * 解决,其中
LocalDate
只代表日期。在下面的句子中,Oracle 教程很好地描述了其目的:
例如,您可以使用 LocalDate 对象来表示出生日期 约会,因为大多数人在同一天庆祝生日, 无论他们在自己的出生城市还是在地球另一端 国际日期变更线一侧。
几页之后,又描述如下:
LocalDate 代表 ISO 日历中的年月日,并且是 对于表示没有时间的日期很有用。您可能会使用 LocalDate 用于跟踪重要事件,例如出生日期或 结婚日期。
LocalDate
?它与
DATE
ANSI SQL 类型映射。 ANSI SQL 类型与 java.time
类型的映射在这篇 Oracle 文章中描述如下:
ANSI SQL | Java SE 8 |
---|---|
|
|
|
|
|
|
|
|
|
|
下面给出了将
LocalDate
插入 columnfoo
(属于 DATE
类型)的示例代码:
LocalDate localDate = LocalDate.now();
PreparedStatement st = conn.prepareStatement("INSERT INTO mytable (columnfoo) VALUES (?)");
st.setObject(1, localDate);
st.executeUpdate();
st.close();
下面给出的是从
LocalDate
检索 columnfoo
的示例代码:
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("SELECT * FROM mytable WHERE <some condition>");
while (rs.next()) {
// Assuming the column index of columnfoo is 1
LocalDate localDate = rs.getObject(1, LocalDate.class));
System.out.println(localDate);
}
rs.close();
st.close();
从 Trail:日期时间了解有关 现代日期时间 API* 的更多信息。
* 出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,则可以使用 ThreeTen-Backport,它将大部分 java.time 功能向后移植到 Java 6 和 7。正在为 Android 项目工作,并且您的 Android API 级别仍然不符合 Java-8,请检查 通过脱糖可用的 Java 8+ API 和 如何在 Android 项目中使用 ThreeTenABP。
您可以将所有时间/时区内容归零:
public static Date truncateDate(Date date)
{
GregorianCalendar cal = getGregorianCalendar();
cal.set(Calendar.ZONE_OFFSET, 0); // UTC
cal.set(Calendar.DST_OFFSET, 0); // We don't want DST to get in the way.
cal.setTime(date);
cal.set(Calendar.MILLISECOND, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.HOUR, 0);
cal.set(Calendar.AM_PM, Calendar.AM);
return cal.getTime();
}
我得出的结论是,当前应用程序(直接使用 jdbc 的简单实用程序)中的最佳方法是直接作为字符串插入。对于更大的 Hibernate 应用程序,我可能会费心编写自己的用户类型。不敢相信有人还没有在一些公开可用的代码中解决这个问题......
难道您不能在表中使用 MySQL
DATE
类型,然后在插入语句中使用格式化字符串吗? 我认为这样的事情可以避免任何时区调整。
INSERT INTO time_table(dt) VALUES('2008-12-31')
由于您指定您的服务器和数据库位于不同的时区,因此在我看来您在解释方面存在问题。如果您将日期归零,或存储为 MM-dd-YYYY,这相当于相同的事情,那么您存储的是服务器日期还是数据库日期?如果服务器上是晚上 11:00,数据库上是第二天凌晨 1 点,那么哪个日期是“正确”的?当您查看一年后的日期时,您会记得这些日期取决于哪个机器时区吗?
这些考虑可能有点矫枉过正。但为什么不直接将日期存储为 GMT(如果您愿意,可以将秒数清零)?
类
java.sql.Date
正是出于这个原因而存在,不幸的是它使用起来非常不方便,因为它只是扩展了java.util.Date
并且你必须手动将时间部分设置为零。