Java 将java.util.Date转换为java.time.LocalDate,用户希望将 java.util.Date
或 java.util.Calendar
类转换为 java.time
包中相应的类,转换时既可以利用 Instant
类作为中介,也可以使用 java.sql.Date
和 java.sql.Timestamp
类提供的方法,还可以使用字符串或整数。
Java 将java.util.Date转换为java.time.LocalDate 问题描述
用户希望将 java.util.Date
或 java.util.Calendar
类转换为 java.time
包中相应的类。
Java 将java.util.Date转换为java.time.LocalDate 解决方案
转换时既可以利用 Instant
类作为中介,也可以使用 java.sql.Date
和 java.sql.Timestamp
类提供的方法,还可以使用字符串或整数。
Java 将java.util.Date转换为java.time.LocalDate 具体实例
新的 java.time
包并未提供太多的内置方式来转换 java.util
包中用于处理标准日期和时间的类,这点或许会让读者感到讶异。
为了将 java.util.Date
类转换为 java.time.LocalDate
类,一种方案是调用 toInstant
方法来创建 Instant
,然后应用系统默认时区(ZoneId
),并从生成的 ZonedDateTime
中提取出 LocalDate
,如例 8-18 所示。
例 8-18 利用
Instant
作为中介,将java.util.Date
类转换为java.time.LocalDate
类
public LocalDate convertFromUtilDateUsingInstant(Date date) {
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}
java.util.Date
类包含日期和时间信息,但并不提供时区信息 6,因此它相当于 java.time.Instant
类。将 atZone
方法应用到系统默认时区将创建 ZonedDateTime
,之后就能从中提取出 LocalDate
。
6打印 java.util.Date
时,字符串采用 Java 默认的时区进行格式化。
此外,借由 java.sql.Date
和 java.sql.Timestamp
类定义的一些方法,也可以方便地将 java.util.Date
类转换为 java.time.LocalDate
类,相关示例请参见例 8-19 和例 8-20。
例 8-19
java.sql.Date
类中的转换方法
LocalDate toLocalDate()
static Date valueOf(LocalDate date)
例 8-20
java.sql.Timestamp
类中的转换方法
LocalDateTime toLocalDateTime()
static Timestamp valueOf(LocalDateTime dateTime)
如例 8-21 所示,我们创建一个名为 ConvertDate
的类,从而方便地实现转换。
例 8-21 将
java.util
包中的类转换为java.time
包中相应的类
package datetime;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
public class ConvertDate {
public LocalDate convertFromSqlDatetoLD(java.sql.Date sqlDate) {
return sqlDate.toLocalDate();
}
public java.sql.Date convertToSqlDateFromLD(LocalDate localDate) {
return java.sql.Date.valueOf(localDate);
}
public LocalDateTime convertFromTimestampToLDT(Timestamp timestamp) {
return timestamp.toLocalDateTime();
}
public Timestamp convertToTimestampFromLDT(LocalDateTime localDateTime) {
return Timestamp.valueOf(localDateTime);
}
}
既然所需的方法基于 java.sql.Date
类,那么如何转换 java.util.Date
(大部分开发人员仍在使用)以及 java.sql.Date
类呢?一种方案是利用 java.sql.Date
类提供的构造函数,根据给定的毫秒时间值创建一个 Date
对象(long
型数据)。
纪元时间与 Java
在基于 Unix 的操作系统中,Unix 纪元时间(Unix epoch)也称为 Unix 时间戳(Unix timestamp)或 POSIX 时间(POSIX time),定义为从 1970 年 1 月 1 日 00:00:00 UTC 起经过的秒数,不考虑闰秒。目前,计算机的系统时钟均以纪元时间为基础。
需要注意的是,由于 Unix 纪元时间采用 32 位有符号整数存储经过的秒数,将在 2038 年 1 月19 日03:14:07 UTC 时溢出。在这一刻之后,全球所有 32 位操作系统的时间将突然跳回 1901 年 12 月 13 日。这就是所谓的“2038 年问题”(Year 2038 Problem)7。尽管到那个时候,所有操作系统应该都已升级为 64 位,但可能仍有部分嵌入式系统尚未更新8。
Java 采用毫秒作为经过时间的单位,这或许会让情况变得更加糟糕。不过使用 long 而非 int 存储经过时间,可以将溢出问题推后数千年。
7参见维基百科的详细介绍。(32 位有符号整数的最大值为 0x7FFFFFFF,即 2^31 – 1= 2147483647 秒,也就是 2038 年 1 月 19 日 03:14:07 UTC。可以通过 Epoch Converter 方便地将 Unix 纪元时间转换为人类可读的格式。——译者注)
8作者届时想必已安全退休,不过当 2038 年问题发生时,希望作者使用的呼吸机不会受到影响。
java.util.Date
类定义了一个返回 long
型数据的 getTime
方法,而 java.sql.Date
类定义了一个传入该 long
值作为参数的构造函数 setTime
9。
9事实上,setTime
是 java.sql.Date
类中唯一一个未被弃用的构造函数,可以利用该方法来设置现有的 Date
对象。
因此,借由 java.sql.Date
类,也可以将 java.util.Date
实例转换为 java.time.LocalDate
实例,如例 8-22 所示。
例 8-22 将
java.util.Date
类转换为java.time.LocalDate
类
public LocalDate convertUtilDateToLocalDate(java.util.Date date) {
return new java.sql.Date(date.getTime()).toLocalDate();
}
实际上,早在 Java 1.1 发布时,整个 java.util.Date
类就已被弃用,并推荐采用 java.util.Calendar
类作为替代。Calendar
实例与 java.time
包中相应实例之间的转换可以通过 toInstant
方法完成,并根据时区进行调整,如例 8-23 所示。
例 8-23 将
java.util.Calendar
类转换为java.time. ZonedDateTime
类
public ZonedDateTime convertFromCalendar(Calendar cal) {
return ZonedDateTime.ofInstant(cal.toInstant(), cal.getTimeZone().toZoneId());
}
上述方法使用 ZonedDateTime
类。LocalDateTime
类也定义了一个名为 ofInstant
的方法,不过由于某种原因,该方法传入 ZoneId
作为第二个参数。因为 LocalDateTime
类并不包含时区信息,这显得颇为奇怪。有鉴于此,改用 ZonedDateTime
类定义的 ofInstant
方法或许更加直观。
如果完全不必考虑时区信息,也可以在 Calendar
类上显式地使用各种 getter 方法,直接转换为相应的 LocalDateTime
,如例 8-24 所示。
例 8-24 利用 getter 方法将
java.util.Calendar
转换为java.time.LocalDateTime
public LocalDateTime convertFromCalendarUsingGetters(Calendar cal) {
return LocalDateTime.of(cal.get(Calendar.YEAR),
cal.get(Calendar.MONTH),
cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR),
cal.get(Calendar.MINUTE),
cal.get(Calendar.SECOND));
}
另一种方案是根据 Calendar
类生成一个经过格式化的字符串,然后将其解析为 LocalDateTime
,如例 8-25 所示。
例 8-25 生成并解析时间戳字符串
public LocalDateTime convertFromUtilDateToLDUsingString(Date date) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
return LocalDateTime.parse(df.format(date),
DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
上述方案并非特别理想,不过了解它并无坏处。此外,Calendar
类并未提供能直接转换为 ZonedDateTime
的方法,但 GregorianCalendar
类定义的 toZonedDateTime
方法可以实现这种转换,如例 8-26 所示。
例 8-26 将
java.util.GregorianCalendar
类转换为java.time.ZonedDateTime
类
public ZonedDateTime convertFromGregorianCalendar(Calendar cal) {
return ((GregorianCalendar) cal).toZonedDateTime();
}
上述程序可以执行,不过前提是采用公历(Gregorian calendar)。由于 GregorianCalendar
类是 Calendar
类的唯一实现,这种前提应该成立,但无法百分之百确定。
最后,Java 9 为 LocalDate
类引入了 ofInstant
方法,使得转换操作更为简单,如例 8-27 所示。
例 8-27 将
java.util.Date
类转换为java.time.LocalDate
类(仅针对 Java 9)
public LocalDate convertFromUtilDateJava9(Date date) {
return LocalDate.ofInstant(date.toInstant(), ZoneId.systemDefault());
}
这种方案更直接,但仅能在 Java 9 中使用。