将 java.util.Date 截断为 LocalDate *without* toInstant() 因为 java.sql.Date 给出了 UnsupportedOperationException

如果我在一个碰巧是 的变量上使用 's,我得到一个 .java.util.DatetoInstant()java.sql.DateUnsupportedOperationException

try {
    java.util.Date input = new java.sql.Date(System.currentTimeMillis());
    LocalDate date = input.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
} catch (UnsupportedOperationException e) {
    // grrr!
}

我所关心的来自mysql DB中通过遗留API的DATE字段,实际上是一个.java.util.Datejava.sql.Date

现在以下相关问题都非常有趣:

不支持的OperationException - 为什么你不能在java.sql.Date上调用instant()?

将 java.util.Date 转换为 java.time.LocalDate

LocalDate 到 java.util.Date,反之亦然 最简单的转换?

但是他们没有提供任何优雅的截断方法来摆脱时间组件并获得Java 8 。java.util.DateLocalDate

我承认存在一个问题,即一个时区的同一时间时刻可能与另一个时区的同一时刻的日期不同。

我怀疑这个解决方案将涉及java.util.Calendar,但与其制定自己的解决方案,我宁愿先确定其他人做了什么。

我宁愿找到比这更短的东西:

在 Java 中重置时间戳的时间部分

Date date = new Date();                      // timestamp now
Calendar cal = Calendar.getInstance();       // get calendar instance
cal.setTime(date);                           // set cal to date
cal.set(Calendar.HOUR_OF_DAY, 0);            // set hour to midnight
cal.set(Calendar.MINUTE, 0);                 // set minute in hour
cal.set(Calendar.SECOND, 0);                 // set second in minute
cal.set(Calendar.MILLISECOND, 0);            // set millis in second
Date zeroedDate = cal.getTime();             // actually computes the new Date

答案 1

通常,最简单的解决方案是最难找到的:

public LocalDate convertDateObject(java.util.Date suspectDate) {

    try {
        // Don't do this if there is the smallest chance 
        // it could be a java.sql.Date!
        return suspectDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

    } catch (UnsupportedOperationException e) {
        // BOOM!!
    }

    // do this first:
    java.util.Date safeDate = new Date(suspectDate.getTime());

    return safeDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

}

答案 2

如果已知变量是 一个 ,那么你可以简单地强制转换它并调用该方法:inputjava.sql.DatetoLocalDate()

LocalDate date = ((java.sql.Date) input).toLocalDate();

不幸的是,你不能调用一个,因为根据javadoc,它总是抛出一个.toInstant()java.sql.DateUnsupportedOperationException

如果您不知道类型(可以是 a 或 a),则可以使用方法返回的值来构建 ,然后将其转换为时区(下面我使用的是 JVM 的默认值),最后从中获取本地日期:java.util.Datejava.sql.DategetTime()Instant

LocalDate date = Instant
    // get the millis value to build the Instant
    .ofEpochMilli(input.getTime())
    // convert to JVM default timezone
    .atZone(ZoneId.systemDefault())
    // convert to LocalDate
    .toLocalDate();

该方法获取日期部分(日/月/年),忽略其余部分,因此无需截断它:无论时间是午夜,上午10点还是一天中的任何其他时间,都会忽略它并只获取日期部分。toLocalDate()toLocalDate()

但是,如果您真的想将时间设置为午夜,则可以使用 with 方法并向其传递 LocalTime

LocalDate date = Instant
    // get the millis value to build the Instant
    .ofEpochMilli(input.getTime())
    // convert to JVM default timezone
    .atZone(ZoneId.systemDefault())
    // set time to midnight
    .with(LocalTime.MIDNIGHT)
    // convert to LocalDate
    .toLocalDate();

但正如我所说,该方法将忽略时间部分,因此在这种情况下不需要设置时间(将相同)。toLocalDate()LocalDate


您还可以检查日期的类型并相应地选择正确的操作,如下所示:

if (input instanceof java.sql.Date) {
    date = ((java.sql.Date) input).toLocalDate();
} else {
    date = input.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}

您可以根据需要使用任何其他时区,而不是使用 JVM 默认时区 (),方法是调用 ,其中区域名称是任何有效的 IANA 时区名称(始终采用 格式为 ,如 或 )。避免使用 3 个字母的缩写(如 或 ),因为它们含糊不清且不标准ZoneId.systemDefault()ZoneId.of("zoneName")Region/CityAmerica/New_YorkEurope/LondonCETPST

您可以通过致电 获取可用时区的列表(并选择最适合您的系统的时区)。如果需要,您也可以继续使用JVM默认时区,但要提醒一下,即使在运行时也可以在不通知的情况下更改它,因此最好始终明确说明您正在使用哪个时区。ZoneId.getAvailableZoneIds()