【问题标题】:Truncating java.util.Date to LocalDate *without* toInstant() because java.sql.Date gives UnsupportedOperationException将 java.util.Date 截断为 LocalDate *without* toInstant() 因为 java.sql.Date 给出了 UnsupportedOperationException
【发布时间】:2018-03-29 10:03:52
【问题描述】:

如果我在恰好是java.sql.Date 的变量上使用java.util.DatetoInstant(),我会得到UnsupportedOperationException

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

我关心的 java.util.Date 来自 mysql DB 中的 DATE 字段,通过旧版 API,实际上是 java.sql.Date

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

UnsupportedOperationException - Why can't you call toInstant() on a java.sql.Date?

Convert java.util.Date to java.time.LocalDate

LocalDate to java.util.Date and vice versa simplest conversion?

但它们没有提供任何优雅的方式来截断 java.util.Date 以摆脱时间组件并获得 Java 8 LocalDate

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

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

我希望找到比这更短的:

来自Resetting the time part of a timestamp in 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

【问题讨论】:

  • LocalDate date = ((java.sql.Date) input).toLocalDate(); 还不够吗?
  • @VGR 没错。在使用toInstant() 时,JavaDoc 说:此方法总是抛出 UnsupportedOperationException 并且不应使用,因为 SQL 日期值没有时间组件。
  • @Hugo 它也可能不是java.sql.Date
  • 我认为您的问题在于继承关系具有欺骗性:它表明您应该能够统一和多态地处理 Date 类型,这不是故意的。

标签: java date datetime java-time date-conversion


【解决方案1】:

我不知道为什么,但这段代码对我有用:)

LocalDate ld =new java.util.Date(personEntity.getBirthday().getTime()).toInstant() .atZone(ZoneId.systemDefault()).toLocalDate();

这不起作用:

LocalDate ld =personEntity.getBirthday().getTime().toInstant() .atZone(ZoneId.systemDefault()).toLocalDate();

