java8 详解时间日期API

在使用java8以前的时间日期类的时候,总觉得这一块有点蹩脚:

  • java.util.Date和java.sql.Date,不知道为啥定义两个同名的类,而且后者还没有默认的构造方法;
  • 日期类格式化类SimpleDateFormat居然是在java.text包下;
  • 推荐实用Calendar获取Date对象的年月日时分秒,不过从名字上很难感觉到两者之间有什么关联。

还有一些其他的问题我不再一一说明了,下面是从网上找到的关于时间日期类的问题总结:

  • 非线程安全:java.util.Date、java.util.Calendar、java.util.GregoiranCalendar和 java.text.SimpleDateFormat四大类都不是线程安全的;
  • 设计不佳 :一方面日期和日期格式化分布在多个包中;另一方面,java.util.Date 的默认日期为1970年1月1日,没有统一性。而且Date 类也缺少直接操作日期的相关方法。
  • 时区处理困难:因为设计不佳,开发人员不得不编写大量代码来处理时区问题。
  • 还有其它一些问题,如Calendar类月份从零开始计算等。

java8对时间日期类进行了重新设计,将这些类都放在了 java.time包和其子包下,并做出了一下改进:

  • 新的日期时间 API 是线程安全的。不仅没有 setter 方法,而且任何对实例的变更都会返回一个新的实例,保证原来的实例不变。
  • 新的日期时间 API 提供了大量的方法,用于修改日期时间的各个部分,并返回一个新的实例。
  • 借鉴了第三方日期时间库joda很多的优点。
  • 在时区方面,新的日期时间 API 引入了 域 ( domain ) 这个概念。
  • Java 8 还针对原来复杂的 API 进行重新组合和拆分,分成了好多个类。

下表是java8中主要的日期时间类:

作用
LocalDate本地日期,没有时区信息
LocalTime本地时间,没有时区信息
LocalDateTime本地日期时间,是上面两个类的集合,没有时区信息
TemporalAdjuster调整日期工具类,比如获取下一日
Duration计算两个时间的时间间隔
Period计算两个日期的时间间隔
Instant记录瞬时时间,没有时区信息
Clock时钟类,包含有时区信息
ZonedDate日期类,包含有时区信息
ZonedTime时间类,包含有时区信息
ZonedDateTime日期时间类,包含有时区信息
ZoneId时区类
DateTimeFormatter日期时间格式化类,功能类似于SimpleDateFormat

接下来按照上表的顺序一一介绍每个类。

一、LocalDate 、LocalTime、LocalDateTime

这三个类没有时区信息,表示的是本地时间或者日期。
这三个类将构造方法都设为private,因此都不能通过new关键字创建对象。为了获取对象,它们都提供了相似的静态方法。

  1. now():不带参数的now()可以获取当前系统时间,内部是借助Clock.systemDefaultZone()完成的,也可以指定参数来获取指定时区或者指定时间日期的对象;
  2. of():通过分别设置年月日时分秒来获得对象,LocalDateTime还提供了入参为LocalDate 和LocalTime的of()方法
  3. parse():可以根据字符串和指定的格式获得时间日期对象,如果没有传入格式,默认使用ISO格式:yyyy-MM-ddThh:mm:ss;
  4. ofXXX():这三个类还提供了一些以of开头的方法来获取对象,有的方法默认起点时间是1970-01-01 00:00:00,大家使用时注意一下,各个类提供的方法不太一致,这里不再一一介绍了;
  5. from():入参为时间日期类对象(TemporalAccessor对象),可以将入参对象转换为LocalDate 、LocalTime或者LocalDateTime。

注意:这三个类都是不可变对象,修改时间日期的任何一个部分都会新建对象。
除了静态方法之外,还有以下实例方法:

  1. with()/withMonth()/withDay()/…:以当前日期时间为基准,修改其中某一部分的值获得一个新对象;
  2. plus()/plusYears()/plusMonths()/…/minus()/minusMonths()/…:在当前时间上加上/减去某个值获得一个新对象;
  3. format():获得一个格式化后的时间日期字符串;
  4. isAfter()/isBefore()/isEqual():比较两个对象;
  5. get()/getXXX():可以获得日期时间的某一个部分值,比如获取年、月。

