Java 8 引入的 Date-Time API (java.time 包) 为日期和韶光处理带来了显著的改进,办理了 java.util.Date 类的许多痛点:
非线程安全时区处理麻烦格式化和韶光打算繁琐设计有缺陷,Date 类同时包含日期和韶光;还有一个 java.sql.Date,随意马虎稠浊。
本文将详细讲解 Java 8 新的 Date-Time API,并通过源码和示例代码比拟 Java 8 之前的实现办法,深入阐发其设计的用意和目的。

java.time 紧张类
java.util.Date 既包含日期又包含韶光,而 java.time 将它们进行了分离:
LocalDateTime:日期和韶光,格式为 yyyy-MM-ddTHH:mm:ss.SSSLocalDate:仅日期,格式为 yyyy-MM-ddLocalTime:仅韶光,格式为 HH:mm:ss
格式化Java 8 之前:
java
public void oldFormat(){ Date now = new Date(); //格式化 yyyy-MM-dd SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String date = sdf.format(now); System.out.println(String.format("date format : %s", date)); //格式化 HH:mm:ss SimpleDateFormat sdft = new SimpleDateFormat("HH:mm:ss"); String time = sdft.format(now); System.out.println(String.format("time format : %s", time)); //格式化 yyyy-MM-dd HH:mm:ss SimpleDateFormat sdfdt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String datetime = sdfdt.format(now); System.out.println(String.format("dateTime format : %s", datetime));}
Java 8 之后:
java
public void newFormat(){ //格式化 yyyy-MM-dd LocalDate date = LocalDate.now(); System.out.println(String.format("date format : %s", date)); //格式化 HH:mm:ss LocalTime time = LocalTime.now().withNano(0); System.out.println(String.format("time format : %s", time)); //格式化 yyyy-MM-dd HH:mm:ss LocalDateTime dateTime = LocalDateTime.now(); DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String dateTimeStr = dateTime.format(dateTimeFormatter); System.out.println(String.format("dateTime format : %s", dateTimeStr));}
字符串转日期格式
Java 8 之前:
java
//已弃用Date date = new Date("2021-01-26");//更换为SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");Date date1 = sdf.parse("2021-01-26");
Java 8 之后:
java
LocalDate date = LocalDate.of(2021, 1, 26);LocalDate.parse("2021-01-26");LocalDateTime dateTime = LocalDateTime.of(2021, 1, 26, 12, 12, 22);LocalDateTime.parse("2021-01-26T12:12:22");LocalTime time = LocalTime.of(12, 12, 22);LocalTime.parse("12:12:22");
Java 8 之前 的转换须要借助 SimpleDateFormat 类,而 Java 8 之后 只须要利用 LocalDate、LocalTime、LocalDateTime 的 of 或 parse 方法。
日期打算
以下以打算 一周后的日期 为例,其他单位(年、月、日、时等)类似。这些单位都在 java.time.temporal.ChronoUnit 列举中定义。
Java 8 之前:
java
public void afterDay(){ //一周后的日期 SimpleDateFormat formatDate = new SimpleDateFormat("yyyy-MM-dd"); Calendar ca = Calendar.getInstance(); ca.add(Calendar.DATE, 7); Date d = ca.getTime(); String after = formatDate.format(d); System.out.println("一周后日期:" + after); //算两个日期间隔多少天,打算间隔多少年,多少月方法类似 String dates1 = "2021-12-23"; String dates2 = "2021-02-26"; SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); Date date1 = format.parse(dates1); Date date2 = format.parse(dates2); int day = (int) ((date1.getTime() - date2.getTime()) / (1000 3600 24)); System.out.println(dates1 + "和" + dates2 + "相差" + day + "天");}
Java 8 之后:
java
public void pushWeek(){ //一周后的日期 LocalDate localDate = LocalDate.now(); //方法1 LocalDate after = localDate.plus(1, ChronoUnit.WEEKS); //方法2 LocalDate after2 = localDate.plusWeeks(1); System.out.println("一周后日期:" + after); //算两个日期间隔多少天,打算间隔多少年,多少月 LocalDate date1 = LocalDate.parse("2021-02-26"); LocalDate date2 = LocalDate.parse("2021-12-23"); Period period = Period.between(date1, date2); System.out.println("date1 到 date2 相隔:" + period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "天"); //获取总天数 long day = date2.toEpochDay() - date1.toEpochDay(); System.out.println(date1 + "和" + date2 + "相差" + day + "天");}
获取指定日期
获取特定一个日期,如本月末了一天或第一天。
Java 8 之前:
java
public void getDay() { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); //获取当前月第一天: Calendar c = Calendar.getInstance(); c.set(Calendar.DAY_OF_MONTH, 1); String first = format.format(c.getTime()); System.out.println("first day:" + first); //获取当前月末了一天 Calendar ca = Calendar.getInstance(); ca.set(Calendar.DAY_OF_MONTH, ca.getActualMaximum(Calendar.DAY_OF_MONTH)); String last = format.format(ca.getTime()); System.out.println("last day:" + last); //当年末了一天 Calendar currCal = Calendar.getInstance(); Calendar calendar = Calendar.getInstance(); calendar.clear(); calendar.set(Calendar.YEAR, currCal.get(Calendar.YEAR)); calendar.roll(Calendar.DAY_OF_YEAR, -1); Date time = calendar.getTime(); System.out.println("last day:" + format.format(time));}
Java 8 之后:
java
public void getDayNew() { LocalDate today = LocalDate.now(); //获取当前月第一天: LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth()); //获取当前月末了一天: LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth()); //获取下一天: LocalDate nextDay = lastDayOfThisMonth.plusDays(1); //获取当年末了一天: LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear()); //2021年末了一个周日 LocalDate lastSundayOf2021 = LocalDate.parse("2021-12-31").with(TemporalAdjusters.lastInMonth(DayOfWeek.SUNDAY));}
java.time.temporal.TemporalAdjusters 供应了很多便捷的方法来获取特定日期。
JDBC 和 Java 8
现在 JDBC 韶光类型和 Java 8 韶光类型的对应关系是:
Date ---> LocalDateTime ---> LocalTimeTimestamp ---> LocalDateTime
而之前统统对应 Date 类型。
时区
java.util.Date 工具实际上存储的是 1970 年 1 月 1 日 0 点(GMT)至 Date 工具所表示时候所经由的毫秒数。因此,它记录的毫秒数与时区无关,但在利用上须要转换本钱地韶光,这就涉及到了韶光的国际化。java.util.Date 本身并不支持国际化,须要借助 TimeZone。
java
//北京韶光Date date = new Date();SimpleDateFormat bjSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//北京时区bjSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));System.out.println("毫秒数:" + date.getTime() + ", 北京韶光:" + bjSdf.format(date));//东京时区SimpleDateFormat tokyoSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");tokyoSdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));System.out.println("毫秒数:" + date.getTime() + ", 东京韶光:" + tokyoSdf.format(date));//直接打印会自动转成当前时区韶光System.out.println(date);
在新特性中引入了 java.time.ZonedDateTime 来表示带时区的韶光。它可以算作是 LocalDateTime + ZoneId。
java
//当前时区韶光ZonedDateTime zonedDateTime = ZonedDateTime.now();System.out.println("当前时区韶光: " + zonedDateTime);//东京韶光ZoneId zoneId = ZoneId.of(ZoneId.SHORT_IDS.get("JST"));ZonedDateTime tokyoTime = zonedDateTime.withZoneSameInstant(zoneId);System.out.println("东京韶光: " + tokyoTime);// ZonedDateTime 转 LocalDateTimeLocalDateTime localDateTime = tokyoTime.toLocalDateTime();System.out.println("东京韶光转当地韶光: " + localDateTime);//LocalDateTime 转 ZonedDateTimeZonedDateTime localZoned = localDateTime.atZone(ZoneId.systemDefault());System.out.println("本地时区韶光: " + localZoned);
java.time 包与 Joda-Time 的比拟
Java 8 引入的 java.time 包是对日期和韶光处理的一次重大改进,它借鉴了许多 Joda-Time 的设计理念,并在此根本上进行了优化。那么,java.time 包与 Joda-Time 比较有哪些上风和劣势呢?
1. 设计理念和 API 风格
Joda-Time
设计理念:Joda-Time 旨在供应一个更直不雅观和更强大的日期韶光处理库,填补 java.util.Date 和 java.util.Calendar 的不敷。API 风格:Joda-Time 的 API 风格非常直不雅观,类名和方法名都非常清晰易懂。例如,DateTime 类表示日期和韶光,LocalDate 类表示仅日期。
Java 8 java.time
设计理念:java.time 包是基于 JSR 310 的规范实现,接管了 Joda-Time 的优点,并在此根本上进行了优化和改进。API 风格:java.time 包的 API 风格与 Joda-Time 类似,但在命名和构造上更加规范。例如,LocalDateTime 表示日期和韶光,LocalDate 表示仅日期,LocalTime 表示仅韶光。
2. 集成和兼容性Joda-Time
集成:Joda-Time 是一个独立的第三方库,须要手动添加依赖。兼容性:Joda-Time 与 Java SE 8 之前的版本兼容,适用于所有版本的 Java。
Java 8 java.time
集成:java.time 包是 Java SE 8 的一部分,无需额外添加依赖。兼容性:java.time 包仅适用于 Java SE 8 及以上版本,对付 Java 8 之前的版本,须要额外的兼容性库(如 ThreeTen-Backport)。
3. 性能和线程安全Joda-Time
性能:Joda-Time 的性能相对较好,但在某些操作上可能不如 java.time 包高效。线程安全:Joda-Time 的大多数类是不可变的,因此是线程安全的。
Java 8 java.time
性能:java.time 包在设计时考虑了性能优化,尤其是在日期打算和格式化等高频操作上性能更优。线程安全:java.time 包中的所有类都是不可变的,因此也是线程安全的。
4. 功能和扩展性Joda-Time
功能:Joda-Time 供应了丰富的功能,支持各种日期韶光操作、格式化、解析、时区转换等。扩展性:Joda-Time 供应了一些扩展功能,例如自定义韶光单位和字段。
Java 8 java.time
功能:java.time 包供应了与 Joda-Time 类似的功能,并在某些方面进行了增强。例如,供应了更丰富的日期调度器(TemporalAdjuster)和更强大的韶光量(Duration 和 Period)。扩展性:java.time 包也供应了良好的扩展性,许可用户自定义韶光单位和字段。
示例比拟Joda-Time 示例
java
import org.joda.time.DateTime;import org.joda.time.format.DateTimeFormat;import org.joda.time.format.DateTimeFormatter;public class JodaTimeExample { public static void main(String[] args) { // 当前韶光 DateTime now = DateTime.now(); System.out.println("当前韶光: " + now); // 格式化 DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); String formattedDate = now.toString(formatter); System.out.println("格式化后的韶光: " + formattedDate); // 解析 DateTime parsedDate = DateTime.parse("2021-01-26 12:12:22", formatter); System.out.println("解析后的韶光: " + parsedDate); // 日期打算 DateTime oneWeekLater = now.plusWeeks(1); System.out.println("一周后的韶光: " + oneWeekLater); }}
Java 8 java.time 示例
java
import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;public class JavaTimeExample { public static void main(String[] args) { // 当前韶光 LocalDateTime now = LocalDateTime.now(); System.out.println("当前韶光: " + now); // 格式化 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String formattedDate = now.format(formatter); System.out.println("格式化后的韶光: " + formattedDate); // 解析 LocalDateTime parsedDate = LocalDateTime.parse("2021-01-26 12:12:22", formatter); System.out.println("解析后的韶光: " + parsedDate); // 日期打算 LocalDateTime oneWeekLater = now.plusWeeks(1); System.out.println("一周后的韶光: " + oneWeekLater); }}
总结
上风集成:java.time 包是 Java 8 的一部分,无需额外依赖,集成更方便。性能:java.time 包在性能上进行了优化,尤其是在高频日期韶光操作上。线程安全:java.time 包中的所有类都是不可变的,因此线程安全。标准化:java.time 包是基于 JSR 310 的规范实现,更加标准化。劣势兼容性:java.time 包仅适用于 Java SE 8 及以上版本,对付 Java 8 之前的版本须要额外的兼容性库。学习本钱:对付熟习 Joda-Time 的开拓者来说,迁移到 java.time 包可能须要一定的学习本钱。利用 java.time 包中的类处理韶光间隔和持续韶光在 Java 8 的 java.time 包中,处理韶光间隔和持续韶光有两个紧张的类:Duration 和 Period。Duration 用于表示基于韶光的间隔(以秒和纳秒为单位),而 Period 用于表示基于日期的间隔(以年、月、日为单位)。
1. Duration 类
Duration 类用于表示两个韶光点之间的韶光间隔,精确到秒和纳秒。它常用于打算精确的韶光差,如秒、分钟、小时等。
示例代码
import java.time.Duration;import java.time.LocalDateTime;import java.time.temporal.ChronoUnit;public class DurationExample { public static void main(String[] args) { // 当前韶光 LocalDateTime startTime = LocalDateTime.now(); System.out.println("开始韶光: " + startTime); // 仿照一个耗时操作 try { Thread.sleep(3000); // 休眠3秒 } catch (InterruptedException e) { e.printStackTrace(); } // 结束韶光 LocalDateTime endTime = LocalDateTime.now(); System.out.println("结束韶光: " + endTime); // 打算韶光间隔 Duration duration = Duration.between(startTime, endTime); System.out.println("韶光间隔: " + duration.getSeconds() + " 秒 " + duration.getNano() + " 纳秒"); // 其他常用方法 System.out.println("韶光间隔(分钟): " + duration.toMinutes()); System.out.println("韶光间隔(毫秒): " + duration.toMillis()); // 利用ChronoUnit打算韶光间隔 long secondsBetween = ChronoUnit.SECONDS.between(startTime, endTime); System.out.println("韶光间隔(ChronoUnit): " + secondsBetween + " 秒"); }}
2. Period 类
Period 类用于表示两个日期之间的韶光间隔,精确到年、月、日。它常用于打算日期间的差异,如天数、月数、年数等。
示例代码
java
import java.time.LocalDate;import java.time.Period;import java.time.temporal.ChronoUnit;public class PeriodExample { public static void main(String[] args) { // 当前日期 LocalDate startDate = LocalDate.now(); System.out.println("开始日期: " + startDate); // 目标日期 LocalDate endDate = startDate.plusYears(1).plusMonths(2).plusDays(3); System.out.println("结束日期: " + endDate); // 打算日期间隔 Period period = Period.between(startDate, endDate); System.out.println("日期间隔: " + period.getYears() + " 年 " + period.getMonths() + " 月 " + period.getDays() + " 天"); // 其他常用方法 System.out.println("总月数: " + period.toTotalMonths()); // 利用ChronoUnit打算日期间隔 long daysBetween = ChronoUnit.DAYS.between(startDate, endDate); System.out.println("日期间隔(ChronoUnit): " + daysBetween + " 天"); }}
选择 Duration 和 Period 的适用场景
选择 Duration 的场景须要精确到秒和纳秒的韶光打算。计时操作,如丈量代码实行韶光、任务的持续韶光等。时区无关的韶光打算,如两个韶光点之间的间隔。选择 Period 的场景须要基于年、月、日的日期打算。打算两个日期之间的年、月、日差异。日历干系操作,如打算某个日期之后的某年某月某日。比较两个日期之间的差异示例代码
java
import java.time.LocalDate;import java.time.Period;public class PeriodDifferenceExample { public static void main(String[] args) { // 两个日期 LocalDate startDate = LocalDate.of(2020, 1, 1); LocalDate endDate = LocalDate.of(2021, 6, 15); // 打算日期间隔 Period period = Period.between(startDate, endDate); System.out.println("日期间隔: " + period.getYears() + " 年 " + period.getMonths() + " 月 " + period.getDays() + " 天"); }}
利用 Duration 进行时区无关的韶光打算
示例代码
java
import java.time.Duration;import java.time.ZonedDateTime;import java.time.ZoneId;public class ZonedDateTimeExample { public static void main(String[] args) { // 纽约韶光 ZonedDateTime startDateTime = ZonedDateTime.now(ZoneId.of("America/New_York")); System.out.println("纽约开始韶光: " + startDateTime); // 东京韶光 ZonedDateTime endDateTime = ZonedDateTime.now(ZoneId.of("Asia/Tokyo")); System.out.println("东京结束韶光: " + endDateTime); // 打算韶光间隔 Duration duration = Duration.between(startDateTime, endDateTime); System.out.println("韶光间隔: " + duration.toHours() + " 小时"); }}
利用 ChronoUnit 列举
ChronoUnit 列举供应了一系列用于韶光单位的常量,支持基于韶光和日期的打算。
示例代码
java
import java.time.LocalDate;import java.time.LocalDateTime;import java.time.temporal.ChronoUnit;public class ChronoUnitExample { public static void main(String[] args) { // 当前韶光 LocalDateTime now = LocalDateTime.now(); System.out.println("当前韶光: " + now); // 打算一周后的韶光 LocalDateTime oneWeekLater = now.plus(1, ChronoUnit.WEEKS); System.out.println("一周后的韶光: " + oneWeekLater); // 打算一年前的日期 LocalDate oneYearAgo = LocalDate.now().minus(1, ChronoUnit.YEARS); System.out.println("一年前的日期: " + oneYearAgo); // 打算两个韶光点之间的小时数 long hoursBetween = ChronoUnit.HOURS.between(now, oneWeekLater); System.out.println("韶光间隔(小时): " + hoursBetween + " 小时"); }}
总结
利用 Java 8 java.time 包中的 Duration 和 Period 类可以方便地处理韶光间隔和持续韶光。Duration 用于表示基于韶光的间隔,如秒、分钟、小时等;而 Period 用于表示基于日期的间隔,如年、月、日等。此外,ChronoUnit 列举供应了一系列用于韶光单位的常量,支持基于韶光和日期的打算。