为Java SQL日期

时间:2016-02-16 16:58:36

标签: java mysql

我在使用Java计算数据库中的值时遇到了一些麻烦。

我试图按照日期从我的数据库计算一些值,但它们有小时,分钟和秒。如果我使用java.sql.Date进行搜索,则只允许我输入年,月和日,因此不会计算日期为year"-"month"-31 (hour:minutes:seconds != 00:00:00)"的值。

有没有办法为java.sql.Date添加小时,分钟和秒?如果没有,那么它会有什么更好的方法呢?

我的代码:

public static int getAmountPos(Connection con, Integer month, Integer year) {

        java.sql.Date date1 = java.sql.Date.valueOf(year+"-"+month+"-01");
        java.sql.Date date2 = java.sql.Date.valueOf(year+"-"+month+"-31");

        Integer amount = null;
        String sql = "SELECT COUNT(*) AS 'cantidad' "
                + " FROM table.point_of_service "
                + " WHERE registration_date between ? AND ?";

        PreparedStatement ps = con.getPreparedStatement(sql);
        ps.setDate(1, date1);
        ps.setDate(2, date2);

        ResultSet rs = ps.executeQuery();

        if(rs.next()) {
            amount = rs.getInt("cantidad");
        }

        return amount;
    }

3 个答案:

答案 0 :(得分:5)

java.sql.Date课程不打算保存有关时间的信息。它只包含日期(年,月,日)。您应该根据自己的需要使用java.sql.Timestamp

答案 1 :(得分:2)

TL;博士

更改代码以使用(a)java.time类型和(b)半开放方法来延长时间。

String sql = "SELECT COUNT(*) AS 'count_' "
           + "FROM table_.point_of_service_ "
           + "WHERE registration_date_ >= ? "  // Greater-than-or-equal.
           + "AND WHERE registration_date_ < ? ; ";  // Less-than (not 'or-equal') is Half-Open approach.

PreparedStatement ps = con.getPreparedStatement(sql);
ps.setObject( 1, YearMonth.of( 2016 , Month.FEBRUARY ).atDay( 1 ) );  // Calling `setObject` to handle `LocalDate` in JDBC 4.2 and later.
ps.setObject( 2, YearMonth.of( 2016 , Month.FEBRUARY ).plusMonths( 1 ).atDay( 1 ) );

的java.sql

java.sql类型仅用于将数据移入和移出数据库。通常不应该用于业务逻辑或操纵日期时间值。

  • java.sql.Date类表示没有时间且没有时区的仅限日期的值。
  • java.sql.Time相反,这是一个没有日期和时区的时间。
  • 对于日期时间,请使用java.sql.Timestamp。此值始终为UTC

对于日期时间,请致电getTimestamp上的ResultSet

java.sql.Timestamp tsWhenRegistered = myResultSet.getTimestamp( "when_registered_" );

当然,您必须在数据库中调用具有匹配数据类型的列。

java.time

在Java 8及更高版本中,将java.time框架用于业务逻辑。

避免使用旧的java.util.Date/.Calendar类,因为它们已被证明是麻烦,混乱和有缺陷的。它们已被java.time类取代。

一旦从数据库中获取它们,就从java.sql类型转换为java.time类型。希望JDBC驱动程序将被更新以直接处理java.time类型,但在此之前自己进行转换。

Instant

Instant是UTC时间轴上的一个时刻。

Instant instant = tsWhenRegistered.toInstant(); // From java.sql to java.time.

时区

在您的上下文中应用时区(ZoneId)。假设您的公司位于魁北克省。

ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = instant.atZone( zoneId );

YearMonth

现在,从您的问题中看到的java.time到java.sql的另一种方式。不要传递Integer年份和月份的对象,而是使用YearMonth类。请记住,这些java.time类现在已内置到Java中。因此,您可以在整个代码中使用它们。这为您提供了类型安全和编译器检查,并保证了有效值。

YearMonth yearMonth = YearMonth.of( 2016 , Month.FEBRUARY ); // Pass Month enum for type-safety and range-checking.
YearMonth yearMonth = YearMonth.of( 2016 , 2 );  // Or, pass month number 1-12.

重新执行问题中显示的方法,假设“when_registered_”是日期 - 时间值而不是日期,因为您似乎在您的问题中建议(不太清楚)。

public static int getAmountPos( Connection con, YearMonth yearMonth ) {
…

顺便提一下,提示:在数据库名称后附加下划线可以防止与SQL中的关键字发生冲突。 SQL标准明确承诺永远不会使用尾随下划线。

半开

不要使用SQL BETWEEN。在日期时间工作中,最佳实践是“半开放”方法,其中开头是包含,而结尾是独占。搜索逻辑开头为greater-than-or-equal,结尾为less-than小于或等于结尾)。这避免了确定分秒的结束日期的问题。

YearMonthLocalDate

YearMonth开始,我们可以将该月的第一天和下个月的第一天作为仅限日期(LocalDate)值。

LocalDate localDateStart = yearMonthArg.atDay( 1 ); // Get first-of-month.
LocalDate localDateStop = yearMonthArg.plusMonths( 1 ).atDay( 1 ); // Get first of *next* month.

