初探java8的时间API

JAVA8之前时间API的不足

  • Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。
  • java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • java.util.Date这个类只能以毫秒的精度表示时间。这个类还有很多糟糕的问题,比如年份的起始选择是1900年,月份的起始 从0开始。这意味着你要想表示2018年8月22日,就必须创建下面这样的Date实例:
    Date date = new Date (118,7,22);
  • java.util.Date这个类不支持时区,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在一些问题。
  • 所有的日期类都是可变的,因此他们都不是线程安全的。
    举个例子:
    Date类的setTime方法:
    //在没有加锁的情况下,直接操作了实例,会导致线程不安全。
    public void setTime(long time) {
          fastTime = time;
          cdate = null;
      }
    
  • 对于时间、时间戳、格式化以及解析,并没有一些明确定义的类。对于格式化和解析的需求,我们有java.text.DateFormat抽象类,但通常情况下,SimpleDateFormat类被用于此类需求。但是DateFormat类和SimpleDateFormat同时又是线程不安全的。SimpleDateFormat在类的内部维护了一个Calendar实例,这个实例在SimpleDateFormat对象被创建的时候初始化。
    • 在调用format的方法的时候会调用这个Calendar实例 的setTime方法,但是这个setTime方法又是线程不安全的,所以就会造成线程安全的问题。
    • 在调用parse方法的时候,会调用establish这个方法,这个方法会对Calendar实例进行clear操作,而这个操作同样是线程不安全的。

JAVA8时间API

1、JAVA8时间API原则

  • 不变性:新的日期/时间API中,所有的类都是不可变的,如果对实例进行时间加减操作,会返回一个新的实例,而不会修改原实例,这就保证了其在多线程下的良好工作。
  • 关注点分离:新的API将人可读的日期时间和机器时间(unix timestamp)明确分离,它为日期(Date)、时间(Time)、日期时间(DateTime)、时间戳(unix timestamp)以及时区定义了不同的类。
  • 清晰:在所有的类中,方法都被明确定义用以完成相同的行为。举个例子,要拿到当前实例我们可以使用now()方法,在所有的类中都定义了format()和parse()方法,而不是像以前那样专门有一个独立的类。为了更好的处理问题,所有的类都使用了工厂模式和策略模式,一旦你使用了其中某个类的方法,与其他类协同工作并不困难。
  • 实用操作:所有新的日期/时间API类都实现了一系列方法用以完成通用的任务,如:加、减、格式化、解析、从日期/时间中提取单独部分,等等。
  • 可扩展性:新的日期/时间API是工作在ISO-8601日历系统上的,但我们也可以将其应用在非ISO的日历上。

2、新的日期API

Java日期/时间API包
Java日期/时间API包含以下相应的包。

  • java.time包:这是新的Java日期/时间API的基础包,所有的主要基础类都是这个包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。所有这些类都是不可变的和线程安全的,在绝大多数情况下,这些类能够有效地处理一些公共的需求。
  • java.time.chrono包:这个包为非ISO的日历系统定义了一些泛化的API,我们可以扩展AbstractChronology类来创建自己的日历系统。
  • java.time.format包:这个包包含能够格式化和解析日期时间对象的类,在绝大多数情况下,我们不应该直接使用它们,因为java.time包中相应的类已经提供了格式化和解析的方法。
  • java.time.temporal包:使用字段和单位访问日期和时间,以及日期时间调整器。
  • java.time.zone包:这个包包含支持不同时区以及相关规则的类。

Java日期/时间API类

  • ZoneId: 时区ID,用来确定Instant和LocalDateTime互相转换的规则
  • Instant: 用来表示时间线上的一个点
  • LocalDate: 表示日期,不包含与时区相关的信息, LocalDate是不可变并且线程安全的
  • LocalTime: 表示时间,不包含与时区相关的信息, LocalTime是不可变并且线程安全的
  • LocalDateTime: 表示日期时间,不包含与时区相关的信息, LocalDateTime是不可变并且线程安全的
  • Clock: 用于访问当前时刻、日期、时间,用到时区
  • Duration: 持续时间
  • Period:持续日期
  • DateTimeFormatter:时间/日期的解析和格式化

2.1、【不包含时区信息的】日期类—LocalDate

常用方法:

  • now()获取当前日期
  • ofxxx方法,通过传递年月日信息来构建日期
  • parse方法,解析字符串到日期
  • format,格式化日期到字符串
  • getYear、getMonthValue等一些列获取年、月、日信息的方法
  • withXxx方法,例如,withYear,构造一个和当前实例年份不同,但是月、日信息相同的实例
  • plusXxx/minusXxx 进行日期的加减
  • atXxx 构造LocalDateTime实例
  • isAfter/isBefore 日期比较

2.2、【不包含时区信息的】时间类—LocalTime

与LocalDate类似,只不过操作的元素变成了时、分、秒

2.3、【不包含时区信息的】日期时间类—LocalDateTime

相当于LocalDate与LocalTime结合

2.4、时间点—Instant