下面以LocalDateTime为例,介绍这些方法的使用:

    public static void main(String argv[]){
        //获取当前时间
        LocalDateTime now= LocalDateTime.now();
        System.out.println(now);
        LocalDateTime parseDate= LocalDateTime.parse("2021-01-01T00:00:01");
        System.out.println(parseDate);
        //获取2021-01-01T00:01:01
        LocalDateTime ofDate= LocalDateTime.of(2021,1,1,0,1,1);
        System.out.println(ofDate);
        LocalDateTime ofDate1= LocalDateTime.of(LocalDate.now(), LocalTime.now());
        System.out.println(ofDate1);

        LocalDateTime firstDay=now.with(TemporalAdjusters.firstDayOfMonth());
        System.out.println(firstDay==now);
        LocalDateTime addOneDay=now.plusDays(1);
        System.out.println(addOneDay);
        System.out.println(now.format(DateTimeFormatter.ofPattern("yyyy_MM_dd hh:mm:ss")));
    }

运行结果为:

2021-01-13T21:15:37.980
2021-01-01T00:00:01
2021-01-01T00:01:01
2021-01-13T21:15:38.091
false
2021-01-14T21:15:37.980
2021_01_13 09:15:37

二、TemporalAdjuster

该类用于调整时间日期,可以在当前对象的基础上调整到本月的第一个天或者下个星期。
该类可以让我们更加灵活方便的调整时间日期。注意该类是一个接口,不过java为我们提供了TemporalAdjusters工具类可以直接获取TemporalAdjuster对象。

	public static void main(String argv[]){
        //获取当前时间
        LocalDateTime now= LocalDateTime.now();
        System.out.println(now);
        //调整为本月的第一天
        System.out.println(now.with(TemporalAdjusters.firstDayOfMonth()));
        //调整为下个月的第一天
        System.out.println(now.with(TemporalAdjusters.firstDayOfNextMonth()));
        //调整到下个星期五
        System.out.println(now.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)));
        //如果TemporalAdjusters不能满足功能,可以创建TemporalAdjuster对象
        //下面实现一个对象,用于调整当前时间到5个小时之后
        System.out.println(now.with(new TemporalAdjuster(){
            //TemporalAdjuster接口只有这一个方法
            public Temporal adjustInto(Temporal temporal){
                if(temporal instanceof LocalDate) {
                    return temporal;//日期对象无法调整
                }
                else if(temporal instanceof LocalTime)   {
                    LocalTime time=(LocalTime)temporal;
                    return time.plusHours(5);
                }else if(temporal instanceof LocalDateTime){
                    LocalDateTime time=(LocalDateTime)temporal;
                    return time.plusHours(5);
                }
                return temporal;
            }
        }));
    }

运行结果为:

2021-01-13T21:37:00.536
2021-01-01T21:37:00.536
2021-02-01T21:37:00.536
2021-01-15T21:37:00.536
2021-01-14T02:37:00.536

三、Duration

该类表示一个时间间隔,时间间隔会转换为秒和纳秒存储在Duration对象中。
常用方法有:

  1. ofDays()/ofHours()/ofXXX():设置一个时间间隔,单位为天/小时/XXX;
  2. parse():入参为字符串,根据字符串解析出时间间隔,字符串的格式为+/-PnDTnHnMn.nS,n表示数字,D表示天,H表示小时,M表示分钟,S表示秒,这是 ISO-8601的表示格式;
  3. between():计算两个日期时间之间的间隔,该方法要求两个入参必须支持时间,比如LocalDate只支持日期,使用该方法会报错。

以上三个方法是静态方法,下面的方法是实例方法;

  1. withSeconds()/withNanos():调整秒或者纳秒;
  2. plus()/minus():在当前间隔的基础上增加或者减去一个时间;
  3. toHours()/toMillis()/toDays()/…:将当前间隔转换为要求的时间日期单位。
    public static void main(String argv[]){
        Duration dayDuration=Duration.ofDays(1);
        System.out.println(dayDuration);
        Duration minutesDuration=Duration.ofMinutes(1);
        System.out.println(minutesDuration);
        //字符串的格式为PnDTnHnMn.nS
        Duration parseDuration=Duration.parse("P1DT1H1M1.1S");
        System.out.println(parseDuration);
        //获取当前时间
        LocalDateTime now= LocalDateTime.now();
        System.out.println(now);
        //一个小时后的时间
        LocalDateTime oneHour= now.plusHours(1);
        System.out.println(oneHour);
        Duration betweenDuration=Duration.between(now,oneHour);
        System.out.println(betweenDuration);
        //一分钟加上13秒
        System.out.println(minutesDuration.plusSeconds(13));
        //一分钟加上13秒转换成毫秒
        System.out.println(minutesDuration.plusSeconds(13).toMillis());
    }

