Java/MySQL 中没有时间或时区组件的日期
Posted
技术标签:
【中文标题】Java/MySQL 中没有时间或时区组件的日期【英文标题】:Dates with no time or timezone component in Java/MySQL 【发布时间】:2010-09-22 01:58:48 【问题描述】:我需要能够存储没有时间成分的日期(年/月/日)。这是日期的抽象概念,例如生日 - 我需要表示一年中的日期,而不是特定的时间点。
我正在使用 Java 从一些输入文本中解析日期,并且需要存储在 mysql 数据库中。无论数据库、应用程序或任何客户端位于哪个时区,它们都应该看到相同的年/月/日。
我的应用程序将在与数据库服务器具有不同系统时区的机器上运行,而我也无法控制。有没有人有一个优雅的解决方案来确保我正确存储日期?
我能想到这些解决方案,似乎都不是很好:
查询我的 MySQL 连接的时区并解析该时区的输入日期 将日期完全处理为字符串 yyyy-MM-dd【问题讨论】:
【参考方案1】:您可以将所有时间/时区的内容归零:
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();
【讨论】:
【参考方案2】:难道你不能只在你的表中使用 MySQL DATE
类型,然后在你的插入语句中使用格式化的字符串吗?我认为这样的事情可以避免任何时区调整。
INSERT INTO time_table(dt) VALUES('2008-12-31')
【讨论】:
你好,SQL注入漏洞!【参考方案3】:我得出的结论是,在我当前的应用程序(一个直接使用 jdbc 的简单实用程序)中,最好的方法是直接作为字符串插入。对于更大的 Hibernate 应用程序,我可能会费心编写自己的用户类型。不敢相信有人还没有在一些公开可用的代码中解决这个问题......
【讨论】:
是的,在您的 Java 代码中,创建您自己的日期类型,或者只使用字符串。 java.util.Date 对象是一个特定的时间点,不能正确地为您的用例建模。 我有一个类似的用例:一个独立于年份的任意日期,用于将我的数据与季节性数据相关联。我将月份和日期存储为整数而不是使用字符串,并且我不存储任何年份的表示形式。【参考方案4】:由于您指定您的服务器和数据库位于不同的时区,因此在我看来您的解释有问题。如果您将日期归零,或者存储为 MM-dd-YYYY,这相当于同一件事,您是存储服务器日期还是数据库日期?如果服务器上是晚上 11:00,数据库上是第二天凌晨 1 点,那么哪个日期是“正确的”?当您查看一年后的日期时,您会记得日期所依赖的机器时区吗?
这些考虑可能有点矫枉过正。但是,为什么不将日期存储在 GMT 中(如果您愿意,可以将秒数归零)?
【讨论】:
提问者不想保存特定的时间实例,只保存日期。在这种情况下是出生日期。所以时区变得无关紧要,只有在解析时才应该解释本地时区。将时区设置为 UTC/GMT 实际上是一回事。所以我看不出……你的回答中有任何一点。我错过了什么吗?我觉得我必须这样做,因为你可能比我更有经验一千倍。【参考方案5】:java.sql.Date
类正是因为这个原因而存在,不幸的是它使用起来非常不方便,因为它只是扩展了java.util.Date
,并且您必须手动将时间部分设置为零。
【讨论】:
【参考方案6】:java.time
这确实是 java.util
日期时间 API 的问题,直到 JSR-310 并入 Java SE 8。java.util.Date
对象不是真正的日期时间对象,如 modern date-time types;相反,它表示自称为“纪元”的标准基准时间以来的毫秒数,即January 1, 1970, 00:00:00 GMT
(或 UTC)。当你打印一个java.util.Date
的对象时,它的toString
方法会返回JVM 时区中的日期时间,根据这个毫秒值计算得出。
您所描述的问题已通过 modern date-time API* 解决,其中 LocalDate
仅代表一个日期。在下面的句子中,Oracle tutorial 很好地描述了它的用途:
例如,您可以使用 LocalDate 对象来表示出生 约会,因为大多数人在同一天过生日, 无论他们是在他们的出生城市还是在全球其他地方 国际日期变更线的一侧。
A couple of pages later,再次描述如下:
LocalDate 表示 ISO 日历中的年月日,并且是 用于表示没有时间的日期。你可能会使用一个 LocalDate 跟踪重要事件,例如出生日期或 结婚日期。
LocalDate
应该使用哪种数据类型?
它映射到DATE
ANSI SQL 类型。 ANSI SQL 类型与java.time
类型的映射在this Oracle's article 中描述如下:
ANSI SQL | Java SE 8 |
---|---|
DATE | LocalDate |
TIME | LocalTime |
TIMESTAMP | LocalDateTime |
TIME WITH TIMEZONE | OffsetTime |
TIMESTAMP WITH TIMEZONE | OffsetDateTime |
如何在JDBC中使用?
下面是一个将LocalDate
插入columnfoo
(DATE
类型)的示例代码:
LocalDate localDate = LocalDate.now();
PreparedStatement st = conn.prepareStatement("INSERT INTO mytable (columnfoo) VALUES (?)");
st.setObject(1, localDate);
st.executeUpdate();
st.close();
下面是从columnfoo
检索LocalDate
的示例代码:
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: Date Time 了解有关 modern date-time API* 的更多信息。
* 出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它将大部分 java.time 功能向后移植到 Java 6 和 7 . 如果您正在为一个 android 项目工作并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaring 和 How to use ThreeTenABP in Android Project。
【讨论】:
以上是关于Java/MySQL 中没有时间或时区组件的日期的主要内容,如果未能解决你的问题,请参考以下文章