计算机程序的思维逻辑 (95) - Java 8的日期和时间API
本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http://item.jd.com/12299018.html
本节继续探讨Java 8的新特性,主要是介绍Java 8对日期和时间API的增强,关于日期和时间,我们在之前已经介绍过两节了,32节介绍了Java 1.8以前的日期和时间API,主要的类是Date和Calendar,由于它的设计有一些不足,业界广泛使用的是一个第三方的类库Joda-Time,关于Joda-time,我们在33节进行了介绍。Java 1.8学习了Joda-time,引入了一套新的API,位于包java.time下,本节,我们就来简要介绍这套新的API。
我们先从日期和时间的表示开始。
表示日期和时间
基本概念
我们在32节介绍过日期和时间的几个基本概念,这里简要回顾下。
- 时刻:所有计算机系统内部都用一个整数表示时刻,这个整数是距离格林尼治标准时间1970年1月1日0时0分0秒的毫秒数,可以理解时刻就是绝对时间,它与时区无关,不同时区对同一时刻的解读,即年月日时分秒是不一样的;
- 时区:同一时刻,世界上各个地区的时间可能是不一样的,具体时间与时区有关,一共有24个时区,英国格林尼治是0时区,北京是东八区,也就是说格林尼治凌晨1点,北京是早上9点;
- 年历:我们都知道,中国有公历和农历之分,公历和农历都是年历,不同的年历,一年有多少月,每月有多少天,甚至一天有多少小时,这些可能都是不一样的,我们主要讨论公历。
Java 8中表示日期和时间的类有多个,主要的有:
- Instant:表示时刻,不直接对应年月日信息,需要通过时区转换
- LocalDateTime: 表示与时区无关的日期和时间信息,不直接对应时刻,需要通过时区转换
- LocalDate:表示与时区无关的日期,与LocalDateTime相比,只有日期信息,没有时间信息
- LocalTime:表示与时区无关的时间,与LocalDateTime相比,只有时间信息,没有日期信息
- ZonedDateTime: 表示特定时区的日期和时间
- ZoneId/ZoneOffset:表示时区
类比较多,但概念更为清晰了,下面我们逐个来看下。
Instant
Instant表示时刻,获取当前时刻,代码为:
Instant now = Instant.now();
可以根据Epoch Time (纪元时)创建Instant,比如,另一种获取当前时刻的代码可以为:
Instant now = Instant.ofEpochMilli(System.currentTimeMillis());
我们知道,Date也表示时刻,Instant和Date可以通过纪元时相互转换,比如,转换Date为Instant,代码为:
public static Instant toInstant(Date date) {
return Instant.ofEpochMilli(date.getTime());
}
转换Instant为Date,代码为:
public static Date toDate(Instant instant) {
return new Date(instant.toEpochMilli());
}
Instant有很多基于时刻的比较和计算方法,大多比较直观,我们就不列举了。
LocalDateTime
LocalDateTime表示与时区无关的日期和时间信息,获取系统默认时区的当前日期和时间,代码为:
LocalDateTime ldt = LocalDateTime.now();
还可以直接用年月日等信息构建LocalDateTime,比如,表示2017年7月11日20点45分5秒,代码可以为:
LocalDateTime ldt = LocalDateTime.of(2017, 7, 11, 20, 45, 5);
LocalDateTime有很多方法,可以获取年月日时分秒等日历信息,比如:
public int getYear()
public int getMonthValue()
public int getDayOfMonth()
public int getHour()
public int getMinute()
public int getSecond()
还可以获取星期几等信息,比如:
public DayOfWeek getDayOfWeek()
DayOfWeek是一个枚举,有七个取值,从DayOfWeek.MONDAY到DayOfWeek.SUNDAY。
LocalDateTime不能直接转为时刻Instant,转换需要一个参数ZoneOffset,ZoneOffset表示相对于格林尼治的时区差,北京是+08:00,比如,转换一个LocalDateTime为北京的时刻,方法为:
public static Instant toBeijingInstant(LocalDateTime ldt) {
return ldt.toInstant(ZoneOffset.of("+08:00"));
}
给定一个时刻,使用不同时区解读,日历信息是不同的,Instant有方法根据时区返回一个ZonedDateTime:
public ZonedDateTime atZone(ZoneId zone)
默认时区是ZoneId.systemDefault(),可以这样构建ZoneId:
//北京时区
ZoneId bjZone = ZoneId.of("GMT+08:00")
ZoneOffset是ZoneId的子类,可以根据时区差构造。
LocalDate/LocalTime
可以认为,LocalDateTime由两部分组成,一部分是日期LocalDate,另一部分是时间LocalTime,它们的用法也很直观,比如:
//表示2017年7月11日
LocalDate ld = LocalDate.of(2017, 7, 11); //当前时刻按系统默认时区解读的日期
LocalDate now = LocalDate.now(); //表示21点10分34秒
LocalTime lt = LocalTime.of(21, 10, 34); //当前时刻按系统默认时区解读的时间
LocalTime time = LocalTime.now();
LocalDateTime由LocalDate和LocalTime构成,LocalDate加上时间可以构成LocalDateTime,LocalTime加上日期可以构成LocalDateTime,比如:
LocalDateTime ldt = LocalDateTime.of(2017, 7, 11, 20, 45, 5);
LocalDate ld = ldt.toLocalDate(); //2017-07-11
LocalTime lt = ldt.toLocalTime(); // 20:45:05 //LocalDate加上时间,结果为2017-07-11 21:18:39
LocalDateTime ldt2 = ld.atTime(21, 18, 39); //LocalTime加上日期,结果为2016-03-24 20:45:05
LocalDateTime ldt3 = lt.atDate(LocalDate.of(2016, 3, 24));
ZonedDateTime
ZonedDateTime表示特定时区的日期和时间,获取系统默认时区的当前日期和时间,代码为:
ZonedDateTime zdt = ZonedDateTime.now();
LocalDateTime.now()也是获取默认时区的当前日期和时间,有什么区别呢?LocalDateTime内部不会记录时区信息,只会单纯记录年月日时分秒等信息,而ZonedDateTime除了记录日历信息,还会记录时区,它的其他大部分构建方法都需要显式传递时区,比如:
//根据Instant和时区构建ZonedDateTime
public static ZonedDateTime ofInstant(Instant instant, ZoneId zone) //根据LocalDate, LocalTime和ZoneId构造
public static ZonedDateTime of(LocalDate date, LocalTime time, ZoneId zone)
ZonedDateTime可以直接转换为Instant,比如:
ZonedDateTime ldt = ZonedDateTime.now();
Instant now = ldt.toInstant();
格式化/解析字符串
Java 8中,主要的格式化类是java.time.format.DateTimeFormatter,它是线程安全的,看个例子:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime ldt = LocalDateTime.of(2016,8,18,14,20,45);
System.out.println(formatter.format(ldt));
输出为:
2016-08-18 14:20:45
将字符串转化为日期和时间对象,可以使用对应类的parse方法,比如:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String str = "2016-08-18 14:20:45";
LocalDateTime ldt = LocalDateTime.parse(str, formatter);
设置和修改时间
修改时期和时间有两种方式,一种是直接设置绝对值,另一种是在现有值的基础上进行相对增减操作,Java 8的大部分类都支持这两种方式,另外,与Joda-Time一样,Java 8的大部分类都是不可变类,修改操作是通过创建并返回新对象来实现的,原对象本身不会变。
我们来看一些例子。
调整时间为下午3点20
代码示例为:
LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.withHour(15).withMinute(20).withSecond(0).withNano(0);
还可以为:
LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.toLocalDate().atTime(15, 20);
三小时五分钟后
示例代码为:
LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.plusHours(3).plusMinutes(5);
LocalDateTime有很多plusXXX和minusXXX方法,用于相对增加和减少时间。
今天0点
可以为:
LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.with(ChronoField.MILLI_OF_DAY, 0);
ChronoField是一个枚举,里面定义了很多表示日历的字段,MILLI_OF_DAY表示在一天中的毫秒数,值从0到(24 * 60 * 60 * 1,000) - 1。
还可以为:
LocalDateTime ldt = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
LocalTime.MIN表示"00:00"
也可以为:
LocalDateTime ldt = LocalDate.now().atTime(0, 0);
下周二上午10点整
可以为:
LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.plusWeeks(1).with(ChronoField.DAY_OF_WEEK, 2)
.with(ChronoField.MILLI_OF_DAY, 0).withHour(10);
下一个周二上午10点整
上面下周二指定是下周,如果是下一个周二呢?这与当前是周几有关,如果当前是周一,则下一个周二就是明天,而其他情况则是下周,代码可以为:
LocalDate ld = LocalDate.now();
if(!ld.getDayOfWeek().equals(DayOfWeek.MONDAY)){
ld = ld.plusWeeks(1);
}
LocalDateTime ldt = ld.with(ChronoField.DAY_OF_WEEK, 2).atTime(10, 0);
针对这种复杂一点的调整,Java 8有一个专门的接口TemporalAdjuster,这是一个函数式接口,定义为:
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}
Temporal是一个接口,表示日期或时间对象,Instant,LocalDateTime,LocalDate等都实现了它,这个接口就是对日期或时间进行调整,还有一个专门的类TemporalAdjusters,里面提供了很多TemporalAdjuster的实现,比如,针对下一个周几的调整,方法是:
public static TemporalAdjuster next(DayOfWeek dayOfWeek)
针对上面的例子,代码可以为:
LocalDate ld = LocalDate.now();
LocalDateTime ldt = ld.with(TemporalAdjusters.next(DayOfWeek.TUESDAY)).atTime(10, 0);
这个next方法是怎么实现的呢?看代码:
public static TemporalAdjuster next(DayOfWeek dayOfWeek) {
int dowValue = dayOfWeek.getValue();
return (temporal) -> {
int calDow = temporal.get(DAY_OF_WEEK);
int daysDiff = calDow - dowValue;
return temporal.plus(daysDiff >= 0 ? 7 - daysDiff : -daysDiff, DAYS);
};
}
它内部封装了一些条件判断和具体调整,提供了更为易用的接口。
TemporalAdjusters中还有很多方法,部分方法如下:
public static TemporalAdjuster firstDayOfMonth()
public static TemporalAdjuster lastDayOfMonth()
public static TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek)
public static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek)
public static TemporalAdjuster previous(DayOfWeek dayOfWeek)
public static TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek)
这些方法的含义比较直观,就不解释了,它们主要是封装了日期和时间调整的一些基本操作,更为易用。
明天最后一刻
代码可以为:
LocalDateTime ldt = LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.MAX);
或者为:
LocalDateTime ldt = LocalTime.MAX.atDate(LocalDate.now().plusDays(1));
本月最后一天最后一刻
代码可以为:
LocalDateTime ldt = LocalDate.now()
.with(TemporalAdjusters.lastDayOfMonth())
.atTime(LocalTime.MAX);
lastDayOfMonth()是怎么实现的呢?看代码:
public static TemporalAdjuster lastDayOfMonth() {
return (temporal) -> temporal.with(DAY_OF_MONTH, temporal.range(DAY_OF_MONTH).getMaximum());
}
这里使用了range方法,从它的返回值可以获取对应日历单位的最大最小值,展开来,本月最后一天最后一刻的代码还可以为:
long maxDayOfMonth = LocalDate.now().range(ChronoField.DAY_OF_MONTH).getMaximum();
LocalDateTime ldt = LocalDate.now()
.withDayOfMonth((int)maxDayOfMonth)
.atTime(LocalTime.MAX);
下个月第一个周一的下午5点整
代码可以为:
LocalDateTime ldt = LocalDate.now()
.plusMonths(1)
.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY))
.atTime(17, 0);
时间段的计算
Java 8中表示时间段的类主要有两个,Period和Duration,Period表示日期之间的差,用年月日表示,不能表示时间,Duration表示时间差,用时分秒表等表示,也可以用天表示,一天严格等于24小时,不能用年月表示,下面看一些例子。
计算两个日期之间的差
看个Period的例子:
LocalDate ld1 = LocalDate.of(2016, 3, 24);
LocalDate ld2 = LocalDate.of(2017, 7, 12);
Period period = Period.between(ld1, ld2);
System.out.println(period.getYears() + "年"
+ period.getMonths() + "月" + period.getDays() + "天");
输出为:
1年3月18天
根据生日计算年龄
示例代码可以为:
LocalDate born = LocalDate.of(1990,06,20);
int year = Period.between(born, LocalDate.now()).getYears();
计算迟到分钟数
假定早上9点是上班时间,过了9点算迟到,迟到要统计迟到的分钟数,怎么计算呢?看代码:
long lateMinutes = Duration.between(
LocalTime.of(9,0),
LocalTime.now()).toMinutes();
与Date/Calendar对象的转换
Java 8的日期和时间API没有提供与老的Date/Calendar相互转换的方法,但在实际中,我们可能是需要的,前面介绍了,Date可以与Instant通过毫秒数相互转换,对于其他类型,也可以通过毫秒数/Instant相互转换。
比如,将LocalDateTime按默认时区转换为Date,代码可以为:
public static Date toDate(LocalDateTime ldt){
return new Date(ldt.atZone(ZoneId.systemDefault())
.toInstant().toEpochMilli());
}
将ZonedDateTime转换为Calendar,代码可以为:
public static Calendar toCalendar(ZonedDateTime zdt) {
TimeZone tz = TimeZone.getTimeZone(zdt.getZone());
Calendar calendar = Calendar.getInstance(tz);
calendar.setTimeInMillis(zdt.toInstant().toEpochMilli());
return calendar;
}
Calendar保持了ZonedDateTime的时区信息。
将Date按默认时区转为LocalDateTime,代码可以为:
public static LocalDateTime toLocalDateTime(Date date) {
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(date.getTime()),
ZoneId.systemDefault());
}
将Calendar转为ZonedDateTime,代码可以为:
public static ZonedDateTime toZonedDateTime(Calendar calendar) {
ZonedDateTime zdt = ZonedDateTime.ofInstant(
Instant.ofEpochMilli(calendar.getTimeInMillis()),
calendar.getTimeZone().toZoneId());
return zdt;
}
小结
本节简要介绍了Java 8中的日期和时间API,相比以前版本的Date和Calendar,它引入了更多的类,但概念更为清晰了,更为强大和易用了,Java 8学习了Joda-Time的很多概念和实现,与我们之前介绍的Joda-Time很像。
从91节讨论Lambda表达式到本节,关于Java 8的主要内容,我们就介绍完了。
同时,关于整个Java编程的基础部分,通过共95节的内容,我们也基本探讨完了,下一节是本系列文章的最后一篇,我们对全部95节内容进行简要梳理。
(与其他章节一样,本节所有代码位于 https://github.com/swiftma/program-logic,位于包shuo.laoma.java8.c95下)
----------------
未完待续,查看最新文章,敬请关注微信公众号“老马说编程”(扫描下方二维码),从入门到高级,深入浅出,老马和你一起探索Java编程及计算机技术的本质。用心原创,保留所有版权。
计算机程序的思维逻辑 (95) - Java 8的日期和时间API的更多相关文章
- Java编程的逻辑 (95) - Java 8的日期和时间API
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...
- 计算机程序的思维逻辑 (8) - char的真正含义
看似简单的char 通过前两节,我们应该对字符和文本的编码和乱码有了一个清晰的认识,但前两节都是与编程语言无关的,我们还是不知道怎么在程序中处理字符和文本. 本节讨论在Java中进行字符处理的基础 - ...
- 计算机程序的思维逻辑 (29) - 剖析String
上节介绍了单个字符的封装类Character,本节介绍字符串类.字符串操作大概是计算机程序中最常见的操作了,Java中表示字符串的类是String,本节就来详细介绍String. 字符串的基本使用是比 ...
- 计算机程序的思维逻辑 (64) - 常见文件类型处理: 属性文件/CSV/EXCEL/HTML/压缩文件
对于处理文件,我们介绍了流的方式,57节介绍了字节流,58节介绍了字符流,同时,也介绍了比较底层的操作文件的方式,60节介绍了随机读写文件,61节介绍了内存映射文件,我们也介绍了对象的序列化/反序列化 ...
- 计算机程序的思维逻辑 (91) - Lambda表达式
在之前的章节中,我们的讨论基本都是基于Java 7的,从本节开始,我们探讨Java 8的一些特性,主要内容包括: 传递行为代码 - Lambda表达式 函数式数据处理 - 流 组合式异步编程 - C ...
- 建议大家使用Java 8 的日期、时间,而非java.util.Date
建议大家使用Java 8 的日期.时间,而非java.util.Date. 详细原因见:如何在Java 8中愉快地处理日期和时间 总结一下就是, java.util.Date 太乱,如 月份从0开始. ...
- Java中的日期和时间
Java中的日期和时间 Java在java.util包中提供了Date类,这个类封装了当前的日期和时间. Date类支持两种构造函数.第一个构造函数初始化对象的当前日期和时间. Date() 下面的构 ...
- Java 8 (11) 新的日期和时间API
在Java 1.0中,对日期和时间的支持只能依赖java.util.Date类.这个类只能以毫秒的精度表示时间.这个类还有很多糟糕的问题,比如年份的起始选择是1900年,月份的起始从0开始.这意味着你 ...
- java 8中新的日期和时间API
java 8中新的日期和时间API 使用LocalDate和LocalTime LocalDate的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息.另外,它也不附带任何与时区相关的信 ...
随机推荐
- 用Node.JS+MongoDB搭建个人博客(成品展示)
在博客里可以随意畅写和分享自己喜欢的技术,和网友分享知识也是一种提升.根据自己所发表的博客也能更加加深印象. 与此同时写博客也可以提高自己的写作能力(虽然不咋地),但我相信博客只会越写越有质量的. 博 ...
- Ubuntu上安装PHP环境-mysql+apache+php-Linux操作系统
安装MYSQL 1. sudo apt-get install mysql-server 或者 apt-get isntall mysql-client 2. 安装过程中会提示设置密码,注意设 ...
- EasyUI combobox 中文无法检索最终解决方案!
写在前面: 因为之前一直用EasyUI的combobox控件,但是苦于在火狐浏览器下输入中文无法直接检索必须在输入完成后再敲击一下键盘才可以(按一下shift或空格),原因是中文输入法屏蔽了EasyU ...
- Maven在导入其他项目时报错:Plugin execution not covered by lifecycle configuration
这几天想把Spring 攻略第二版完整的学习下,所以就在网上下载了该教材的源码,寻思边看书边练习!之前有过一些Maven开发的相关经验,觉得Maven在引入jar包上的配置还是很方便的,所以这次源码的 ...
- (转)搬瓦工(bandwagonhost)后台管理VPS
1. Bandwagonghost使用建议 购买了搬瓦工(bandwagonhost)的VPS,如何使用呢? 首先插几句使用建议,老高认为十分重要,为什么呢?搬瓦工如果监控到有大量的垃圾信息从我们的主 ...
- iOS开发实战-时光记账Demo 网络版
之前写了一个本地数据库版本 戳这里 现在这个就是增加了后台 登录注册页面以及web的上传记录展示页面 含有少量php有兴趣可以看下 另外demo中包括数据库操作.json.网络请求等都没有用到第三方库 ...
- HDOJ2002-计算球体面积
Problem Description 根据输入的半径值,计算球的体积. Input 输入数据有多组,每组占一行,每行包括一个实数,表示球的半径. Output 输出对应的球的体积,对于每组输 ...
- HDU-4787 GRE Words Revenge 解题报告
这是我之前博客里提到的一道AC自动机的练手题,但是要完成这道题,我之前博客里提到的东西还不够,这里总结一下这道题. 这道题不是一般的裸的AC自动机,它的询问和插入是交叉出现的所以用我之前写的板子不大合 ...
- css鼠标样式cursor
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- [补档][ZJOI2007] 报表统计
[ZJOI2007] 报表统计 题目 传送门 小Q的妈妈是一个出纳,经常需要做一些统计报表的工作.今天是妈妈的生日,小Q希望可以帮妈妈分担一些工作,作为她的生日礼物之一. 经过仔细观察,小Q发现统计一 ...