运行结果为:

PT24H
PT1M
PT25H1M1.1S
2021-01-13T22:09:02.874
2021-01-13T23:09:02.874
PT1H
PT1M13S
73000

如果将Duration.between()方法的入参对象换成LocalDate:

    public static void main(String argv[]){
        //获取当前时间
        LocalDate now= LocalDate.now();
        System.out.println(now);
        //一个小时后的时间
        LocalDate oneDay= now.plusDays(1);
        System.out.println(oneDay);
        Duration betweenDuration=Duration.between(now,oneDay);
    }

执行上面的代码会报如下错误:

Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds
	at java.time.LocalDate.until(LocalDate.java:1614)
	at java.time.Duration.between(Duration.java:475)
	at Main.main(Main.java:30)

四、Period

Period与Duration功能相似,都是表示一个时间间隔,只不过Period的最小间隔单位为天,Duration的最小间隔单位为纳秒。
Period使用years、months、days三个int类型记录时间间隔。
Period提供了和Duration类似的方法,可以参考Duration。
Period.between()方法只能计算两个LocalDate对象之间的时间间隔,这一点与Duration不同。
下面是Period的例子:

    public static void main(String argv[]){
        Period dayPeriod=Period.ofDays(1);
        System.out.println(dayPeriod);
        Period monthsDuration=Period.ofMonths(1);
        System.out.println(monthsDuration);
        //字符串的格式为PnYnMnD
        Period parsePeriod=Period.parse("P1Y1M2D");
        System.out.println(parsePeriod);
        //获取当前时间
        LocalDate now= LocalDate.now();
        System.out.println(now);
        //一个小时后的时间
        LocalDate tenDays= now.plusDays(10);
        System.out.println(tenDays);
        Period betweenPeriod=Period.between(now,tenDays);
        System.out.println(betweenPeriod);
        //一个月加上两天
        System.out.println(monthsDuration.plusDays(2));
        //一个月加上15天,最后转换为月表示
        System.out.println(monthsDuration.plusDays(15).toTotalMonths());
    }

运行结果为:

P1D
P1M
P1Y1M2D
2021-01-14
2021-01-24
P10D
P1M2D
1

五、Instant

Instant表示某个时间点或者说某个时刻,最小精确到纳秒,内部使用long型的seconds和int型的nanos记录该时间点,其中seconds记录了从1970-01-01T00:00:00以来的秒数。
Instant提供了如下常用方法:

  1. now():返回当前时刻,与LocalTime不同的是,这里返回的UTC时间,与北京时间相差八个小时,
  2. ofEpochSecond()/ofEpochSecond()/ofEpochMilli():可以根据秒数/纳秒数/毫秒数设置当前时刻,当以秒或者毫秒设置时,时间基准是1970-01-01T00:00:00;
  3. parse():入参为字符串,根据字符串解析出当前时刻,字符串的格式为yyyy-MM-DDThh:mm:ss.SSZ,比如2007-12-03T10:15:30.00Z,这个字符串表示时间也是UTC时间;
  4. getEpochSecond()/getNano():前者返回从1970-01-01T00:00:00以来的秒数,后者返回当前时刻的纳秒数;
  5. with():在当前时刻的基础上调整时间,只支持以秒/纳秒/毫秒/微秒调整;
  6. plus()/minus():在当前时刻基础上加上/减去时间得到一个新的时刻;
  7. compareTo()/isAfter()/isBefore():比较大小
  8. atOffset():入参为基于UTC时间的偏移量,可以指定偏移几个小时或者几分钟,返回值是一个OffsetDateTime,该值是基于UTC时间的偏移时间。

Instant不包含时区信息,该类记录的时间都是UTC时间,不是某个时区的时间。

    public static void main(String argv[]){
        Instant now=Instant.now();
        System.out.println(now);
        Instant oneSec=Instant.ofEpochSecond(1);
        System.out.println(oneSec);
        Instant parseInstant=Instant.parse("2017-02-03T10:37:30.00Z");
        System.out.println(parseInstant);
        System.out.println(parseInstant.getEpochSecond());
        System.out.println(now.atOffset(ZoneOffset.ofHours(8)));
    }

运行结果为:

2021-01-14T13:35:32.586Z
1970-01-01T00:00:01Z
2017-02-03T10:37:30Z
1486118250
2021-01-14T21:35:32.586+08:00

六、Clock

