【Java小攻略】时间API全解析

时间API全解析

一、艰苦岁月

(一)简述

在Java 1.0中,对日期和时间的支持只依赖java.util.Date类。这个类无法表示日期,只能以毫秒的精度表示时间。由于Java初期设计上的缺陷,此类的易用性非常糟糕。

表现如下:

  • 创建具体日期的方式以及toString的输出结果让初始者感到怪异
        Date date = new Date(121,1,22);
        System.out.println(date);

在这里插入图片描述

年份的起始选择是1900年,月份的起始从0开始。
所以这里的结果是 2021年 CST(北京时间) 2月22日 周一 凌晨

  • 因为设计原因大量被淘汰的API
  • 与java.sql.Date 混用时,容易出现问题。

在Java 1.1中,出现了java.util.Calendar类缓解了此问题,但是遗憾的是Calendar类也有类似的问题和设计缺陷,导致使用这些方法写出的代码非常容易出错:

  • 月份依旧是从0开始计算
  • Date和Calendar的选用让人困惑,有的特性只在某一个类有提供,比如用于以语言无关方式格式化和解析日期或时间的DateFormat方法就只在Date类里有

而且DateFormat方法在设计上也是存在问题的。最知名的就是该方法非线程安全的。意味着两个线程如果尝试使用同一个formatter解析日期,可能得到无法预期的结果。

这些缺陷和不一致导致用户们转投第三方的日期和时间库,比如Joda-Time

不过在老旧系统中,这些时间API的运用是很常见,所以还是要会运用

(二)时间与计算机

时区与时差:

时区是地球上的区域使用同一个时间定义。人们通过观察太阳的位置(时角)决定时间,这就使得不同经度的地方的时间有所不同(地方时)。

世界各个国家位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差。这些偏差就是所谓的时差。

有关国际会议决定将地球表面按经线从东到西,划成一个个区域,并且规定相邻区域的时间相差1小时。

现今全球共分为24个时区。由于一个国家领土跨着多个时区、所以时区并不严格按南北直线来划分。比如中国幅员宽广,差不多跨5个时区,但为了使用方便简单,实际上在只用东八时区的标准时即北京时间(CST可视为美国、澳大利亚、古巴或中国的标准时间)为准。

除了CST,常见的时区缩写有:

  • CET 欧洲中部时间
  • UTC 世界标准时间或世界协调时间
  • GMT格林尼治标准时间

格林尼治平时(GMT)是指位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时,因为本初子午线被定义为通过那里的经线。自1924年2月5日开始,格林尼治天文台负责每隔一小时向全世界发放调时信息。格林尼治平时的正午是指当平太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治平时基于天文观测本身的缺陷,已经被原子钟报时的协调世界时(UTC)所取代。一般使用GMT+8表示中国的时间,是因为中国位于东八区,时间上比格林威治时间快8个小时。

关系:

  • CET=UTC/GMT + 1小时
  • CST=UTC/GMT + 8小时
  • CST=CET+9

时间戳: 通常是一个字符序列,能唯一地标识某一刻的时间。

时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。通俗的讲, 时间戳是一份能够表示一份数据在一个特定时间点已经存在的完整的可验证的数据。

当有些计算机存储或者传输 Timestamp 出错时,这个 Timestamp 就会取默认值。而在计算机中,默认值通常是 0。

所以在有些系统出现问题的时候,时间会显示成:Thu Jan 01 08:00:00 CST 1970 或者 1970-01-01 08:00:00

(三)常见的API操作

1、Calendar介绍

Calendar类是抽象类,且Calendar类的构造方法是protected的,无法使用Calendar类的构造方法来创建对象,但是API中提供了getInstance方法用来创建对象。

Calendar c = Calendar.getInstance();

这里获得的Calendar对象就代表当前的系统时间,由于Calendar类toString实现的没有Date类那么直观,所以直接输出Calendar类的对象意义不大

Calendar设置指定的时间,还是比较方便:

        Calendar c1 = Calendar.getInstance();
        c1.set(2021,10,11,1,1); // 年月日时分

Calendar 获取时间细节

        int year=calendar.get(Calendar.YEAR);
        //month=0代表1月,最大为11月
        int month=calendar.get(Calendar.MONTH);
        int day1=calendar.get(Calendar.DATE);
        int hour=calendar.get(Calendar.HOUR);
        int min=calendar.get(Calendar.MINUTE);
        int sec=calendar.get(Calendar.SECOND);

Calendar变更时间

        // 【增加时间】
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        // 增加一天
         calendar.add(Calendar.DATE,1);
        // 增加一周
         calendar.add(Calendar.WEEK_OF_YEAR, 1);
        // 增加一个月
         calendar.add(Calendar.MONTH, 1);
        // 增加一年
         calendar.add(Calendar.YEAR, 1);
         date = calendar.getTime();
         
        // 【设置时间】
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.YEAR, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        System.out.println(dateToString(calendar.getTime()));

Calendar.DAY_OF_YEAR、Calendar.DAY_OF_MONTH、Calendar.DATE 主要是与Calendar的get方法结合使用的。分别返回:

  • DAY_OF_YEAR : 该日期是这一年的第多少天。
  • DAY_OF_MONTH:该日期是这个月第多少天。
  • DATE:日期,包含年月日。
        Calendar cal = Calendar.getInstance();
        int day = cal.get(Calendar.DATE);
        int month = cal.get(Calendar.MONTH) + 1;
        int year = cal.get(Calendar.YEAR);
        int dow = cal.get(Calendar.DAY_OF_WEEK);
        int dom = cal.get(Calendar.DAY_OF_MONTH);
        int doy = cal.get(Calendar.DAY_OF_YEAR);
        System.out.println("当期时间: " + cal.getTime());
        System.out.println("日期: " + day);
        System.out.println("月份: " + month);
        System.out.println("年份: " + year);
        System.out.println("一周的第几天: " + dow); // 星期日为一周的第一天输出为 1,星期一
        System.out.println("一月中的第几天: " + dom);
        System.out.println("一年的第几天: " + doy);