【讨论】:

    【解决方案2】:

    tl;博士

    您在java.sql.Datejava.util.Date 之间交叉的前提是不正确的。

    如果我在一个恰好是 java.sql.Date 的变量上使用 java.util.Date ...

    您违反了课程文档中规定的合同。你被告知忽略java.sql.Datejava.util.Date 的子类这一事实。

    但解决方案可能很简单。

    ZonedDateTime zdt = myJavaSqlDate.toLocalDate().atStartOfDay( ZoneId.of( "America/Montreal" ) ) ;
    

    详情

    显然你手头有一个java.sql.Date。该类表示没有时间和时区的仅日期值。至少那是那个班级的意图。从 java.util.Date 子类化的设计选择非常糟糕,尽管名称如此,但它表示日期 UTC 时间。这种继承关系是一种 hack,一种糟糕的 hack,他们将内部时间设置为“00:00:00”。文档明确指示我们忽略这种继承关系,忽略具有时间组件的事实,并假装这两个Date 类是不相关的。但是像作者这样的许多人并没有仔细阅读该文档,而是根据类名做出粗略的假设。

    Date 两个类都是 Java 最早版本附带的日期时间框架的一部分。这些已被 Java 8 及更高版本中内置的 java.time 类所取代。

    遇到遗留日期时间对象的第一步:使用添加到旧类的新方法转换为 java.time。

    将我们的虚假仅日期对象java.sql.Date 转换为类型为java.util.LocalDate 的真正仅日期对象。 “本地”一词表示没有时区或与 UTC 的偏移量。

    LocalDate ld = myJavaSqlDate.toLocalDate() ;
    

    显然,您希望将该仅日期值转换为具有一天中时间的日期。也许你想要一天的第一刻。确定第一时刻需要一个时区。例如,Europe/Paris 的新一天比America/Montreal 更早出现,而且Asia/Kolkata 更早。

    ZoneId z = ZoneId.of( "Pacific/Auckland" ) ; 
    ZonedDateTime zdt = ld.atStartOfDay( z ) ;
    

    【讨论】:

      【解决方案3】:

      试试这个。

      java.util.Date input = new java.sql.Date(System.currentTimeMillis());
      Instant instant = Instant.ofEpochMilli(input.getTime());
      LocalDate date = instant .atZone(ZoneId.systemDefault()).toLocalDate();
      

      【讨论】:

        【解决方案4】:

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

        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();
        
        }
        

        【讨论】:

          【解决方案5】:

          tl;博士

          myResultSet.getObject( … , LocalDate.class ) 
          

          java.time

          您的麻烦始于使用旧的日期时间类,例如 java.sql.Datejava.util.DateCalendar。完全避免这些课程。它们现在是遗留的,被 java.time 类所取代。

          您说该值以DATE 类型的 MySQL 列中的存储值开始。该类型是仅限日期的,没有时间。所以您通过使用错误的类不必要地引入了时间值

          使用符合 JDBC 4.2 或更高版本的 JDBC 驱动程序使用 java.time 类与数据库交换值。

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

          并传递给PreparedStatement

          myPstmt.setObject( … , myLocalDate ) ;
          

          【讨论】:

          • 检测到明显不悔改的java.util.Date 用户的挫败感,我愿意。不幸的是,我目前无法重新编程我正在使用的 API,否则我肯定会按照您建议的方式实现。
          【解决方案6】:

          如果已知input 变量是java.sql.Date,那么您可以简单地转换它并调用toLocalDate() 方法:

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

          不幸的是,您不能在java.sql.Date 上调用toInstant(),因为according to javadoc 总是会抛出UnsupportedOperationException

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

          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();
          

          toLocalDate() 方法获取日期部分(日/月/年),忽略其余部分,因此无需截断它:时间是午夜、上午 10 点还是任何其他时间都没有关系当天,toLocalDate() 将忽略它并仅获取日期部分。

          如果您真的想将时间设置为午夜,您可以使用with method 并将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 默认时区 (ZoneId.systemDefault()),您还可以根据需要使用任何其他时区,方法是调用 ZoneId.of("zoneName"),其中区域名称是任何有效的 IANA timezones names(始终在格式Region/City,如America/New_YorkEurope/London)。 避免使用三个字母的缩写(如CETPST),因为它们是ambiguous and not standard

          您可以致电ZoneId.getAvailableZoneIds() 获取可用时区列表(并选择最适合您系统的时区)。如果您愿意,您也可以继续使用 JVM 默认时区,但请注意 can be changed without notice, even at runtime,因此最好始终明确说明您使用的是哪个时区。

          【讨论】:

            【解决方案7】:

            您是否尝试使用 SimpleDateFormat 来帮助您?

            import java.text.ParseException;
            import java.text.SimpleDateFormat;
            import java.time.LocalDate;
            import java.time.ZoneId;
            import java.util.Date;
            
            public class Utils {
            
                private static SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd" );
            
                public static void main( String[] args ) {
                    LocalDate ld = sqlDateToLocalDate( new java.sql.Date( System.currentTimeMillis() ) );
                    System.out.println( ld );
                }
            
                public static LocalDate sqlDateToLocalDate( java.sql.Date sqlDate ) {
            
                    try {
                        Date d = sdf.parse( sdf.format( sqlDate ) );
                        return d.toInstant().atZone( ZoneId.systemDefault() ).toLocalDate();
                    } catch ( ParseException exc ) {
                    }
            
                    return null;
            
                }
            
            }
            

            【讨论】:

            • 我建议你尽量减少使用像 Date 这样过时的类,当然不要引入臭名昭著的麻烦 SimpleDateFormat。此外,此解决方案比其他答案中的解决方案要复杂一些。
            • 您正在使用已被 java.time 类取代的过时类。
            猜你喜欢
            • 1970-01-01
            • 2011-08-29
            • 2016-02-14
            • 2019-07-22
            • 2012-08-21
            • 1970-01-01
            • 1970-01-01
            • 2011-12-08
            • 2018-07-15
            相关资源
            最近更新 更多