【问题标题】:How to check validity of Java date type?如何检查 Java 日期类型的有效性?
【发布时间】:2019-04-08 04:57:20
【问题描述】:

我写了两个简单的函数来获取我在 MySQL 表中的日期值。开始日期和结束日期列都是 Date 数据类型。所以,在我的这两个函数中是这样的:

public Date get_startdate(long nodeid,String ts) {
    try {
        String sql="Select STARTDT FROM urllink WHERE URL='f0="+nodeid+"&ts="+ts + "'";
        if (em == null) {
             throw new Exception("could not found URL object.");
        }
        return (Date) em.createNativeQuery(sql).getSingleResult();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

public Date get_enddate(long nodeid,String ts) {
    try {
        String sql="Select ENDDT FROM urllink WHERE URL='f0="+nodeid+"&ts="+ts + "'";
        if (em == null) {
            throw new Exception("could not found URL object.");
        }
        return (Date) em.createNativeQuery(sql).getSingleResult();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

现在我像这样在我的主页中调用这些函数,我想检查日期条件,如果 URL 在这两个日期之间,那么它应该是有效的并做一些事情。我在下面强调我的意思:

Date file_getstartdate=fileFacade1.get_startdate(fileID,hash);
Date file_getenddate=fileFacade1.get_enddate(fileID,hash);

String currentDate = CoreUtil.parseDate(new Date());

if( file_getstartdate<= currentDate<= file_getendDate){
    //URL is valid, do something
}else {
    //do nothing
}

我存储在表中的日期格式为YYYY-MM-DD,而我面临的问题是在上面的if 语句中进行比较。我不能使用这些运算符进行检查。有没有办法实现我的愿望?

【问题讨论】:

  • 我建议你不要使用Date,不管是java.sql.Date还是java.util.Date。这些课程设计不良且早已过时。而是使用来自java.time, the modern Java date and time APILocalDate。它有 isAfterisBeforeisEqual 方法,可以满足您的需求。
  • 我知道使用 localeDate,因为它更新且更新更多,但它仅在 Java 8 中可用。我记得你上次确实提供了一个替代解决方案,如果我记得的话,关于使用一些向后可移植性功能正确。
  • 是的,java.time 已在 ThreeTen Backport 中向后移植到 Java 6 和 7。不幸的是,它没有与 Java Persistence 或 JDBC 集成,所以你必须做一些转换。

标签: java date-comparison date


【解决方案1】:

不要使用字符串连接来构造 SQL 查询

这很容易受到 SQL 注入的影响,而且非常危险:

String sql="Select STARTDT FROM urllink WHERE URL='f0="+nodeid+"&ts="+ts + "'";
...
return (Date) em.createNativeQuery(sql).getSingleResult();

假设em 指的是EntityManager 对象,那么您可以构建一个基于Criteria 的查询,或者如果您确实需要坚持使用本机SQL,那么您应该使用PreparedStatement

对于您的比较问题,您有三个问题:

  • 您正在尝试比较两种不同的类型(StringDate)。
  • 您正在尝试使用只能用于比较基元而非对象的运算符 (&lt;=)。
  • 您将条件写成数学语句而不是编程条件语句(a &lt;= b &lt;= c 不是有效的 Java 语句。它必须是 a &lt;= b &amp;&amp; b &lt;= c)。

这是一种使用 Date 类进行比较的方法(请注意,由于 Java 8 LocalDate 是一个更好的类,如果您有选择的话)。

Date fileStartDate = fileFacade1.get_startdate(fileID, hash);
Date fileEndDate = fileFacade1.get_enddate(fileID, hash);

Date currentDate = new Date();

if (fileStartDate.before(currentDate) && fileEndDate.after(currentDate) {
  ...

回应您的评论

好的,但是使用preparedStatement 对这个SQL 注入有帮助。实体管理器是否阻止注入?我被告知不要使用preparedstatement,还有其他选择吗?

如果有人告诉您使用字符串连接(代码中的 +nodeid++ts + 部分)而不是使用 PreparedStatement 进行本机查询,那么他们就错了。 EntityManager 不会保护您免受上述代码中的注入,但 PreparedStatement 以及更改查询的构造方式会。

PreparedStatement 看起来像

String url = "f0=" + nodeid + "&ts=" + ts;
PreparedStatement preparedStatement = connection.prepareStatement("Select STARTDT FROM urllink WHERE URL= ?");
preparedStatement.setString(1, url);
ResultSet resultSet = preparedStatement.executeQuery();

如果您被告知使用EntityManager 而不是编写本机 SQL,那么这实际上是个好建议。您需要使用Criteria 抽象来构造您的查询。如何做到这一点可能是一个单独的问题。

【讨论】:

  • 但是我的表格以YYYY-MM-DD 格式存储并且不需要时间戳来进行比较,这可能吗?
  • YYYY-MM-DD 的优点是字符串比较将得到与将其转换为日期然后进行比较相同的结果。所以要么使用两个字符串和String.compareTo(),要么使用两个LocalDates 和LocalDate.compareTo()。无论哪种方式,您都应该得到相同的结果。
  • 我不关注,你能提供答案吗?
  • @Daredevil 我在其中编辑了一个示例。另外,请不要忽视我所说的关于 SQL 注入的内容,这非常重要。
  • 我想添加localdate,因为我使用的是java 7,所以我无法使用。在您的示例中,您可以将日期类型与currentDate的字符串类型进行比较,我很困惑?
【解决方案2】:

tl;博士

if( file_getstartdate&lt;= currentDate&lt;= file_getendDate) { … }

LocalDateRange                                // Represent a span-of-time as a pair of `LocalDate` (date-only) objects.
.ofClosed( startLocalDate , stopLocalDate )   // Making the date range in fully-closed approach, against my advice of using half-open approach.
.contains(                                    // Compares a specified `LocalDate` against the start and stop dates of the range.
    LocalDate.now(                            // Use `java.time.LocalDate` to represent a date-only value without a time-of-day and without a time zone.
        ZoneId.of( "Africa/Tunis" )           // Specify the time zone by which we want to perceive the calendar date for the current moment. "Tomorrow" arrives in Paris France while still "yesterday" in Montréal Québec.
    )                                         // Returns a `LocalDate` object.
)                                             // Returns a `boolean` primitive.

java.time

Answer by Player One 是正确的,但可以通过使用现代的 java.time 类而不是糟糕的遗留类(Date 等)来改进。

LocalDate

对于 SQL 标准 DATE 类型的列,请使用 LocalDate 类。 LocalDate 类表示仅日期值,没有时间,也没有 time zoneoffset-from-UTC

时区

时区对于确定日期至关重要。对于任何给定的时刻,日期在全球范围内因区域而异。例如,Paris France 中午夜后几分钟是新的一天,而 Montréal Québec 中仍然是“昨天”。

如果没有指定时区,JVM 会隐式应用其当前的默认时区。该默认值可能在运行时(!)期间change at any moment,因此您的结果可能会有所不同。最好将您想要/预期的时区明确指定为参数。如果关键,请与您的用户确认该区域。

ZoneID

Continent/Region 的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用 2-4 个字母的缩写,例如 ESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。

ZoneId z = ZoneId.of( "America/Montreal" ) ;  
LocalDate today = LocalDate.now( z ) ;

如果你想使用 JVM 当前的默认时区,请求它并作为参数传递。如果省略,代码会变得难以阅读,因为我们不确定您是否打算使用默认值,或者您是否像许多程序员一样没有意识到这个问题。

ZoneId z = ZoneId.systemDefault() ;  // Get JVM’s current default time zone.

半开

if(file_getstartdate

如果您始终使用半开放式方法来定义时间跨度,您会发现您的工作会更轻松。在这种方法中,开头是inclusive,而结尾是exclusive。因此,一个月从第一个月开始,一直持续到但不包括个月的第一个月。

因此,您的查询逻辑将如下所示,使用&lt;=&lt;

if(file_getstartdate

这意味着在 SQL 中,不要BETWEEN 用于日期时间工作。

String sql = "SELECT * from tbl WHERE when >= ? AND when < ? ;" ;
…
myPreparedStatement.setObject( 1 , today ) ;
myPreparedStatement.setObject( 2 , today ) ;

要检索DATE,值:

LocalDate localDate = myResultSet.getObject( … , LocalDate.class ) ;

Java 代码中相同类型的半开放逻辑类似于以下代码。请注意,“等于或晚于”的更简短的说法是“不在之前”。

boolean isTodayWithinDateRange = 
    ( ! today.isBefore( startDate ) )  // Is today same or later than start…
    &&                                 // AND
    today.isBefore( stopDate )         // Is today before the stop (for Half-Open approach). 
;

不要将日期时间值与文本混为一谈

我存储在表中的日期格式为 YYYY-MM-DD

不,不是。 MySQL 中的DATE 类型通过其内部定义的机制存储日期,而不是纯文本。

诸如数据库中的DATE 列或Java 中的LocalDate 之类的日期时间对象可以将输入的字符串解析为日期值,并可以从该日期值生成字符串。但日期值本身不是字符串。不要混淆两者。换句话说,日期值没有“格式”,只有它们的文本表示有格式。

LocalDateRange

如果要完成大部分工作,请将ThreeTen-Extra 库添加到您的项目中。这使您可以访问LocalDateRange 类。

LocalDate start = … ;
LocalDate stop = … ;
LocalDateRange range = LocalDateRange.of( start , stop ) ;
boolean rangeContainsToday = range.contains( today ) ;

默认情况下,LocalDateRange 类使用半开方法工作。但是,如果您坚持使用完全封闭的方法,请使用 LocalDateRange.ofClosed 方法覆盖其默认行为。


关于java.time

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

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

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

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

从哪里获得 java.time 类?

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

【讨论】:

    【解决方案3】:

    如果您使用字符串而不是日期对象,那么 if 语句将适用于该格式,假设您将其编写为 (a

    否则,我不确定如何将getSingleResult() 转换为 Date 对象,因为这可能是任何东西,您实际上必须解析该值,然后使用适当的 Date 方法检查 isBefore 和 isAfter

    Java: how do I check if a Date is within a certain range?

    【讨论】:

    • 获取单个结果返回说开始日期为2019-04-08
    • 我不知道你使用什么 sql 库,但我希望 ResultSet 对象不能转换为 Date
    • 如果我将它们转换为字符串并使用&lt;&gt; 来执行if 语句,它会起作用吗?
    • 应该,是的。
    • 另一种选择是将Date String 传递到SQL 并在那里进行比较
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多