输出结果

当期时间: Thu Jan 21 20:26:27 CST 2021
日期: 21
月份: 1
年份: 2021
一周的第几天: 5
一月中的第几天: 21
一年的第几天: 21

2、TimeStamp介绍

java.sql.TimeStamp 很明显与数据库有关。在JDBC API中广泛被使用、还有数据库字段存储中。

java创建Timestamp的几种方式

Timestamp time1 = new Timestamp(System.currentTimeMillis());
Timestamp time2 = new Timestamp(new Date().getTime());
Timestamp time3 = new Timestamp(Calendar.getInstance().getTimeInMillis());
//不建议使用
Timestamp time4 = new Timestamp(2021-1900,11,11,11,11,11,0);

3、Date介绍

Date类是经常会使用到的一个用来处理日期、时间的一个类。这里主要说的是java.util.Date

【比较方法】
public boolean after(Date when)
测试此日期是否在指定日期之后。 
返回:
* 当且仅当此Date对象比when表示的时间晚,才返回 true* 否则返回 falsepublic boolean before(Date when)
测试此日期是否在指定日期之前。 
返回:
* 当且仅当此 Date对象表示的比when表示的时间早,才返回 true* 否则返回 falsepublic int compareTo(Date anotherDate)
比较两个日期的顺序。
返回:
* 如果参数 Date 等于此 Date,则返回值 0* 如果此 Date 在 Date 参数之前,则返回小于 0 的值;
* 如果此 Date 在 Date 参数之后,则返回大于 0 的值。

public boolean equals(Object obj)
比较两个日期的相等性。
返回:
* 当且仅当参数不为 null,并且是一个表示与此对象相同的时间点(到毫秒)的 Date 对象时,结果才为 true* 否则返回 false。

【时间戳】
public long getTime()
返回自 19701100:00:00 GMT 以来此 Date 对象表示的毫秒数。

public void setTime(long time)
设置此 Date 对象,以表示 19701100:00:00 GMT 以后 time 毫秒的时间点。

【时间输出】
public String toString()

形式:dow mon dd hh:mm:ss zzz yyyy
其中:
* dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat)* mon 是月份 (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec)* dd 是一月中的某一天(0131),显示为两位十进制数。 
* hh 是一天中的小时(0023),显示为两位十进制数。 
* mm 是小时中的分钟(0059),显示为两位十进制数。 
* ss 是分钟中的秒数(0061),显示为两位十进制数。 
* zzz 是时区(并可以反映夏令时)。标准时区缩写包括方法 parse 识别的时区缩写。如果不提供时区信息,则 zzz 为空,即根本不包括任何字符。 
* yyyy 是年份,显示为 4 位十进制数。

yyyy-MM-dd HH:mm:ss 其中在小时部分有hh和HH的区别,hh为12小时格式,HH为24小时格式。MM代表的是月份只能用在月份上,mm代表的是分钟只能用在分钟上这两个必须固定。

可以使用 SimpleDateFormat 格式化日期,SimpleDateFormat 允许你选择任何用户自定义日期时间格式来运行。数值描述参考上面toString方法的形式

        Date dNow = new Date();
        SimpleDateFormat ft = new SimpleDateFormat ("yyyy---MM----dd hh||mm||ss");
        System.out.println("当前时间为: " + ft.format(dNow)); 
        // result: 当前时间为: 2021---01----21 07||35||27

不过需要注意的是 SimpleDateFormat不是线程安全的,不能使用SimpleDateFormat作为多线程下的共享变量。

