【问题标题】:Java compareTo Dates cannot be equalJava compareTo 日期不能相等
【发布时间】:2018-11-22 07:44:28
【问题描述】:

我正在尝试比较 2 个日期,第一个日期来自 MySQL 数据库,第二个日期来自当前日期。

如下所示,数据库中有不同的日期

但问题是我得到了 3 个 if 语句,它们应该告诉我的程序数据库日期是在当前日期之前、之后还是等于当前日期。 Before 和 After 语句应该可以工作,但它可以看到日期 2018-06-12 应该等于当前日期,因此它以“before 语句”结尾。

希望你能看到我做错了什么。

private static void Resetter() throws ParseException, SQLException {
    String host = "****";
    String username = "root";
    String mysqlpassword = "";

    //Querys
    String query = "select * from accounts";
    String queryy = "update accounts set daily_search_count = 0 where id = ?";
    Connection con = DriverManager.getConnection(host, username, mysqlpassword);
    Statement st = con.createStatement();
    ResultSet rs = st.executeQuery(query);

    DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
    dateFormat.setTimeZone( TimeZone.getTimeZone( "UTC" ));
    Date currentDate = new Date();

    while(rs.next()){
        System.out.println(dateFormat.format(currentDate));
        if (rs.getDate(5).compareTo(currentDate) > 0) {
           // System.out.println("Database-date is after currentDate");
        } else if (rs.getDate(5).compareTo(currentDate) < 0) {
           // System.out.println("Database-date is before currentDate");
            PreparedStatement updatexdd = con.prepareStatement(queryy);
            updatexdd.setInt(1, rs.getInt(1));
            int updatexdd_done = updatexdd.executeUpdate();
        } else if (rs.getDate(5).compareTo(currentDate) == 0) {
           // System.out.println("Database-date is equal to currentDate");
        } else {
            System.out.println("Any other");
        }
    }
}

【问题讨论】:

  • 不相关(我不确定您在问什么),但答案将是 >0、
  • 这是因为根据数据库中的日期 2018-06-12 应该等于当前日期。但在这种情况下,它没有。我只是在 结束
  • 是的。 rs.getDate() 只返回一个日期 - 没有时间部分(或更准确地说,时间部分是午夜)。但是new Date() 会为您提供当前日期和时间
  • 在所有 if 语句中使用 dateFormat.format(rs.getDate(5)),所以所有的格式都相同。
  • 你需要的是来自java.time, the modern Java date and time APILocalDate。这是一个没有时间的日期,所以不能对你玩这样的把戏。另外,java.time 使用起来更好。

标签: java validation date compareto


【解决方案1】:

tl;博士

LocalDate today = LocalDate.now( ZoneOffset.UTC ) ;
Instant instant = myResultSet.getObject( … , Instant.class ) ;  // Retrieve a `TIMESTAMP WITH TIME ZONE` value in database as an `Instant` for a date with time-of-day in UTC with a resolution as fine as nanoseconds.
LocalDate ld = instant.atOffset( ZoneOffset.UTC ).toLocalDate() ;  // Extract a date-only value without time-of-day and without time zone.

if ( ld.isBefore( today ) ) { … }       // Compare `LocalDate` objects.
else if ( ld.isEqual( today ) ) { … }
else if ( ld.isAfter( today ) ) { … }
else { … handle error }

java.time

您正在使用和滥用麻烦的旧日期时间类。

正如其他人指出的那样:

  • SQL 标准的DATE 类型仅包含没有时间和时区的日期
  • 旧的java.util.Date 类命名错误,在UTC 中同时包含日期时间。
  • 旧的java.sql.Date 类假装只保存一个日期,但实际上有一个时间,因为这个类继承自上面的类,而文档告诉我们在使用时忽略这个事实。 (是的,这很令人困惑,而且是一个糟糕的设计,一个笨拙的 hack。)

切勿使用java.util.Datejava.util.Calendarjava.sql.Timestampjava.sql.Date 和相关类。相反,只使用理智的 java.time 类。他们从其前身 Joda-Time 项目的经验中收集到的简洁设计和对日期时间处理的深度理解方面处于行业领先地位。

对于仅日期值,存储在 SQL 标准数据库类型 DATE 中,使用 java.time.LocalDate

LocalDate ld = myResultSet.get( … , LocalDate.class ) ;  // Retrieving from database.
myPreparedStatement.setObject( … , ld ) ;  // Storing in database.

对于以 UTC 值表示的日期,存储在 SQL 标准数据库类型 TIMESTAMP WITH TIME ZONE 中,请使用 java.time.InstantInstant 类代表UTC 时间线上的时刻,分辨率为nanoseconds(最多九 (9) 位小数)。

