Java 8中的日期时间API

本文内容基本是对《Java 8实战》这本书中关于新日期时间API的讲述的再整理。

Java 8之前的日期、时间类的缺陷

使用过java8之前的日期和时间类(DateCalendarDateFormat)的同学一定对其易用性的缺失表示遗憾,我本人平时使用时,对它们有以下几点不爽:

  1. DateCalendar类的Month值是从0开始计算的,有点反人类。
  2. DateCalendar类是可变对象,给代码可维护性造成了困难。
  3. DateFormat类的方法不是线程安全的,导致无法在线程间共享单例。只能每次使用时去new个新对象或是使用ThreadLocal机制避开多线程访问的隐患。
  4. 无法完成有关时间段的计算:如计算两个日期之前相隔x年y月z天这种需求。

在寻找上述第4条中描述的需求的实现方案时,一位同事找到了优秀的类库JodaTime,而后我们也得知Java 8中吸取了JodaTime中的精髓,提供了新的日期时间API,它们在java.time.*包中。

Java 8对日期和时间的建模方法

相关的类包括:LocalDateLocalTimeLocalDateTimeInstant。这几个类在Java 8中分别表示日期(年月日)、时间(时分秒)、日期时间(年月日时分秒)、时间偏移量。

使用LocalDate

通过如下方法获取一个LocalDate对象并获取日期有关的字段值。

LocalDate date = LocalDate.now();          //对系统当前日期建模
LocalDate date1 = LocalDate.of(2017,9,9);  //指定年月日建模
date.get(ChronoField.DAY_OF_WEEK);         //获取日期是星期几
date1.get(ChronoField.DAY_OF_YEAR);        //获取日期是一年中的第几天

使用LocalTime

通过类似方法创建LocalTime对象,并获取字段值。

LocalTime time = LocalTime.now();                     //为当前时间建模
LocalTime time2 = LocalTime.of(12,14,56);             //指定时分秒
int hourOfDay = time.get(ChronoField.HOUR_OF_DAY);    //获取当前时间在一天内是第几个小时,0-23
int SecondOfDay = time2.get(ChronoField.SECOND_OF_DAY); //获取当前时间在一天内是第几秒

使用LocalDateTime

大家可把LocalDateTime类看成是LocalDateLocalTime两者功能的合集,事实上我们可以通过组合日期和时间对象的方式构建LocalDateTime对象:

LocalDateTime dt = LocalDateTime.now();           //获取系统当前日期时间
LocalDateTime dt2 = LocalDateTime.of(date,time);  //用`LocalDate`和`LocalTime`对象拼装一个`LocalDateTime`对象
LocalDateTime dt3 = LocalDateTime.of(2017,9,10,20,51,24); //对`2017年9月10日20点51分24秒`建模
dt.get(ChronoField.DAY_OF_MONTH);                 //获取一月当中的第几天这个字段值

使用Instant

Instant是以Unix元年时间(UTC时间1970年1月1日午夜时分)开始所经历的秒数进行建模的。使用方法:

Instant instant1 = Instant.now();    //对当前时间建模
Instant instant2 = Instant.ofEpochSecond(1505049200);    //用秒数构建一个Instant对象
Instant instant3 = Instant.ofEpochSecond(1505049200,1_111_000); //用秒数+纳秒数构建一个对象
instant1.get(ChronoField.MILLI_OF_SECOND);    //获取对象中的毫秒部分值
instant2.getLong(ChronoField.INSTANT_SECONDS);    //获取对象中的秒数值
instant2.getLong(ChronoField.NANO_OF_SECOND);    //获取对象中的纳秒部分的值

TIP:单一的Instant无法转换为日期时间,必须加上相应的时区信息才可以,反之亦然。具体请参考下文中关于时区的小节。

建模总结

这几个模型类有一些共性,总结如下:

  • 都可以通过now()方法获得当前对象,或通过of()方法拼装对象
  • 都可通过get(TemporalField field)方法获取对应的字段值。一般只需传入ChronoField类中的预定义的字段枚举即可。
    但是要注意:不同类支持的字段是不一样的。如:不能尝试获取LocalTime类的DAY_OF_YEAR字段,或尝试获取LocalDate类的HOUR_OF_DAY字段。否则你将收获异常-_-
  • 这四个类的对象都是不可变对象,这意味着下文中对它们的修改事实上是创建了该对象的一个副本。

如何对日期、时间操作

回想一下,我们在工程中最经常做的关于日期和时间的计算是:

  1. 在已有对象上进行字段修改加减从而获得计算结果。
  2. 把一个字符串解析成日期或时间对象;或将对象格式化输出为字符串。

本章对LocalDateLocalTimeLocalDateTimeInstant这四个类的操作方法进行总结。

修改操作

with方法

with方法的作用是通过重新设置一个字段的值来获取一个新的对象,如:

LocalDate date = LocalDate.now();    //获取当前日期
LocalDate date2 = date.withDayOfMonth(3);  //将日期改为3日
LocalDate date3 = date2.withMonth(2);  //继续将月份改为2月

还有更通用的with方法的重载版本,它接受一个TemporalField作为要修改的字段:

LocalDate date4 = date3.with(ChronoField.DAY_OF_WEEK,4);  //获取当周的星期四的日期对象

plus、minus方法

这两个方法顾名思义,在原对象上增加或减少一个时间量,从而获取计算结果对象。

注意:此类方法有多个重载版本。因此,这个时间量可以是某个时间单位上的值,也可以是实现了TemporalAmount接口的对象,比如下文介绍的DurationPeriod

使用TemporalAdjuster

如果上述操作仍满足不了你对日期时间计算的需求,那么你可以使用with()方法的另一个重载版本,它接受一个TemporalAdjuster对象。该接口定义如下:

@FunctionalInterface
public interface TemporalAdjuster {
  Temporal adjustInto(Temporal temporal);
}

也就是说你可以自己实现自己的计算过程。这还不算完,JDK中提供了工具类TemporalAdjusters,里面的静态方法实现了多个实用的TemporalAdjuster实现。如:

image.png

格式化和字符串解析

格式化方面,主要涉及的类是DateTimeFormatter,它是线程安全的。基本使用方法如下:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date1 = LocalDate.of(2014, 3, 18);
String formattedDate = date1.format(formatter);
LocalDate date2 = LocalDate.parse(formattedDate, formatter);

另外它还能通过ofPattern()的重载方法创建一个与Locale相关的Formatter。

DateTimeFormatterBuilder可以用来自主构建一个格式器。

Java 8中的时间量(Duration、Period)

Duration用来记录与时间单位相关的时间量,例如"1天1小时30分20秒"。
Period用来记录日期单位相关的时间量,例如“两年3个月零10天”。
这两个类用来对“一段时间的长短”进行建模,因此它们很相似,也提供了一些共同的方法:

image.png

Java 8中的时区

在Java 8中,java.time.ZoneId类用来表示一个时区。你可以通过这样的方式获取到ZoneId对象:

 ZoneId romeZone = ZoneId.of("Europe/Rome");
 ZoneId zoneId = TimeZone.getDefault().toZoneId();  //获取系统默认时区

当获取到了时区对象后,可以用它来:

  1. Instant对象和LocalDatetime互相转换
  2. LocalDateLocalTimeInstant对象转换为ZonedDateTime对象,后者是日期时间加上时区信息后的模型。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容