JDK8新特性10——日期和时间API

1. 旧版本日期和时间API存在的问题

1)设计差:在java.util和java.sql的包中都包含了日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期。此外,用于格式化和解析的类在java.text包中定义

2)非线程安全:java.utl.Date是非线程安全的,所有的日期类都是可变的,这是java日期类最大的问题之一

3)时区处理麻烦:日期类并不提供国际化,没有时区支持,因此,java引入了java.util.Calendar和java.util.TimeZone类,但是,他们同样存在上述所有的问题。

先来看一下在之前我们处理DateFormate做线程安全一般是怎么做的

package com.bjc.time.traditional;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TraditionalTimeUtils {
	private static final ThreadLocal<DateFormat> local = new ThreadLocal<DateFormat>() {
		@Override
		protected DateFormat initialValue() {
			return new SimpleDateFormat("yyyy-MM-dd");
		}
	};
	
	public static Date convert(String date) throws ParseException {
		return local.get().parse(date);
	}
}

调用:

package com.bjc.time.traditional;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TraditionalTime {

	public static void main(String[] args) throws Exception {
		List<Future<Date>> dates = new ArrayList<>();
		ExecutorService pool = Executors.newFixedThreadPool(10);
		for(int i = 0 ; i < 10 ; i++) {
			Future<Date> submit = pool.submit(() -> {
				try {
					return TraditionalTimeUtils.convert("2020-05-14");
				} catch (ParseException e) {
					e.printStackTrace();
				}
				return null;
			});
			dates.add(submit);
		}
		dates.forEach(fu -> {
			try {
				System.out.println(fu.get());
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace();
			}
		});
		pool.shutdownNow();
	}

}

 

2. 新日期时间API概览

        JDK8中新增了一套全新的日期时间API,这套API设计合理,是线程安全的。新的日期及时间API位于java.time包中,下面是一些关键类:

1)localDate:表示日期,包含年月日,格式为 2019-10-16

2)LocalTime:表示时间,包含时分秒,格式为 16:58:54.158549300

3)LocalDateTime:表示日期时间,包含年月日,时分秒,格式为:2019-10-16T16:58:54.158549300

4)DateTimeFormatter:日期格式化类

5)Instant:时间戳,表示一个特定的时间瞬间

6)Duration:用于计算2个时间(LocalTime,时分秒)的距离

7)Period:用于计算2个日期(LocalDate,年月日)的距离。

8)ZoneDateTime:包含时区的时间。

在java中,使用的历法是世界民用历法ISO 8601日历系统,也就是我们所说的公立。平均一年365天,闰年366天。此外java8还提供了4套其他历法,分别是:

1)ThaiBuddhistDate:泰国佛教历

2)MinguoDate:中华民国历

3)JapaneseDate:日本历

4)HijrahDate:伊斯兰历

3. JDK8日期与时间

3.1 日期与时间类

        LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。

3.1.1 LocalDate类

表示日期,有年月日

1)LocalDate.now():获取当前日期

2)LocalDate.of(int year,int month,int day):获取指定日期时间

3)获取单独的年月日:使用LocalDate对象分别调用getYear()、getMonth()、getDayOfMonth()

注意:这里获取到的月份是英文表示,这是因为getMonth()得到的是一个Month,它是一个枚举,要想得到数字月份,可以使用getMonthValue

例如:

public void testLocalDate(){
	LocalDate now = LocalDate.now();
	System.out.println(now);        // 2020-02-24

	LocalDate ofDate = LocalDate.of(2020, 2, 2);
	System.out.println(ofDate); // 2020-02-02

	int year = now.getYear();
	Month month = now.getMonth();
	int dayOfMonth = now.getDayOfMonth();
	int dayOfYear = now.getDayOfYear();
	DayOfWeek dayOfWeek = now.getDayOfWeek();
	// 年:2020 月:FEBRUARY 日:24 dayOfYear:55 dayOfWeek:MONDAY
	System.out.println("年:" + year + " 月:" + month + " 日:" + dayOfMonth + " dayOfYear:" + dayOfYear + " dayOfWeek:" + dayOfWeek);

	// 获取数字月
	int monthValue = now.getMonthValue();
	System.out.println(monthValue);  // 2
}

3.1.2 LocalTime类

表示时间,有时分秒

1)LocalTime.now():获取当前时间的时分秒

2)LocalTime.of(int hour, int minute, int sec):获取指定的时分秒

3)获取单独的时分秒:使用LocalTime对象分别调用getHour、getMinute、getSecond