Instant instant = myResultSet.get( … , Instant.class ) ;  // Retrieving from database.
myPreparedStatement.setObject( … , instant ) ;  // Storing in database.

要在 Java 中进行比较,请使用 isEqualisBeforeisAfterequalscompare 方法。

Boolean overdue = someLocalDate.isAfter( otherLocalDate ) ;

时区

时区对于确定某一时刻的日期和时间至关重要 (Instant/TIMESTAMP WITH TIME ZONE)。

从数据库中以Instant 形式检索您的TIMESTAMP WITH TIME ZONE 值后,调整为time zoneoffset-from-UTC,您希望使用其挂钟时间来感知日期和时间。对于时区,应用ZoneId 以获取ZonedDateTime 对象。对于与 UTC 的偏移,应用 ZoneOffset 以获取 OffsetDateTime 对象。在任何一种情况下,通过调用 toLocalDate 来提取仅日期值以获取 LocalDate 对象。

在您的情况下,您显然希望将日期视为 UTC。所以应用常量 ZoneOffset.UTC 得到一个 OffsetDateTime

OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;  
LocalDate ld = odt.toLocalDate() ;   // Extract a date-only value without time-of-day and without time zone.

我们想与当前的 UTC 日期进行比较。

LocalDate today = LocalDate.now( ZoneOffset.UTC ) ;  // Specify the offset/zone by which you want to perceive the current date.

比较。

if ( ld.isBefore( today ) ) { … }
else if ( ld.isEqual( today ) ) { … }
else if ( ld.isAfter( today ) ) { … }
else { … handle error }

ISO 8601

避免不必要地使用“yyyy/MM/dd”等自定义格式。尽可能使用标准的ISO 8601 格式。

对于仅日期值,应为 YYYY-MM-DD。

String output = LocalDate.now().toString() ;  // Ex: 2018-01-23

H2 为例

这是一个从 SQL 标准 DATE 类型的数据库列写入、查询和读取 LocalDate 对象的完整示例。

使用H2 Database Engine,因为我不是MySQL 用户。创建内存数据库而不是写入存储。我假设 MySQL 的代码几乎相同。

try (
    Connection conn = DriverManager.getConnection( "jdbc:h2:mem:trashme" )
) {
    String sql = "CREATE TABLE " + "tbl_" + " (\n" +
                     "  uuid_ UUID DEFAULT random_uuid() , \n" +  // Every table should have a primary key.
                     "  when_ DATE \n" +                          // Three columns per the Question.
                     " );";
    try (
        Statement stmt = conn.createStatement() ;
    ) {
        stmt.execute( sql );
    }

    sql = "INSERT INTO tbl_ ( when_ ) VALUES ( ? ) ;";
    LocalDate start = LocalDate.of( 2018 , Month.JANUARY , 23 );
    LocalDate ld = start;  // Keep the `start` object for use later.
    try (
        PreparedStatement ps = conn.prepareStatement( sql )
    ) {
        for ( int i = 1 ; i <= 10 ; i++ ) {
            ps.setObject( 1 , ld );
            ps.executeUpdate();
            // Prepare for next loop.
            int randomNumber = ThreadLocalRandom.current().nextInt( 1 , 5 + 1 ); // Pass minimum & ( maximum + 1 ).
            ld = ld.plusDays( randomNumber ); // Add a few days, an arbitrary number.
        }
    }

    // Dump all rows, to verify our populating of table.
    System.out.println( "Dumping all rows: uuid_ & when_ columns." );
    sql = "SELECT uuid_ , when_ FROM tbl_ ; ";
    int rowCount = 0;
    try (
        Statement stmt = conn.createStatement() ;
        ResultSet rs = stmt.executeQuery( sql ) ;
    ) {
        while ( rs.next() ) {
            rowCount++;
            UUID uuid = rs.getObject( 1 , UUID.class );
            LocalDate localDate = rs.getObject( 2 , LocalDate.class );
            System.out.println( uuid + " " + localDate );
        }
    }
    System.out.println( "Done dumping " + rowCount + " rows." );


    // Dump all rows, to verify our populating of table.
    System.out.println( "Dumping rows where `when_` is after " + start + ": uuid_ & when_ columns." );
    sql = "SELECT uuid_ , when_ FROM tbl_ WHERE when_ > ? ; ";
    rowCount = 0; // Reset count.
    final PreparedStatement ps = conn.prepareStatement( sql );
    ps.setObject( 1 , start );
    try (
        ps ;
        ResultSet rs = ps.executeQuery() ;
    ) {
        while ( rs.next() ) {
            rowCount++;
            UUID uuid = rs.getObject( 1 , UUID.class );
            LocalDate localDate = rs.getObject( 2 , LocalDate.class );
            System.out.println( uuid + " " + localDate );
        }
    }
    System.out.println( "Done dumping " + rowCount + " rows." );

} catch ( SQLException eArg ) {
    eArg.printStackTrace();
}