它是以Unix元年时间(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的秒数进行计算。
常用方法:

  • now() 当前的时间
  • ofEpochSecond 通过与1970年1月1日相隔的秒数来构造Instant
  • ofEpochMilli 通过与1970年1月1日相隔的毫秒数来构造Instant
  • getEpochSecond 获取与1970年1月1日相隔的秒数
  • plusXxx/minusXxx 加减
  • toEpochMilli 获取与1970年1月1日相隔的毫秒数
  • isAfter/isBefore 比较

2.5、“时间”间隔—Duration

方法:

  • ofXxx
  • between 计算时间间隔
    由于 LocalDateTime 和 Instant 是为不同的目的而设计的,一个是为了便于人 阅读使用,另一个是为了便于机器处理,所以你不能将二者混用。如果你试图在这两类对象之间创建 duration ,会触发一个 DateTimeException 异常。此外,由于 Duration 类主要用于以秒和纳秒衡量时间的长短,你不能仅向between 方法传递一个 LocalDate 对象做参数。 如果你需要以年、月或者日的方式对多个时间单位建模,可以使用 Period 类。
	Duration duration1 =Duration.between(LocalTime.now(),LocalTime.now());
    Duration duration2 =Duration.between(Instant.now(),Instant.now());
    //exception will happen here
    Duration duration3 =Duration.between(LocalDate.now(),LocalDate.now());
    System.out.println(duration1);
    System.out.println(duration2);
  • getSeconds() 获取相隔的秒数
  • plusXxx/minusXxxx 在这个间隔的基础上加时间或者是其他的间隔
  • toDays/toHours…将间隔转换成天数、小时数…,对结果取整而不是四舍五入
  • compareTo 比较两个时间间隔

2.6、“日期”间隔—Period

只简单介绍下between方法,其他方法;类比Duration

  • between(LocalDate startDateInclusive, LocalDate endDateExclusive)
    接受两个LocalDate参数

2.7、时间/日期格式化和解析—DateTimeFormatter

此类提供打印和解析的主要应用程序入口点,并提供以下常见实现DateTimeFormatter:

  • 使用预定义的常量,例如 ISO_LOCAL_DATE
  • 使用模式字母,如 uuuu-MMM-dd
  • 使用本地化样式,例如long或medium

更复杂的格式化程序由【DateTimeFormatterBuilder】提供 。

格式化/解析可以通过【LocalDate/LocalTime/LocalDateTime】的format/parse方法实现,调用这两个方法的时候会传递一个【DateTimeFormatter】实例进去

时间/日期字符串解析

String dateStr ="2018-12-12 10:20";
LocalTime localTime = LocalTime.parse(dateStr,DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));

时间/日期 格式化

LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("HH:mm:ss");
System.out.println(localDate.format(formatter1));
System.out.println(localTime.format(formatter2));

2.8、日期时间调整器—TemporalAdjuster

TemporalAdjuster—调整时间对象的策略。
调整器是修改时间对象的关键工具。 它们存在于外部化调整过程中,根据策略设计模式允许不同的方法。
例子将时间调整到获取月底最后一天:

 LocalDate localDate = LocalDate.now();
 LocalDate lastDayPfMonth = localDate.with(TemporalAdjusters.lastDayOfMonth());
 System.out.println("当月最后一天是"+lastDayPfMonth);

2.9、查询—TemporalQuery

有时候需要对日期和时间进行比较复杂的查询,这时候可以利用TemporalQuery接口,它只有唯一一个查询方法R queryFrom(TemporalAccessor temporal)。下面的例子利用这个接口和lambda表达式实现了一个查询到年底还有几天的查询对象,然后调用query方法执行这个查询并返回结果。

    TemporalQuery daysAwayQuery = (temporal)->{
        LocalDate localDate = LocalDate.from(temporal);
        LocalDate lastDayOfYear = localDate.with(TemporalAdjusters.lastDayOfYear());
        return lastDayOfYear.getDayOfYear() -  localDate.getDayOfYear();
    };

    LocalDate now = LocalDate.now();
    int days = now.query(daysAwayQuery);
    System.out.println(MessageFormat.format("距离下一年还有{0}天",days));

2.10、时区处理—ZoneId/ZoneOffset

给定一个时刻,使用不同时区解读,日历信息是不同的。ZoneId用于识别用于在Instant和LocalDateTime之间转换的规则。
默认时区是ZoneId.systemDefault(),可以这样构建ZoneId:

//北京时区
ZoneId bjZone = ZoneId.of("GMT+08:00")
Instant now = Instant.ofEpochSecond(0L);

LocalDateTime dateTime1 = LocalDateTime.ofInstant(now,ZoneId.systemDefault());
LocalDateTime dateTime2= LocalDateTime.ofInstant(now,ZoneId.of("GMT+09:00"));

System.out.println(dateTime1);
System.out.println(dateTime2);

附:时间戳与时区

时间戳

时间戳与时区无关。
时间不分东西南北、在地球的每一个角落都是相同的。他们都有一个相同的名字,叫时间戳。时间戳 指的就是Unix时间戳(Unix timestamp)。它也被称为Unix时间(Unix time)、POSIX时间(POSIX time),是一种时间表示方式,定义为从格林威治时间1970年01月01日00时00分00秒起至现在的总秒数。

时区

1884年在华盛顿召开的国际经度会议(又称国际子午线会议)上,规定将全球划分为24个时区(东、西各12个时区)。

全球同一时刻不同时区的本地时间不同
例如,中国时间:10:00 AM
对于美国西部时间:18:00 PM(冬令时)17:00 PM(夏令时)。

如何根据时间戳转换不同时区的时间

以东八区为例

从东八区的时间得到时间戳

东八区的时间 2018-11-11 12:00:00,转换成时间戳的步骤:

  • 【2018-11-11 12:00:00】(本地时间)减去8个小时得到【2018-11-11 04:00:00】(又称统一时间,UTC时间)
  • 算出【2018-11-11 04:00:00】距离【1970年01月01日00时00分00秒】起至现在的总秒数

从时间戳得到东八区的时间

  • 一个时间戳1L(比较好理解)
  • 转换成时间 1970:01:01 00:00:01(又称统一时间,UTC时间)
  • 【1970:01:01 00:00:01】转换为东八区时间【1970:01:01 08:00:01】(本地时间)

你可能感兴趣的:(java基础知识)