public void testLocalTime(){
	LocalTime now = LocalTime.now();
	System.out.println(now);    // 09:14:30.840

	LocalTime time = LocalTime.of(13, 55, 24);
	System.out.println(time);    // 13:55:24

	// 获取具体的时分秒
	int hour = now.getHour();
	int minute = now.getMinute();
	int second = now.getSecond();
	int nano = now.getNano();
}

3.1.3 LocalDateTime类

使用方式都差不多,例如;

public void testLocalDateTime(){
	// 获取当前时间的年月日时分秒
	LocalDateTime now = LocalDateTime.now();  // 2020-02-24T09:21:28.300
	LocalDateTime of = LocalDateTime.of(2020, 5, 25, 10, 55, 23); // 2020-05-25T10:55:23
	int year = now.getYear();
	int month = now.getMonthValue();
	int dayOfMonth = now.getDayOfMonth();
}

3.1.4 LocalDate系列类的设置时间

        三个类设置时间的方式都是一样的,但是与我们之前的习惯所不同的是,其修改事件不是用set方法,而是用的with方法。

        对日期时间的修改,对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。该方法会创建对象的一个副本,并按照其属性去修改。

1)withAttribute设置时间

        值得注意的是,使用withAttribute比如:withYear,是重新创建一个新的LocalDate对象,不会修改原来的LocalDate对象。

// 获取当前时间
LocalDateTime now = LocalDateTime.now();

// 1. 修改时间
LocalDateTime localDateTime = now.withDayOfMonth(3); // 2020-02-03T09:28:06.631
LocalDateTime localDateTime1 = now.withYear(2025);   // 2025-02-24T09:29:02.857

2)plusAttribute与minusAttribute加减时间

与withAttribute一样,返回的也是一个全新的对象.

使用plusAttribute加减时间,新增就是正数,减少就是负数

使用minusAttribute加减时间,正好相反,减少就是正数,新增就是负数

例如:

// 2. 增加或减去时间
LocalDateTime localDateTime2 = now.plusYears(3);  // 2023-02-24T09:36:13.917
LocalDateTime localDateTime3 = now.plusYears(-3);  // 2017-02-24T10:52:49.922

LocalDateTime localDateTime4 = now.minusYears(3);   // 2017-02-24T10:53:53.852
LocalDateTime localDateTime5 = now.minusYears(-3);   // 2023-02-24T10:54:24.212

3)比较时间

在LocalDate中有三个方法用于比较两个时间,一个是isAfter一个是isBefore另一个是isEquals

例如;

public void testEqual(){
	// 获取当前时间
	LocalDateTime now = LocalDateTime.now();
	LocalDateTime of = LocalDateTime.of(2020, 5, 25, 10, 55, 23);
	System.out.println(now.isAfter(of));    // false
	System.out.println(now.isBefore(of));   // true
    System.out.println(now.isEqual(of));   // false
}

3.2 时间格式化与解析

通过java.time.format.DateTimeFormatter类可以进行日期时间解析与格式化

3.2.1 使用format格式化时间

DateTimeFormatter中提供了很多默认的日期时间格式供我们使用,例如:

DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME;
String format = dtf.format(now);
System.out.println(format); // 2020-02-24T11:12:01.398

如果我们想使用我们自定义的时间格式了,我们也可以使用ofPattern函数自己指定,例如;

DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String format1 = timeFormatter.format(now);
System.out.println(format1); // 2020-02-24

3.2.2 使用parse解析

LocalDateTime parse = LocalDateTime.parse("2020-02-24T11:26:00.515", DateTimeFormatter.ISO_DATE_TIME);
System.out.println(parse); // 2020-02-24

注意:这里需要注意一点LocalDateTime.parse解析的时间类型字符串需要是这样子的2020-02-24T11:26:00.515

3.3 Instant类

Instant时间戳/时间线,内部保存了从1970年1月1日 00:00:00以来的秒和纳秒,一般不是给用户使用的,而是方便我们程序做一些统计使用的,比方说,计算方法的耗时等。

public void testInstant(){
   Instant now = Instant.now();
   System.out.println(now); // 2020-02-24T04:48:24.130Z
}

其他常用API

now.plusSeconds(30);// 增加30秒
now.minusSeconds(30);//减少30秒

now.getEpochSecond();   // 得到秒
now.getNano();          // 得到纳秒

3.4 计算日期时间差的类

3.4.1 Duration类

用于计算两个时间(LocalTime,时分秒)的距离

public void testDuration(){
	// 时间
	Duration duration = Duration.between(LocalTime.now(),LocalTime.of(10,25,30));
	System.out.println("相差的天数:" + duration.toDays());       // 0
	System.out.println("相差的小时数:" + duration.toHours());     // -2
	System.out.println("相差的毫秒数:" + duration.toMillis());    // -9281042
	System.out.println("相差的分钟:" + duration.toMinutes());    // -154
}

