旧版日期时间 API 存在的问题
1. 设计很差: 在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期。此外用于格式化和解析的类在java.text包中定义。
2. 非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
3. 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
public static void main(String[] args) { // 旧版日期时间 API 存在的问题 // 1.设计部合理 Date now = new Date(1985, 9, 23); System.out.println(now); // 2.时间格式化和解析是线程不安全的 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); for (int i = 0; i < 50; i++) { new Thread(() -> { try { Date date = sdf.parse("2019-09-09"); System.out.println("date = " + date); } catch (ParseException e) { e.printStackTrace(); } }).start(); } }
/** * 线程安全的 DateTimeFormatter */ @Test public void testDateFormatter() throws InterruptedException, ExecutionException { DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd"); // 定义线程池, 任务, 提交100个解析任务并输出任务的执行情况 ExecutorService pool = Executors.newFixedThreadPool(10); Callable<LocalDate> task = () -> LocalDate.parse("20190305",dtf); List<Future<LocalDate>> results = new ArrayList<>(); IntStream.rangeClosed(0, 100).forEach(i -> results.add(pool.submit(task))); for (Future<LocalDate> future : results) { println(future.get()); } }
新日期时间 API介绍
JDK 8中增加了一套全新的日期时间API,这套API设计合理,是线程安全的。新的日期及时间API位于 java.time 包
中,下面是一些关键类。
- LocalDate :表示日期,包含年月日,格式为 2019-10-16
- LocalTime :表示时间,包含时分秒,格式为 16:38:54.158549300
- LocalDateTime :表示日期时间,包含年月日,时分秒,格式为 2018-09-06T15:33:56.750
- DateTimeFormatter :日期时间格式化类。
- Instant:时间戳,表示一个特定的时间瞬间。
- Duration:用于计算2个时间(LocalTime,时分秒)的距离
- Period:用于计算2个日期(LocalDate,年月日)的距离
- ZonedDateTime :包含时区的时间
Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。此外Java 8还提供了4套其他历法,分别是:
- ThaiBuddhistDate :泰国佛教历
- MinguoDate :中华民国历
- JapaneseDate :日本历
- HijrahDate :伊斯兰历
JDK 8 的日期和时间类
LocalDate 、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用 ISO -8601 日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
@Test public void testLocalDate() { // LocalDate: 表示日期,有年月日 LocalDate date = LocalDate.of(2020, 4, 1); System.out.println("date = " + date); LocalDate now = LocalDate.now(); System.out.println("now = " + now); System.out.println(now.getYear()); System.out.println(now.getMonthValue()); System.out.println(now.getDayOfMonth()); }
@Test public void testLocalTime() { // LocalTime: 表示时间,有时分秒 LocalTime time = LocalTime.of(22, 00, 39); System.out.println("time = " + time); LocalTime now = LocalTime.now(); System.out.println("now = " + now); System.out.println(now.getHour()); System.out.println(now.getMinute()); System.out.println(now.getSecond()); System.out.println(now.getNano()); }
@Test public void testLocalDateTime() { // LocalDateTime: LocalDate + LocalTime 有年月日 时分秒 LocalDateTime dateTime = LocalDateTime.of(2020, 4, 1, 13, 28, 59); System.out.println("dateTime = " + dateTime); LocalDateTime now = LocalDateTime.now(); System.out.println("now = " + now); System.out.println(now.getYear()); System.out.println(now.getMonthValue()); System.out.println(now.getHour()); System.out.println(now.getSecond()); }
对日期时间的修改,对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。
withAttribute方法会创建对象的一个副本,并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对象,他们不会影响原来的对象。
// LocalDateTime 类: 对日期时间的修改 @Test public void test05() { LocalDateTime now = LocalDateTime.now(); System.out.println("now = " + now); // 修改日期时间 LocalDateTime setYear = now.withYear(2078); System.out.println("修改年份: " + setYear); System.out.println("now == setYear: " + (now == setYear)); System.out.println("修改月份: " + now.withMonth(6)); System.out.println("修改小时: " + now.withHour(9)); System.out.println("修改分钟: " + now.withMinute(11)); System.out.println( now.withYear(2021).withMonth(1).withHour(11).withMinute(11)); // 再当前对象的基础上加上或减去指定的时间 LocalDateTime localDateTime = now.plusDays(5); System.out.println("5天后: " + localDateTime); System.out.println("now == localDateTime: " + (now == localDateTime)); System.out.println("10年后: " + now.plusYears(10)); System.out.println("20月后: " + now.plusMonths(20)); System.out.println("20年前: " + now.minusYears(20)); System.out.println("5月前: " + now.minusMonths(5)); System.out.println("100天前: " + now.minusDays(100)); }
now = 2020-03-31T22:10:02.710 修改年份: 2078-03-31T22:10:02.710 now == setYear: false 修改月份: 2020-06-30T22:10:02.710 修改小时: 2020-03-31T09:10:02.710 修改分钟: 2020-03-31T22:11:02.710 2021-01-31T11:11:02.710 5天后: 2020-04-05T22:10:02.710 now == localDateTime: false 10年后: 2030-03-31T22:10:02.710 20月后: 2021-11-30T22:10:02.710 20年前: 2000-03-31T22:10:02.710 5月前: 2019-10-31T22:10:02.710 100天前: 2019-12-22T22:10:02.710 Process finished with exit code 0
日期时间的比较
// 日期时间的比较 @Test public void test06() { // 在JDK8中,LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期,可直接进行比较。 LocalDate now = LocalDate.now(); LocalDate date = LocalDate.of(2020, 4, 1); System.out.println(now.isBefore(date)); //true System.out.println(now.isAfter(date)); // false }
JDK 8 的时间格式化与解析
通过 java.time.format.DateTimeFormatter 类可以进行日期时间解析与格式化。
@Test public void test04(){ LocalDateTime now = LocalDateTime.now(); DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String format = now.format(dtf); System.out.println(format); //2020-03-31 22:15:50 // 将字符串解析为日期时间 LocalDateTime parse = LocalDateTime.parse("1985-09-23 10:12:22", dtf); System.out.println("parse = " + parse); }
JDK 8 的 Instant 类
Instant 时间戳/时间线,内部保存了从1970年1月1日 00:00:00以来的秒和纳秒。
@Test public void test07() { Instant now = Instant.now(); System.out.println("当前时间戳 = " + now); // 获取从1970年1月1日 00:00:00的秒 System.out.println(now.getNano()); System.out.println(now.getEpochSecond()); System.out.println(now.toEpochMilli()); System.out.println(System.currentTimeMillis()); Instant instant = Instant.ofEpochSecond(5); System.out.println(instant); }
JDK 8 的计算日期时间差类
Duration/Period类: 计算日期时间差。
1. Duration:用于计算2个时间(LocalTime,时分秒)的距离
2. Period:用于计算2个日期(LocalDate,年月日)的距离
// Duration/Period 类: 计算日期时间差 @Test public void test08() { // Duration计算时间的距离 LocalTime now = LocalTime.now(); LocalTime time = LocalTime.of(14, 15, 20); Duration duration = Duration.between(time, now); System.out.println("相差的天数:" + duration.toDays()); System.out.println("相差的小时数:" + duration.toHours()); System.out.println("相差的分钟数:" + duration.toMinutes()); // Period计算日期的距离 LocalDate nowDate = LocalDate.now(); LocalDate date = LocalDate.of(1998, 8, 8); // 让后面的时间减去前面的时间 Period period = Period.between(date, nowDate); System.out.println("相差的年:" + period.getYears()); System.out.println("相差的月:" + period.getMonths()); System.out.println("相差的天:" + period.getDays()); }
相差的天数:0 相差的小时数:8 相差的分钟数:492 相差的年:21 相差的月:7 相差的天:23
JDK 8 的时间校正器
有时我们可能需要获取例如:将日期调整到“下一个月的第一天”等操作。可以通过时间校正器来进行。
TemporalAdjuster : 时间校正器。
TemporalAdjusters : 该类通过静态方法提供了大量的常用TemporalAdjuster的实现。
/** * TemporalAdjuster */ @Test public void testTemporalAdjuster() { LocalDate ld = LocalDate.now(); println(ld.with(firstDayOfMonth())); // 2020-03-01 月初 println(ld.with(firstDayOfNextYear())); // 2021-01-01 年初 println(ld.with(lastDayOfMonth())); // 2020-03-31 月末 println(ld.with(lastDayOfYear())); // 2020-12-31 年末 println(ld.with(next(SUNDAY))); // 2020-04-05 下周日 println(ld.with(previousOrSame(MONDAY))); // 2020-03-30 前一个或相同的周一 } private void println(Object obj) { System.out.println(obj); }
JDK 8 设置日期时间的时区
Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别
为:ZonedDate、ZonedTime、ZonedDateTime。其中每个时区都对应着 ID,ID的格式为 “区域/城市” 。例如 :Asia/Shanghai 等。
ZoneId:该类中包含了所有的时区信息。
// 设置日期时间的时区 @Test public void test10() { // 1.获取所有的时区ID // ZoneId.getAvailableZoneIds().forEach(System.out::println); // 不带时间,获取计算机的当前时间 LocalDateTime now = LocalDateTime.now(); // 中国使用的东八区的时区.比标准时间早8个小时 System.out.println("now = " + now); //now = 2020-03-31T22:43:42.289 // 2.操作带时区的类 // now(Clock.systemUTC()): 创建世界标准时间 ZonedDateTime bz = ZonedDateTime.now(Clock.systemUTC()); System.out.println("bz = " + bz); //bz = 2020-03-31T14:43:42.290Z // now(): 使用计算机的默认的时区,创建日期时间 ZonedDateTime now1 = ZonedDateTime.now(); System.out.println("now1 = " + now1); // now1 = 2020-03-31T22:43:42.290+08:00[Asia/Shanghai] // 使用指定的时区创建日期时间 ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Vancouver")); System.out.println("now2 = " + now2); // now2 = 2020-03-31T07:43:42.293-07:00[America/Vancouver] // 修改时区 // withZoneSameInstant: 即更改时区,也更改时间 ZonedDateTime withZoneSameInstant = now2.withZoneSameInstant(ZoneId.of("Asia/Shanghai")); System.out.println("withZoneSameInstant = " + withZoneSameInstant); //withZoneSameInstant = 2020-03-31T22:43:42.293+08:00[Asia/Shanghai] // withZoneSameLocal: 只更改时区,不更改时间 ZonedDateTime withZoneSameLocal = now2.withZoneSameLocal(ZoneId.of("Asia/Shanghai")); System.out.println("withZoneSameLocal = " + withZoneSameLocal); // withZoneSameLocal = 2020-03-31T07:43:42.293+08:00[Asia/Shanghai] }