复现错误信息:

        ExecutorService executorService = Executors.newCachedThreadPool();
        List<String> dateStrList = new ArrayList<>();
        dateStrList.add("2021-06-10 10:01:01");
        dateStrList.add("2021-09-04 11:01:01");
        dateStrList.add("2021-11-11 12:01:01");
        dateStrList.add("2021-12-21 10:01:01");

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

        for (String dateStr : dateStrList) {
            executorService.execute(() -> {
                try {
                    simpleDateFormat.parse(dateStr);
                    TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }

在这里插入图片描述
很明显看到非线程安全。

主要原因是 SimpleDateFormat下的parse()方法里会通过CalendarBuilder调用establish方法,establish方法会先对Calendar执行clear()方法
在这里插入图片描述
在这里插入图片描述

解决方法有:

  • 各线程间不共享SimpleDateFormat,在使用到的时候再创建(可能会大量的创建销毁对象)
  • 使用ThreadLocal
  • 对SimpleDateFormat进行同步处理;(重量级锁)
  • 使用线程安全的第三方类库,例如:Joda-Time类库等
  • 改用Java 8新设计的时间API处理(LocalDateTime,DateTimeFormatter)

获取当前时间

        Date date = new Date();
        System.out.println(date);  // Thu Jan 21 18:53:21 CST 2021

        Calendar calendar=Calendar.getInstance();
        Date date2 = calendar.getTime(); 
        System.out.println(date2); // Thu Jan 21 18:53:21 CST 2021

Date和Calendar互转

		// Date -- > Calendar
        Date date = new Date("2021-09-02 00:00:00.000");
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
		
		// Calendar -- > Date
        Calendar cal = Calendar.getInstance();
		Date date = cal.getTime();

Date和Timestamp互转

	 	// Date -- > Timestamp
		Timestamp timestamp= new Timestamp(new Date().getTime());
		// Timestamp -- > Date
		Timestamp t = new Timestamp(System.currentTimeMillis());
    	Date date = new Date(t.getTime());

Date和String之间相互转换

        // Date -- > String
        Date data = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dataStr = sdf.format(data);
        System.out.println(dataStr);
        
        // String -- > Data
        System.out.println(sdf.parse(dataStr));

SimpleDataFormat也可以格式化不同时区的时间

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
        System.out.println(sdf.format(Calendar.getInstance().getTime()));
		// 2021-01-21 04:15:17
		
        TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
        Calendar cal = Calendar.getInstance();
        System.out.println(cal.getTime());
        // Thu Jan 21 04:15:17 PST 2021

关于时区问题:

查看Date.toString的源码,发现在输出的过程中该方法只会去获取系统的默认时区,只有修改了默认时区才会显示该时区的时间。
在这里插入图片描述
在这里插入图片描述

通过阅读Calendar的源码,可以发现,getInstance方法虽然有一个参数可以传入时区,但是并没有将默认时区设置成传入的时区。而在Calendar.getInstance.getTime后得到的时间只是一个时间戳,其中未保留任何和时区有关的信息,因此,在输出时,还是显示的是当前系统默认时区的时间。

java8之前计算两个时间相差(天、小时、分钟、秒),简单示例:

    public static void dateDiff(String startTime, String endTime,  String format, String str) {
        // 按照传入的格式生成一个simpledateformate对象
        SimpleDateFormat sd = new SimpleDateFormat(format);
        long nd = 1000 * 24 * 60 * 60;// 一天的毫秒数
        long nh = 1000 * 60 * 60;// 一小时的毫秒数
        long nm = 1000 * 60;// 一分钟的毫秒数
        long ns = 1000;// 一秒钟的毫秒数
        long diff;
        long day = 0;
        long hour = 0;
        long min = 0;
        long sec = 0;
        // 获得两个时间的毫秒时间差异
        try {
            diff = sd.parse(endTime).getTime() - sd.parse(startTime).getTime();
            day = diff / nd;// 计算差多少天
            hour = diff % nd / nh + day * 24;// 计算差多少小时
            min = diff % nd % nh / nm + day * 24 * 60;// 计算差多少分钟
            sec = diff % nd % nh % nm / ns;// 计算差多少秒
            // 输出结果
            System.out.println("时间相差:" + day + "天" + (hour - day * 24) + "小时"
                    + (min - day * 24 * 60) + "分钟" + sec + "秒。");
            System.out.println("hour=" + hour + ",min=" + min);

        } catch (ParseException e) {
            e.printStackTrace();
        }
    }

	public static String getDays(Date date){
	        Calendar cal=Calendar.getInstance();
	        cal.setTime(date);
	        long oldTime=cal.getTimeInMillis();
	        long nowTime=System.currentTimeMillis();
	        long days=(nowTime-oldTime)/(1000*60*60*24);//天数
	        long hours=((nowTime-oldTime)%(1000*60*60*24))/(1000*60*60);//小时数
	        long minutes=(((nowTime-oldTime)%(1000*60*60*24))%(1000*60*60))/(1000*60);//分钟数
	        long seconds=((((nowTime-oldTime)%(1000*60*60*24))%(1000*60*60))%(1000*60))/1000;//秒数
	        return days+"天"+hours+"小时"+minutes+"分钟"+seconds+"秒";
	    }

在实际开发的时候我们还会用到java.sql,Date

java.util.Date类型写到数据库后存储的值可以到秒,java.sql.Date 类型的写入后只能到日期(年月日)。

关于相互转换,直接通过构造函数即可:

        java.util.Date utilDate = new java.util.Date();
        System.out.println(utilDate); // Thu Jan 21 20:18:23 CST 2021
        new java.sql.Date(utilDate.getTime()); 

        java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis());
        System.out.println(sqlDate); // 2021-01-21
        new java.util.Date(sqlDate.getTime());

二、新气象

(一)简述

为了解决上面的这些问题,Oracle决定在原生的Java API中提供高质量的日期和时间支持。这些变化可以在Java 8的java.time看到。

Java 8 的日期 - 时间 API 使用 ISO-8601 中定义的日历系统作为默认日历。 该日历基于公历系统,并在全球范围内用作表示日期和时间的事实标准。

java.time.chrono 软件包允许使用其中一个预定义日历系统。 或者自定义。

新版时间API在设计上遵循了四个规范:

  • 明确 / Clear
    API 中的方法定义明确,行为清晰明了。

  • 不可变 / Immutable
    带来了线程安全。

  • 流式 / Fluent
    新版API使用了流式接口,实现了链式调用。

  • 扩展 / Extensible
    可以自定义时间调节器和查询以及日历系统。

java.time 表示日期和时间的 API 的核心。它包括日期,时间,日期和时间相结合的类别,时区,瞬间,持续时间 和 时钟。基于 ISO-8601 中定义的日历系统, 并且不可变。

  • Instant:时间戳
  • LocalDate:只包含日期,比如:2016-10-20
  • LocalTime:只包含时间,比如:23:12:10
  • LocalDateTime:包含日期和时间,比如:2016-10-20 23:14:21(LocalDate和LocalTime的合体)
  • Duration:持续时间,时间差
  • Period:时间段
  • ZoneOffset:时区偏移量,比如:+8:00
  • ZonedDateTime:带时区的时间
  • Clock:时钟,比如获取目前美国纽约的时间

可以看到时间的表示相比Date更加形象。

四个子包:

  • java.time.chrono
    用于表示除默认 ISO-8601 以外的日历系统的 API。不过也可自定义日历系统。

  • java.time.format
    用于格式化和分析日期和时间的类。

  • java.time.temporal
    扩展 API 主要用于框架和库编写器,允许日期和时间类之间的互操作,查询和调整。字段(TemporalField 和 ChronoField) 和单位(TemporalUnit 和 ChronoUnit)在此包中定义。

  • java.time.zone
    支持时区的类,时区的偏移和时区规则。若使用时区,一般会用到ZonedDateTime 和 ZoneId 或 ZoneOffset

(二)常见的API操作

1、LocalDate

java.time.LocalDate类表示 年月日。不附带任何与时区相关的信息。
提供了相比Date更方便、高效的API操作。
在这里插入图片描述
除了可序列化外,另外三个接口是很值得注意的。

        // 【创建】
        // 创建一个LocalDate对象并读取其值(里面的月份可以使用Month枚举类)
        LocalDate date = LocalDate.of(2024, 2, 22);
        // 使用工厂方法从系统时钟中获取当前的日期
        LocalDate today = LocalDate.now();
        // 还可以从指定时钟中获取当前日期
        // static LocalDate now(Clock clock)

        // 从指定时区的系统时钟获取当前日期。
        // static LocalDate now(ZoneId zone)
		
		// static LocalDate ofEpochDay(long epochDay)
		// 从纪元日计数中(时间戳)获取LocalDate的实例。

        // 【year】
        // 获取年份
        int year = date.getYear();  // 2024
        // 判断是否是闰年
        boolean leap = date.isLeapYear(); // true

        // 【month】
        // 使用Month枚举获取月份字段。
        Month month = date.getMonth(); // FEBRUARY
        // 获取1到12之间的月份字段。
        int monthValue = date.getMonthValue(); // 2
        // 返回此日期表示的月份长度。
        int monthLen = date.lengthOfMonth();  // 29

        // 【day and week】
        // 获取当年第几日
        int dayOfYear = date.getDayOfYear(); // 53
        // 获取当月第几日
        int dayOfMonth = date.getDayOfMonth(); // 22
        // 获取星期几字段,即枚举DayOfWeek。
        DayOfWeek dow = date.getDayOfWeek(); // THURSDAY

Month和DayOfWeek是两个枚举类
在这里插入图片描述
在这里插入图片描述
LocalDate继承ChronoLocalDate,此接口提供了时间的比较方法。

boolean isAfter(ChronoLocalDate other)
检查此日期是否在指定日期之后。

boolean isBefore(ChronoLocalDate other)
检查此日期是否在指定日期之前。

boolean isEqual(ChronoLocalDate other)
检查此日期是否等于指定日期。

int compareTo(ChronoLocalDate other)
将此日期与另一个日期进行比较。

boolean equals(Object obj)
检查此日期是否等于另一个日期。

除了LocalDate的日期类外,还有YearMonth(代表一个特定的一年中的月份)、MonthDay(表示某月的一天,如儿童节 6 月 1 日、Year(代表一年,可用来判断闰年)

2、LocalTime

LocalTime表示时分秒。
不附带任何与时区相关的信息。
在这里插入图片描述

        // 【创建时间】
        // 从时分秒创建
        LocalTime time = LocalTime.of(13, 45, 20);

        // static LocalTime of(int hour, int minute)
        //从指定小时分钟获得LocalTime的实例。

        // static LocalTime now(Clock clock)
        //从指定的时钟获得当前时间。

        // static LocalTime now()
        // 获取当前时间

        // static LocalTime now(ZoneId zone)
        //从指定时区的系统时钟获取当前时间。

        int hour = time.getHour();
        int minute = time.getMinute();
        int second = time.getSecond();
        
int compareTo(LocalTime other)
这个时间与另一个时间比较。

boolean equals(Object obj)
检查此时间是否等于另一个时间。

boolean isBefore(LocalTime other)
检查此时间是否在指定时间之前。

boolean isAfter(LocalTime other)
检查此时间是否在指定时间之后。

3、LocalDateTime

在这里插入图片描述
LocalDateTime,是LocalDate和LocalTime的合体。它同时表示了日期和时间,但不带有时区信息,可以直接创建,也可以通过合并日期和时间对象构造。

        LocalDateTime dt1 = LocalDateTime.of(2021, Month.MARCH, 18, 13, 45, 20);
        
        LocalDateTime dt2 = LocalDateTime.of(date, time);

        LocalDateTime dt3 = LocalDateTime.now();

        // static LocalDateTime now(Clock clock)
        // 从指定的时钟获得当前日期时间。

        // static LocalDateTime now(ZoneId zone)
        // 从指定时区的系统时钟获取当前日期时间。

        // static LocalDateTime now(ZoneId zone)
        // 从指定时区的系统时钟获取当前日期时间。

		// static LocalDateTime ofInstant(Instant instant, ZoneId zone)
		// 从Instant和区域ID获取LocalDateTime的实例。
int compareTo(ChronoLocalDateTime<?> other)
将此日期时间与另一个日期时间进行比较。

boolean equals(Object obj)
检查此日期时间是否等于另一个日期时间。

boolean isAfter(ChronoLocalDateTime<?> other)
检查此日期时间是否在指定的日期时间之后。

boolean isBefore(ChronoLocalDateTime<?> other)
检查此日期时间是否在指定的日期时间之前。

boolean isEqual(ChronoLocalDateTime<?> other)
检查此日期时间是否等于指定的日期时间。


LocalDate 基础上转 LocalDateTime

LocalDateTime atTime(int hour, int minute)
将此日期与创建LocalDateTime的时间相结合。

LocalDateTime atTime(int hour, int minute, int second)
将此日期与创建LocalDateTime的时间相结合。

LocalDateTime atTime(int hour, int minute, int second, int nanoOfSecond)
将此日期与创建LocalDateTime的时间相结合。

LocalDateTime atTime(LocalTime time)
将此日期与创建LocalDateTime的时间相结合。

LocalTime基础上转LocalDateTime

LocalDateTime atDate(LocalDate date)
将此时间与日期相结合以创建LocalDateTime。

LocalDateTime转LocalTime和LocalDate

LocalDate toLocalDate()
获取此日期时间的LocalDate部分。

LocalTime toLocalTime()
获取此日期时间的LocalTime部分。

4、Instant(瞬间)

这是“高级版”时间戳,精确到纳秒,其内部是由两个Long字段组成,第一个部分保存的是自标准Java计算时代(就是1970年1月1日开始)到现在的秒数,第二部分保存的是纳秒数(永远不会超过999,999,999)。同样不包含时区信息。

其实上述的LocalDateTime和LocalTime的时间都是精确到纳秒的,因为日常开发中几乎用不到,这里作了内容的剔除。

        //获得当前时间
        Instant instant = Instant.now(); 
        // 以ISO-8601输出的值形式:2021-01-22T02:49:12.842Z

        // static Instant now(Clock clock)
        //从指定时钟获取当前时刻。
		
		// static Instant ofEpochMilli(long epochMilli)
		// 从1970-01-01T00:00:00Z的纪元中使用毫秒获得Instant的实例。

		// static Instant ofEpochSecond(long epochSecond)
		// 使用1970-01-01T00:00:00Z时代的秒数获得Instant的实例。

		// static Instant ofEpochSecond(long epochSecond, long nanoAdjustment)
		// 使用1970-01-01T00:00:00Z和纳秒级秒的秒数获得Instant的实例。

		// long toEpochMilli()
		// 将此瞬间转换为1970-01-01T00:00:00Z时代的毫秒数。

在这里插入图片描述
瞬间的比较

int compareTo(Instant otherInstant)
将此瞬间与指定的瞬间进行比较。

boolean equals(Object otherInstant)
检查此瞬间是否等于指定的瞬间。

boolean isAfter(Instant otherInstant)
检查此瞬间是否在指定的瞬间之后。

boolean isBefore(Instant otherInstant)
检查此瞬间是否在指定的瞬间之前。

5、Duration/Period

Period(周期)and Duration(周期时间)

  • Duration : 可被转换为天,小时,分钟,秒,毫秒,纳秒
  • Period :可被转换为年,月,天

java.time.temporal提供了一组接口、类和枚举,它们支持日期和时间代码,特别是日期和时间计算。

其中表示时间单位的接口是TemporalUnit:
在这里插入图片描述
实现类ChronoUnit

import java.time.Duration;

public enum ChronoUnit implements TemporalUnit {

    NANOS("Nanos", Duration.ofNanos(1)),
    MICROS("Micros", Duration.ofNanos(1000)),
    MILLIS("Millis", Duration.ofNanos(1000_000)),
    SECONDS("Seconds", Duration.ofSeconds(1)),
    MINUTES("Minutes", Duration.ofSeconds(60)),
    HOURS("Hours", Duration.ofSeconds(3600)),
    HALF_DAYS("HalfDays", Duration.ofSeconds(43200)),
    DAYS("Days", Duration.ofSeconds(86400)),
    WEEKS("Weeks", Duration.ofSeconds(7 * 86400L)),
    MONTHS("Months", Duration.ofSeconds(31556952L / 12)),
    YEARS("Years", Duration.ofSeconds(31556952L)),
	……

    private final String name;
    private final Duration duration;

    private ChronoUnit(String name, Duration estimatedDuration) {
        this.name = name;
        this.duration = estimatedDuration;
    }
	……
}

可以看到单位都是通过Duration建立起来的。(平时为了时间表示的方便,可以引用ChronoUnit 的枚举字段)

具体数值以long为单位(计算的时候可能出现负值)
在这里插入图片描述

时间类(LocalDateTime等)都继承而来Temporal接口。

【创建和获取Duration】
static Duration ofDays(long days)
获得表示多个标准24小时工作日的持续时间。

static Duration ofHours(long hours)
获得表示多个标准小时的持续时间。

static Duration ofMinutes(long minutes)
获得表示多个标准分钟的持续时间。

static Duration ofSeconds(long seconds)
获得表示秒数的持续时间。

static Duration ofMillis(long millis)
获得表示毫秒数的持续时间。

static Duration between(Temporal startInclusive, Temporal endExclusive)
获得表示两个时间对象之间的持续时间的持续时间。

【数值计算】
Duration plus(Duration duration)
返回此持续时间的副本,并添加指定的持续时间。

Duration plus(long amountToAdd, TemporalUnit unit)
返回此持续时间的副本,并添加指定的持续时间。

Duration plusDays(long daysToAdd)
返回此持续时间的副本,并在标准的24小时内添加指定的持续时间。

Duration plusHours(long hoursToAdd)
返回此持续时间的副本,并指定持续时间(以小时为单位)。

Duration plusMillis(long millisToAdd)
返回此持续时间的副本,其中包含指定的持续时间(以毫秒为单位)。

Duration plusMinutes(long minutesToAdd)
返回此持续时间的副本,并添加指定的持续时间(分钟)。

Duration plusSeconds(long secondsToAdd)
返回此持续时间的副本,并添加指定的持续时间(以秒为单位)

Temporal subtractFrom(Temporal temporal)
从指定的时态对象中减去此持续时间。

Duration multipliedBy(long multiplicand)
返回此持续时间的副本乘以标量。

Duration minus(Duration duration) 
返回此持续时间的副本,并减去指定的持续时间。

Duration minus(long amountToSubtract, TemporalUnit unit)
返回此持续时间的副本,并减去指定的持续时间。

Duration minusDays(long daysToSubtract)
返回此持续时间的副本,并在标准的24小时内减去指定的持续时间。

Duration minusHours(long hoursToSubtract)
返回此持续时间的副本,并减去指定的持续时间(以小时为单位)。

Duration minusMillis(long millisToSubtract)
返回此持续时间的副本,并减去指定的持续时间(以毫秒为单位)。

Duration minusMinutes(long minutesToSubtract)
返回此持续时间的副本,并减去指定的持续时间(以分钟为单位)。

Duration minusSeconds(long secondsToSubtract) 
返回此持续时间的副本,并减去指定的持续时间(以秒为单位)。

Duration multipliedBy(long multiplicand)
返回此持续时间的副本乘以标量。

【获取和判断数值】
Duration abs()
返回此持续时间的副,数值为正。

boolean isNegative()
检查此持续时间是否为负,不包括零。

int compareTo(Duration otherDuration)
将此持续时间与指定的持续时间进行比较

boolean equals(Object otherDuration)
检查此持续时间是否等于指定的持续时间。

long get(TemporalUnit unit)
获取所请求单元的值。

long getSeconds()
获取此持续时间内的秒数。

List getUnits()
获取此持续时间支持的单位集。

boolean isZero()
检查此持续时间是否为零。

【数值转换】
long toDays()
获取此持续时间内的天数。

long toHours()
获取此持续时间内的小时数。

long toMillis()
将此持续时间转换为总长度(以毫秒为单位)long toMinutes()
获取此持续时间内的分钟数。

这里有个非常常用的方法
static Duration between(Temporal startInclusive, Temporal endExclusive)
获得表示两个时间对象之间的持续时间的持续时间。方法的核心是调用Temporal的until方法。(注意第二个参数是ChronoUnit.NANOS)
在这里插入图片描述
这里以Temporal接口的实现类LocalDateTime为例:(LocalDateTime中做了一些转化后,如果unit为ChronoUnit实现类,则直接返回对应数据,否则调用unit具体实现类的between方法)
在这里插入图片描述

再看Period
在这里插入图片描述
顶层继承接口为TemporalAmount

【创建Period】
static Period of(int years, int months, int days)
获得表示若干年,月和日的时段。

static Duratio from(TemporalAmount amount)
从时间量获得Period的实例。

static Period ofDays(int days)
获得表示天数的Period。

static Period ofMonths(int months)
获得表示若干月份的Period。

static Period ofWeeks(int weeks)
获得代表若干周的Period。

static Period ofYears(int years)
获得代表若干周的Period。

static Period between(LocalDate startInclusive, LocalDate endExclusive)
获得一个包含两个日期之间的年数,月数和天数的Period。

【获取和判断数值】
long toTotalMonths()
获取此Period的总月数。

long get(TemporalUnit unit)
获取所请求单元的值。

int getDays()
获取此Period的天数。

int getMonths()
获取此Period的月数。

boolean isNegative()
检查此期间是否为负数,不包括零。

boolean isZero()
检查此Period大小是否为零。

boolean equals(Object otherPeriod)
检查此Period是否等于指定的Period。

【计算】
Period plus(TemporalAmount amountToAdd)
返回此Period的副本,并添加指定的Period。

Period plusDays(long daysToAdd)
返回此Period的副本,并添加指定的天数。

Period plusMonths(long monthsToAdd)
返回此Period的副本,并添加指定的月份。

Period plusYears(long yearsToAdd)
返回此Period的副本,并添加指定年份。

Temporal subtractFrom(Temporal temporal)
从指定的时态对象中减去此Period。

Period minus(TemporalAmount amountToSubtract)
返回此Period的副本,并减去指定的Period。

Period minusDays(long daysToSubtract)
返回此Period的副本,并减去指定的天数。

Period minusMonths(long months)
返回此Period的副本,并减去指定的月份。

Period minusYears(long years)
返回此Period的副本,并减去指定的年份。

Period multipliedBy(long multiplicand)
返回此Period的副本乘以标量。

【获取变更数据的副本】
Period withDays(int days)
返回具有指定天数的此Period的副本。

Period withMonths(int months)
返回具有指定月份数的此Period的副本。

Period withYears(int years)
返回具有指定年数的此Period的副本。

一段Java 8官方文档中的代码

    public static void main(String[] args) {
        LocalDate today = LocalDate.now();
        LocalDate birthday = LocalDate.of(1910, Month.APRIL, 6);

        Period period = Period.between(birthday, today);
        Period p = period.withDays(16);

        long p2 = ChronoUnit.DAYS.between(birthday, today);

        System.out.println("You are " + p.getYears() + " years, " + p.getMonths() +
                " months, and " + p.getDays() +
                " days old. (" + p2 + " days total)");
    }
    // You are 110 years, 9 months, and 16 days old. (40469 days total)

可以看到有用ChronoUnit做运算,这是由于Duration的枚举字段的第二个参数值是Duration类型
在这里插入图片描述
自然能够调用Duration的between方法,计算差值。

不过Period.between在使用方面有个坑!!!

        LocalDate start =  LocalDate.of(2021,1,22);
        LocalDate end = LocalDate.of(2022,5,14);
        System.out.println(Period.between(start,end).getDays()); // 22
        System.out.println(ChronoUnit.DAYS.between(start,end));  // 477 (正确结果)

这是因为Period的年月日的组合才是相差的时间

        LocalDate start =  LocalDate.of(2021,1,22);
        LocalDate end = LocalDate.of(2022,5,14);
        System.out.println(Period.between(start,end).getYears());  // 1
        System.out.println(Period.between(start,end).getMonths()); // 3
        System.out.println(Period.between(start,end).getDays()); // 22

6、LocalDate/LocalTime/LocalDateTime/Instant运算

刚才已经介绍了时间单位TemporalUnit、下面还要说一下 时间字段TemporalField
最常用的实现类是ChronoField。它的枚举提供了一组用于访问日期和时间值的常量。

范围字段
在这里插入图片描述
实现类ChronoField提供了一组根据日期和时间,从毫秒到几千年标准单元。

并非所有类都支持 ChronoUnit 对象。比如Instant 类不支持 ChronoUnit.MONTHS
TemporalAccessor.isSupported(TemporalUnit)可验证一个类是否支持特定时间单位。

在这里插入图片描述

public enum ChronoField implements TemporalField {

	……
	
    MILLI_OF_SECOND("MilliOfSecond", MILLIS, SECONDS, ValueRange.of(0, 999)),
    MILLI_OF_DAY("MilliOfDay", MILLIS, DAYS, ValueRange.of(0, 86400L * 1000L - 1)),
    SECOND_OF_MINUTE("SecondOfMinute", SECONDS, MINUTES, ValueRange.of(0, 59), "second"),
    SECOND_OF_DAY("SecondOfDay", SECONDS, DAYS, ValueRange.of(0, 86400L - 1)),
    MINUTE_OF_HOUR("MinuteOfHour", MINUTES, HOURS, ValueRange.of(0, 59), "minute"),
    MINUTE_OF_DAY("MinuteOfDay", MINUTES, DAYS, ValueRange.of(0, (24 * 60) - 1)),
    HOUR_OF_DAY("HourOfDay", HOURS, DAYS, ValueRange.of(0, 23), "hour"),
    DAY_OF_WEEK("DayOfWeek", DAYS, WEEKS, ValueRange.of(1, 7), "weekday"),
    DAY_OF_MONTH("DayOfMonth", DAYS, MONTHS, ValueRange.of(1, 28, 31), "day"),
    MONTH_OF_YEAR("MonthOfYear", MONTHS, YEARS, ValueRange.of(1, 12), "month"),
    YEAR("Year", YEARS, FOREVER, ValueRange.of(Year.MIN_VALUE, Year.MAX_VALUE), "year"),
    INSTANT_SECONDS("InstantSeconds", SECONDS, FOREVER, ValueRange.of(Long.MIN_VALUE, Long.MAX_VALUE)),
 
    private final String name;
    private final TemporalUnit baseUnit;
    private final TemporalUnit rangeUnit;
    private final ValueRange range;
    private final String displayNameKey;

    private ChronoField(String name, TemporalUnit baseUnit, TemporalUnit rangeUnit, ValueRange range) {
        this.name = name;
        this.baseUnit = baseUnit;
        this.rangeUnit = rangeUnit;
        this.range = range;
        this.displayNameKey = null;
    }

    private ChronoField(String name, TemporalUnit baseUnit, TemporalUnit rangeUnit,
            ValueRange range, String displayNameKey) {
        this.name = name;
        this.baseUnit = baseUnit;
        this.rangeUnit = rangeUnit;
        this.range = range;
        this.displayNameKey = displayNameKey;
    }
    
    ……
}

有了上面的基础,下面就可以罗列相关运算的API

【LocalDate】
【---获取值】
Temporal adjustInto(Temporal temporal)
将指定的时态对象调整为与此对象具有相同的日期。

int get(TemporalField field)int获取指定字段的值作为intlong getLong(TemporalField field)
从此日期获取指定字段的值为long。

【--计算】
LocalDate minus(long amountToSubtract, TemporalUnit unit)
返回此日期的副本,并减去指定的数量。

LocalDate minus(TemporalAmount amountToSubtract)
返回此日期的副本,并减去指定的数量。

LocalDate minusDays(long daysToSubtract)
返回此LocalDate的副本,并减去指定的天数。

LocalDate minusMonths(long monthsToSubtract)
返回此LocalDate的副本,并减去指定的月数。

LocalDate minusWeeks(long weeksToSubtract)
返回此LocalDate的副本,并减去指定的周数。

LocalDate minusYears(long yearsToSubtract)
返回此LocalDate的副本,并减去指定的年数。

LocalDate plus(long amountToAdd, TemporalUnit unit)
返回此日期的副本,并添加指定的数量。

LocalDate plus(TemporalAmount amountToAdd)
返回此日期的副本,并添加指定的数量。

LocalDate plusDays(long daysToAdd)
返回此LocalDate的副本,并添加指定的天数。

LocalDate plusMonths(long monthsToAdd)
返回此LocalDate的副本,并添加指定的月数。

LocalDate plusWeeks(long weeksToAdd)
返回此LocalDate的副本,并添加指定的周数。

LocalDate plusYears(long yearsToAdd)
返回此LocalDate的副本,其中添加了指定的年数。

Period until(ChronoLocalDate endDateExclusive)
计算此日期与另一个日期之间的期间作为期间。

long until(Temporal endExclusive, TemporalUnit unit)
根据指定的单位计算到另一个日期的时间量。

LocalDate with(TemporalAdjuster adjuster)
返回此日期的调整副本。

LocalDate with(TemporalField field, long newValue)
返回此日期的副本,并将指定的字段设置为新值。

LocalDate withDayOfMonth(int dayOfMonth)
返回此LocalDate的副本,其中包含日期更改。

LocalDate withDayOfYear(int dayOfYear)
返回此LocalDate的副本,其中包含日期更改。

LocalDate withMonth(int month)
返回此LocalDate的副本,其中包含已更改的月份。

LocalDate withYear(int year)
返回此LocalDate的副本,并更改年份。

【LocalTime】
【---获取值】
int get(TemporalField field)
从此时间获取指定字段的值作为int值。

long getLong(TemporalField field)
从此时间获取指定字段的long值。

【---计算】
LocalTime minus(long amountToSubtract, TemporalUnit unit)
返回此时间的副本,并减去指定的数量。

LocalTime minus(TemporalAmount amountToSubtract)
返回此时间的副本,并减去指定的数量。

LocalTime minusHours(long hoursToSubtract)
返回此LocalTime的副本,并减去指定的小时数。

LocalTime minusMinutes(long minutesToSubtract)
返回此LocalTime的副本,并减去指定的分钟数。

LocalTime minusNanos(long nanos)
返回此LocalTime的副本,并减去指定的纳秒数。

LocalTime minusSeconds(long seconds)
返回此LocalTime的副本,并减去指定的秒数。

LocalTime plus(long amountToAdd, TemporalUnit unit)
返回此时间的副本,并添加指定的数量。

LocalTime plus(TemporalAmount amountToAdd)
返回此时间的副本,并添加指定的数量。

LocalTime plusHours(long hoursToAdd)
返回此LocalTime的副本,并添加指定的小时数。

LocalTime plusMinutes(long minutesToAdd)
返回此LocalTime的副本,并添加指定的分钟数。

LocalTime plusSeconds(long seconds)
返回此LocalTime的副本,并添加指定的秒数。

long until(Temporal endExclusive, TemporalUnit unit)
根据指定的单位计算到另一个时间的时间量。

LocalTime with(TemporalField field, long newValue)
返回此时间的副本,并将指定字段设置为新值。

LocalTime withHour(int hour)
返回此LocalTime的副本,并更改日期。

LocalTime withMinute(int minute)
返回此LocalTime的副本,并更改了分钟。

【LocalDateTime】
【---计算】
LocalDateTime minus(long amountToSubtract, TemporalUnit unit)
返回此日期时间的副本,并减去指定的数量。

LocalDateTime minus(TemporalAmount amountToSubtract)
返回此日期时间的副本,并减去指定的数量。

LocalDateTime minusDays(long daysToSubtract)
返回此LocalDateTime的副本,并减去指定的天数。

LocalDateTime minusHours(long hoursToSubtract)
返回此LocalDateTime的副本,并减去指定的小时数。

LocalDateTime minusMinutes(long minutesToSubtract)
返回此LocalDateTime的副本,并减去指定的分钟数。

LocalDateTime minusMonths(long monthsToSubtract)
返回此LocalDateTime的副本,并减去指定的月数。

LocalDateTime minusSeconds(long seconds)
返回此LocalDateTime的副本,并减去指定的秒数。

LocalDateTime minusWeeks(long weeksToSubtract)
返回此LocalDateTime的副本,并减去指定的周数。

LocalDateTime minusYears(long yearsToSubtract)
返回此LocalDateTime的副本,并减去指定的年数。

LocalDateTime plus(long amountToAdd, TemporalUnit unit)
返回此日期时间的副本,并添加指定的数量。

LocalDateTime plus(TemporalAmount amountToAdd)
返回此日期时间的副本,并添加指定的数量。

LocalDateTime plusDays(long daysToAdd)
返回此LocalDateTime的副本,并添加指定的天数。

LocalDateTime plusHours(long hoursToAdd)
返回此LocalDateTime的副本,并添加指定的小时数。

LocalDateTime plusMinutes(long minutesToAdd)
返回此LocalDateTime的副本,并添加指定的分钟数。

LocalDateTime plusMonths(long monthsToAdd)
返回此LocalDateTime的副本,并添加指定的月份数。

LocalDateTime plusSeconds(long seconds)
返回此LocalDateTime的副本,并添加指定的秒数。

LocalDateTime plusWeeks(long weeksToAdd)
返回此LocalDateTime的副本,并添加指定的周数。

LocalDateTime plusYears(long yearsToAdd)
返回此LocalDateTime的副本,其中添加了指定的年数。

【Instant】
【--计算】
Instant plus(long amountToAdd, TemporalUnit unit)
返回此瞬间的副本,并添加指定的数量。

Instant plus(TemporalAmount amountToAdd)
返回此瞬间的副本,并添加指定的数量。

Instant plusMillis(long millisToAdd)
返回此瞬间的副本,并添加指定的持续时间(以毫秒为单位)。

Instant plusSeconds(long secondsToAdd)
返回此瞬间的副本,并添加指定的持续时间(以秒为单位)long until(Temporal endExclusive, TemporalUnit unit)
根据指定的单位计算到另一个瞬间的时间量。

7、解析与格式化

Java8新版时间API中,提供了解析方法,用于解析包含日期和时间信息的字符串。

我们可以利用DateTimeFormatter 提供一个模式来创建一个格式化对象,可以将格式化对象,传到parse方法中,也可以直接利用格式化对象的format方法进行操作

DateTimeFormatter 类是不可变的和线程安全的;

比如LocalDate的prase方法
在这里插入图片描述

String input = Month.APRIL.getDisplayName(TextStyle.FULL, Locale.getDefault()) + " 03 2003";
try {
    DateTimeFormatter formatter =
            DateTimeFormatter.ofPattern("MMM d yyyy");
    LocalDate date = LocalDate.parse(input, formatter);
    System.out.printf("%s%n", date);
} catch (DateTimeParseException exc) {
    System.out.printf("%s is not parsable!%n", input);
    throw exc;      // Rethrow the exception.
}
// 'date' has been successfully parsed

DateTimeFormatter 的format方法。

ZoneId leavingZone = ZoneId.systemDefault();
ZonedDateTime departure = ZonedDateTime.of(LocalDateTime.now(), leavingZone);

try {
        DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM d yyyy  hh:mm a");
        String out = departure.format(format);
        // LEAVING:  五月 8 2018  03:02 下午 (GMT+08:00)
        System.out.printf("LEAVING:  %s (%s)%n", out, leavingZone);
    } catch (DateTimeException exc) {
        System.out.printf("%s can't be formatted!%n", departure);
        throw exc;
    }
}

(三)兼容过去

Date与LocalDate,LocalDateTime,LocalTime的转换

		// Date --> LocalDate、LocalDateTIme、LocalTime
        Date date=new Date();
        LocalDate localDate=date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        LocalTime localTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
        LocalDateTime localDateTime=date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();

        // LocalDateTime -- > Date
        LocalDateTime localDateTime = LocalDateTime.now();
        java.util.Date date = Date.from(localDateTime.atZone( ZoneId.systemDefault()).toInstant());

        // LocalDate -- > Date
        LocalDate localDate = LocalDate.now();
        Instant instant = localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
        java.util.Date date = Date.from(instant);

        // LocalTime -- > Date
        LocalTime localTime = LocalTime.now();
        LocalDate localDate = LocalDate.now();
        LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
        Instant instant = localDateTime.atZone( ZoneId.systemDefault()).toInstant();
        java.util.Date date = Date.from(instant);

Instant与TimeStamp互相转化

Instant x = Instant.now();
//  instant转timeStamp,自动转换为当前时区时间
Timestamp y = Timestamp.from(x);
// timeStamp转instant,转换后是标准时间
y.toInstant();

TimeStamp和 LocalDateTime 互转

	// TimeStamp -- > LocalDateTime
    // 方式一
    long localDateTime = System.currentTimeMillis();
    LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.of("Asia/Shanghai"));
    // 方式二
    localDateTime = new Timestamp(timestamp).toLocalDateTime();

	// LocalDateTime -- > TimeStamp
	Timestamp timestamp = localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();

java.sql.date和LocalDate相互转换

java.time.LocalDate localDate = sqlDate.toLocalDate();

java.sql.Date sqlDate = java.sql.Date.valueOf(date);

参考:
https://pingfangx.github.io/java-tutorials/datetime/iso/index.html
https://book.douban.com/subject/26772632/

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页