运行时。

Dumping all rows: uuid_ & when_ columns.
e9c75998-cd67-4ef9-9dce-6c1eed170387 2018-01-23
741c1452-e224-4e5e-95bc-904d8db58b39 2018-01-27
413de43c-a1be-40b6-9ccf-a9b7d9ba873c 2018-01-31
e2aa148f-48b6-4be6-a0fe-f2881b6b5a63 2018-02-03
f498003c-2d8b-446e-ac55-6d7568ce61c3 2018-02-06
c41606d7-8c05-4bba-9f8e-2a0d1f1bb31a 2018-02-09
3df3abe3-1865-4632-99c4-6cd74883c1ee 2018-02-10
914153fe-34f2-4e4f-a91b-944314994839 2018-02-13
96436bdf-80ee-4afe-b55d-f240140ace6a 2018-02-16
82b43f7b-077d-45c1-8c4f-f5b30dfdd44a 2018-02-19
Done dumping 10 rows.
Dumping rows where `when_` is after 2018-01-23: uuid_ & when_ columns.
741c1452-e224-4e5e-95bc-904d8db58b39 2018-01-27
413de43c-a1be-40b6-9ccf-a9b7d9ba873c 2018-01-31
e2aa148f-48b6-4be6-a0fe-f2881b6b5a63 2018-02-03
f498003c-2d8b-446e-ac55-6d7568ce61c3 2018-02-06
c41606d7-8c05-4bba-9f8e-2a0d1f1bb31a 2018-02-09
3df3abe3-1865-4632-99c4-6cd74883c1ee 2018-02-10
914153fe-34f2-4e4f-a91b-944314994839 2018-02-13
96436bdf-80ee-4afe-b55d-f240140ace6a 2018-02-16
82b43f7b-077d-45c1-8c4f-f5b30dfdd44a 2018-02-19
Done dumping 9 rows.

关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。

从哪里获得 java.time 类?

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

  • 这要简单得多。并让您升级到现代 API。热烈推荐。
  • 感谢您的详细回复!但是现在我面临一个新问题,我不知道为什么,但是当您尝试将日期存储在数据库中时没有任何反应,我没有收到任何错误或任何东西,日期也不会更新,知道吗? gist.github.com/kris914g/16b36602fc8edcae2f2929c818db391f
  • @kristoffer 我添加了一个完整的 H2 数据库示例。至于your linked example code: (a) 您在updateDate SQL 中声明了两个占位符,但您只设置了这两个中的一个。 (b) 你永远不会去执行那个准备好的语句。所以不会尝试更新。 (c) MySQL 语法中是否允许使用反引号字符?据我所知,不在标准 SQL 中。 (d) 提示:利用 Java 9 中新改进的 try-with-resources 语句。
【解决方案2】:

这完全取决于您使用的 Java 版本。如果您使用的是 Java 6 或 Java 7,那么这种方法将是最简单的:

当您处理 java.sql.Date 时,您可以使用这种快速方法来获取没有时间部分的 Date 并将 java.sql.Date 与 java.sql.Date 进行比较:

Date currentDate = new java.sql.Date(System.currentTimeMillis());

您还可以使用java.util.Date#beforejava.util.Date#after 方法以获得更好的代码可读性:

while(rs.next()){
    System.out.println(dateFormat.format(currentDate));
    if (rs.getDate(5).after(currentDate)) {
       // System.out.println("Database-date is after currentDate");
    } else if (rs.getDate(5).before(currentDate)) {
       // System.out.println("Database-date is before currentDate");
        PreparedStatement updatexdd = con.prepareStatement(queryy);
        updatexdd.setInt(1, rs.getInt(1));
        int updatexdd_done = updatexdd.executeUpdate();
    } else {
       // System.out.println("Database-date is equal to currentDate");
    }
}

如果您使用的是 Java 8,那么您可以使用新的 Java 时间 API。

【讨论】:

    【解决方案3】:

    ResultSet#getDate 返回一个删除了时间部分的日期。实例化一个新的Date 对象确实包含时间,因此您必须自己删除它。例如:

    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);
    Date currentDate = cal.getTime();
    

    【讨论】:

    • 或者利用Java 8中引入的日期/时间API,sqlDate.toLocalDate().compareTo(LocalDate.now()):P
    • 谢谢,我从来没有想过时间也包括在内
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-28
    • 2013-03-06
    • 1970-01-01
    相关资源
    最近更新 更多