3.4.2 Period类

用于计算2个日期(LocalDate,年月日)的距离

public void testPeriod(){
	// 日期
	Period period = Period.between(LocalDate.now(), LocalDate.of(2025, 12, 20));
	System.out.println("相差的年份数:" + period.getYears());      // 5
	System.out.println("相差的月份数:" + period.getMonths());     // 9
	System.out.println("相差的天数:" + period.getDays());        // 26
}

注意:使用between的时候,注意参数的位置,其实现是用后面的时间-前面的时间。

3.5 时间校正器

有时候我们需要获取例如:将日期调整到“下一个月的第一天”等,这时候就可以使用事件校正器来进行

3.5.1 TemporalAdjuster

时间校正器,是一个函数式接口,只有一个抽象方法

Temporal adjustInto(Temporal temporal);

注意:参数temporal就是要调整的时间 

例如:将日期调整到“下一个月的第一天”

public void test(){
	LocalDateTime dateTime = LocalDateTime.now();
	System.out.println(dateTime);           // 2020-02-24T13:24:59.909
	// 定义时间校正器:调整到下个月的1号
	TemporalAdjuster ts = (temporal) -> {
	    // 将需要调整的时间temporal转成localDateTime对象
	    LocalDateTime localDateTime = (LocalDateTime) temporal;
	    // 调整时间
	    return localDateTime.plusMonths(1).withDayOfMonth(1);
	};

	LocalDateTime newTime = dateTime.with(ts);
	System.out.println(newTime);  // 2020-03-01T13:24:59.909

}

注意:通过上例可以知道,时间校正器通常是作为LocalDate的with方法的参数使用的。 

3.5.2 TemporalAdjusters

该类通过静态方法,提供了大量的常用TemporalAdjuster的实现

例如:调整到下个月第一天

LocalDateTime dateTime = LocalDateTime.now();
TemporalAdjuster temporalAdjuster = TemporalAdjusters.firstDayOfNextMonth(); 
LocalDateTime localDateTime = dateTime.with(temporalAdjuster);
System.out.println(localDateTime);  // 2020-03-01T13:30:47.863

3.6 设置日期时间的时区

java8中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别为:ZonedDate、ZonedTime、ZonedDateTime。

其中每个时区都对应着ID,ID的格式为“区域/城市”。例如:Asia/Shanghai等

3.6.1 ZoneId类

ZoneId类中包含了所有的时区信息。

获取所有的时区ID

Set<String> zoneIds = ZoneId.getAvailableZoneIds();

3.6.2 ZonedDateTime

ZonedDateTime类也有now方法,其有三个重载形式

1)创建世界标准时间

ZonedDateTime zonedDateTime = ZonedDateTime.now(Clock.systemUTC());

2)创建本地时区的时间

ZonedDateTime now = ZonedDateTime.now(); // 2020-02-24T13:48:01.799+08:00[Asia/Shanghai]

本地时区的时间也就是我们之前使用的LocalDateTime.now()得到的时间。但是,通过时区类得到的本地时区的时间,很明显的与通过LocalDateTime.now()得到的时间有区别,我们通过时区类得到的时间带有时区信息

3)创建指定的时区创建日期时间

ZonedDateTime zonedDateTime1 = ZonedDateTime.now(ZoneId.of("America/Cuiaba"));
// 输出结果:2020-02-24T01:51:02.236-04:00[America/Cuiaba]

3.6.3 时区的其他操作API

1)withZoneSameInstant

修改时区

 ZonedDateTime zonedDateTime1 = ZonedDateTime.now(ZoneId.of("America/Cuiaba"));
System.out.println(zonedDateTime1);    // 2020-02-24T01:54:25.444-04:00[America/Cuiaba]
ZonedDateTime zonedDateTime2 = zonedDateTime1.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
System.out.println(zonedDateTime2);    // 2020-02-24T13:54:25.444+08:00[Asia/Shanghai]

2)withZoneSameLocal

该方法也是用于修改时间的,但是与withZoneSameInstant不同的是,withZoneSameLocal只修改时区,不更改时间。

总结:Java8对于时间API的优势

1)新版时间和日期API中,日期和时间的变化是不可变的,操纵的日期不会影响老值,而是生成一个新的实例

2)新的API提供了两种不同的时间表示方式,有效的区分了人和机器的不同需求

3)TemporalAdjuster可以更精确的操纵日期,还可以自定义日期调整期

4)是线程安全的。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值