如果您的驱动程序符合JDBC 4.2或更高版本,您应该能够通过LocalDate方法将这些PreparedStatement对象传递给setObject,并使用getObject来检索数据

如果您的数据库列被定义为标准SQL DATE类型或类似的仅限日期类型(没有时间,没有时区),则以下是示例代码。

…
String sql = "SELECT COUNT(*) AS 'count_' "
           + "FROM table_.point_of_service_ "
           + "WHERE registration_date_ >= ? "  // Greater-than-or-equal.
           + "AND WHERE registration_date_ < ?";  // Less-than (not 'or-equal') is Half-Open approach.

PreparedStatement ps = con.getPreparedStatement(sql);
ps.setObject( 1, localDateStart );  // Calling `setObject` to handle `LocalDate` in JDBC 4.2 and later.
ps.setObject( 2, localDateStop );
…

如果您的驱动程序无法执行此操作,请使用java.sql.Date对象替换。旧的日期时间类有新的方法来促进这种转换。在这里,我们需要java.sql.Date.valueOf方法。从数据库中检索数据时,转向另一个方向,请致电toLocalDate

…
ps.setDate( 1, java.sql.Date.valueOf( localDateStart ) );  // Converting `LocalDate` to `java.sql.Date`.
ps.setDate( 2, java.sql.Date.valueOf( localDateStop ) );
…

LocalDate + ZoneIdZonedDateTimejava.sql.Timestamp

如果您的数据库列定义为仅日期类型,而是日期时间类型,那么我们需要使用日期时间对象而不是LocalDate对象进行查询见上文。如果您的日期是针对UTC的话,则表示OffsetDateTime个对象;如果您的日期在某个其他时区有意义,则表示ZonedDateTime

请记住,LocalDate个对象没有任何实际意义,因为在任何特定时刻,日期都会因世界而异。我们必须应用时区来将每天的第一个时刻作为ZonedDateTime个对象。请注意,由于夏令时和其他异常情况,一天中的第一个时刻并非总是00:00:00.0。要从java.time转换为java.sql,我们从Instant对象中提取ZonedDateTime个对象,最后转换为java.sql.Timestamp

public Integer countForYearMonth ( Connection connArg , YearMonth yearMonthArg ) {
    // CAUTION: Pseudo-code. Ignoring real-world issues such as closing resources and handling exceptions. Never run, so never tested.

    LocalDate localDateStart = yearMonthArg.atDay( 1 ); // Get first-of-month.
    LocalDate localDateStop = yearMonthArg.plusMonths( 1 ).atDay( 1 ); // Get first of *next* month.

    // Give those LocalDate objects real meaning by applying a time zone.
    ZoneId zoneId = ZoneId.of( "America/Montreal" );  // Perhaps pass as argument rather than hard-code a particular time zone.
    ZonedDateTime zdtStart = localDateStart.atStartOfDay( zoneId );  // Inclusive of first moment of February… 2016-02-01T00:00:00.0-05:00[America/Montreal]
    ZonedDateTime zdtStop = localDateStop.atStartOfDay( zoneId ); // Inclusive of first moment of March… 2016-03-01T00:00:00.0-05:00[America/Montreal]

    // Business logic is complete. So convert to java.sql for database access.
    java.sql.Timestamp tsStart = java.sql.Timestamp.from ( zdtStart.toInstant() );  // February 1st 2016 at 5 AM UTC.
    java.sql.Timestamp tsStop = java.sql.Timestamp.from ( zdtStop.toInstant() );  // March 1st 2016 at 5 AM UTC.

    Integer amount = null;
    String sql = "SELECT COUNT(*) AS 'count_' "
               + "FROM some_table_ "
               + "WHERE when_registered_ >= ? "  // Greater-than-or-equal.
               + "AND when_registered_ < ? "  // Less-than (not 'or-equal') is Half-Open approach.
               + "; ";

    PreparedStatement ps = connArg.getPreparedStatement(sql);
    ps.setDate( 1, tsStart );
    ps.setDate( 2, tsStop );

    ResultSet rs = ps.executeQuery();

    if( rs.next() ) {
        amount = rs.getInt( "count_" );
    }
    return amount;
}

答案 2 :(得分:0)

我不同意那些说java.sql.date没有时间价值的人。当您在构造函数中传递长时间值时,它将保存该时间信息。您不能操纵那些时间信息,因为这些方法已被弃用,但时间信息将持续存在且不会丢失或截断。

根据java documentation:

  

使用给定的毫秒时间值构造Date对象。如果给定的毫秒值包含时间信息,则驱动程序将时间组件设置为默认时区(运行应用程序的Java虚拟机的时区)中与零GMT对应的时间。

现在回答这个问题,你转换为Timestamp或Instant或者LocalDate来创建将在sql字符串中使用的完整日期。见StackOverflow for conversion details。 David Keen有一个很好的cheat sheet that is usefulMichael Sharhag has java 8 date and time conversion details

这样的事情应该有效:

String dateString = year.toString() + "-" + month.toString() + "-01T00:00:00.0Z";
Instant dtgInstant = Instant.parse(dateString);
java.sql.Date timestamp = new java.sql.Date(dtgInstant.toEpochMilli());