该类用于获取某个时刻,包含了日期和时间,带有时区信息。
上面介绍的类里面的now()方法就是通过该类获得的当前时间,
该类是一抽象类,其提供了如下实现类:

  • SystemClock:当前系统时间,内部调用System.currentTimeMillis()实现,使用属性zone记录当前时区;
  • OffsetClock:基于一个Clock对象,添加一个Duration类型的偏移量;
  • TickClock:在一个Clock对象的基础上,偏移一定量的纳秒时间;
  • FixedClock:表示一个固定时间。

Clock提供了一些静态方法可以获得上面这些类的对象。
下面举几个例子:

    public static void main(String argv[]){
        //系统当前时间
        Clock now=Clock.systemDefaultZone();
        System.out.println(now);
        System.out.println(now.instant());
        Clock utc=Clock.systemUTC();
        System.out.println(utc);
        System.out.println(utc.instant());
        //在当前时间的基础上增加1个小时
        Clock oneHour=Clock.offset(now,Duration.ofHours(1));
        System.out.println(oneHour.instant());
        //返回当前时间的毫秒数,与System.currentTimeMillis()运行结果相同
        System.out.println(now.millis());
    }

运行结果为:

SystemClock[Asia/Shanghai]
2021-01-14T14:08:00.675Z
SystemClock[Z]
2021-01-14T14:08:00.719Z
2021-01-14T15:08:00.719Z
1610633280719

七、ZonedDate 、ZonedTime、ZonedDateTime

功能与LocalDate 、LocalTime、LocalDateTime类似,也提供类似的方法,区别在于这三个类记录了时区信息,内部有一个ZoneId的属性记录时区信息。
使用方法可以参考LocalDate 、LocalTime、LocalDateTime。

八、ZoneId

ZoneId是时区对象。

    public static void main(String argv[]){
        //获取当前系统默认的时区
        ZoneId systemZone=ZoneId.systemDefault();
        System.out.println(systemZone);
        System.out.println("======================");
        //获取当前系统支持的所有时区
        Set<String> zoneSet=ZoneId.getAvailableZoneIds();
        zoneSet.stream().forEach(System.out::println);
        System.out.println("======================");
        //设置一个指定时区
        ZoneId certainZone=ZoneId.of("Asia/Shanghai");
        System.out.println(certainZone);
    }

运行结果:

Asia/Shanghai
======================
Asia/Aden
America/Cuiaba
Etc/GMT+9
Etc/GMT+8
Africa/Nairobi
。。。。。。时区省略
Pacific/Majuro
America/Argentina/Buenos_Aires
Europe/Nicosia
Pacific/Guadalcanal
Europe/Athens
US/Pacific
Europe/Monaco
======================
Asia/Shanghai

九、DateTimeFormatter

DateTimeFormatter从功能上来说与SimpleDateFormat类似,都是解析时间字符串或者格式化时间日期对象,两者最大的区别是DateTimeFormatter是线程安全的,SimpleDateFormat是线程不安全的。如果在多线程环境中使用SimpleDateFormat,要么加锁,要么使用ThreadLocal对象做隔离。
DateTimeFormatter常用方法:

  1. ofPattern():静态方法,入参为指定格式的字符串,可以通过本方法获得一个DateTimeFormatter对象;
  2. format():实例方法,可以将日期时间对象格式化为字符串。
    public static void main(String argv[]){
        DateTimeFormatter formatter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        TemporalAccessor data=formatter.parse("2021-01-01 14:14:15");
        System.out.println(data);
        //转换为LocalDateTime对象
        LocalDateTime dateTime=LocalDateTime.from(data);
        System.out.println(dateTime);
        //转换为LocalDate对象
        LocalDate date=LocalDate.from(data);
        System.out.println(date);
        LocalDateTime time=LocalDateTime.now();
        System.out.println(formatter.format(time));
    }

运行结果为:

{},ISO resolved to 2021-01-01T14:14:15
2021-01-01T14:14:15
2021-01-01
2021-01-15 22:29:33

DateTimeFormatter提供一些常用的格式化常量,举例如下:

public static final DateTimeFormatter ISO_INSTANT;
public static final DateTimeFormatter ISO_WEEK_DATE;
public static final DateTimeFormatter ISO_ORDINAL_DATE;
public static final DateTimeFormatter ISO_DATE_TIME;

可以直接使用上面这些常量格式化时间日期对象或者格式化字符串。
除了使用DateTimeFormatter之外,一些时间对象也提供了parse()和format()方法,这两个方法效果与使用DateTimeFormatter一样。

https://blog.csdn.net/ThinkWon/article/details/111087199

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值