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 插入columnfooDATE 类型)的示例代码:

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 中没有时间或时区组件的日期的主要内容,如果未能解决你的问题,请参考以下文章

从时区偏移或Angularjs中的日期获取时区[重复]

获取带有时区偏移的日期

如何在日期选择器中禁用上一个日期

没有时区偏移的 HTML 输入类型日期和时间

在指定时区导入日期时间,忽略夏令时

为什么org.joda.time.LocalDate是没有时区的日期?