Java8系列 (六) 新的日期和时间API

时间:2022-09-24 14:15:10

概述

在Java8之前, 我们一般都是使用 SimpleDateFormat 来解析和格式化日期时间, 但它是线程不安全的。

    @Test
public void test() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
try {
Date date = sdf.parse("20191103091515");
System.out.println(date.toString());
} catch (ParseException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}

多次运行上面这段程序, 会报不同的异常, 下面是其中的一种

Exception in thread "pool-1-thread-2" Exception in thread "pool-1-thread-4" Exception in thread "pool-1-thread-3" java.lang.NumberFormatException: empty String
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1867)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.java8.action.Demo.lambda$test$0(Demo.java:25)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

原因也很简单, 查看一下源码, 发现 SimpleDateFormat 类继承了父类 DateFormat 的成员变量  protected Calendar calendar; , 而Calendar 类没有被 final 修饰, 是可以被修改的。

回到上面这个问题, 看一下 SimpleDateFormat 的解析日期时间的API

Java8系列  (六)  新的日期和时间API

进入 establish() 方法里面看一下

Java8系列  (六)  新的日期和时间API

到此, 已经基本明了, 因为每次 SimpleDateFormat 解析日期时间都会清空一下它的成员变量 calendar 的值, 所以当多个线程并发访问同一个 SimpleDateformat 时, 就会有线程不安全问题。

解决方式也很简单, 你可以使用 ThreadLocal 类存放 SimpleDateFormat 对象, 让每个线程拥有自己的SimpleDateFormat对象。

    /**Map键对应不同的解析规则字符串, 比如yyyyMMdd*/
private static Map<String, ThreadLocal<SimpleDateFormat>> tl = new HashMap<>();

回到我们今天的主题, 在Java8中引入了新的日期和时间API, 这也是下面要介绍的内容。

新的日期时间类都被 final 修饰, 不存在想上面介绍的老版本API的线程不安全问题。

LocalDate、LocalTime和LocalDateTime

LocalDate和LocalTime, LocalDateTime 提供了许多静态工厂方法来创建它们的实例对象, 并且这三者之间可以很方便的互相进行类型转换。

    @Test
public void test() {
//静态方法创建对象
LocalDate ld = LocalDate.of(2019, 10, 3);
System.out.println(ld.getYear() + "\t" + ld.getMonth() + "\t" + ld.getDayOfMonth() + "\t" + ld.getDayOfWeek() + "\t" + ld.lengthOfMonth() + "\t" + ld.isLeapYear());//result: 2019 OCTOBER 3 THURSDAY 31 false
LocalDate now = LocalDate.now();
System.out.println(now.get(ChronoField.YEAR) + "\t" + now.get(ChronoField.MONTH_OF_YEAR) + "\t" + now.get(ChronoField.DAY_OF_MONTH));//result: 2019 11 3 LocalTime lt = LocalTime.of(20, 44, 12);
System.out.println(lt.getHour() + "\t" + lt.getMinute() + "\t" + lt.getSecond());//result: 20 44 12
//解析字符串
LocalDate ld2 = LocalDate.parse("2019-10-05");//默认格式: yyyy-MM-dd
System.out.println(ld2.toString());//result: 2019-10-05
LocalTime lt2 = LocalTime.parse("20:42:12.828");//默认格式: HH:mm:ss.SSS
System.out.println(lt2.toString());//result: 20:42:12.828
//互相进行类型转换
LocalDateTime ldt = LocalDateTime.of(2019, 10, 5, 21, 12, 10, 888).atZone(ZoneId.of("Asia/Shanghai")).toLocalDateTime();
LocalDateTime ldt2 = LocalDateTime.of(ld2, lt2);// 2019-10-05T20:42:12.828
LocalDateTime ldt3 = ld2.atTime(10, 10, 10);// 2019-10-05T10:10:10
LocalDateTime ldt4 = ld2.atTime(lt2);
LocalDateTime ldt5 = lt2.atDate(ld2);
LocalDateTime ldt6 = LocalDateTime.parse("2019/10/05 20:20:20.888", DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS"));
LocalDate ld6 = ldt6.toLocalDate();
LocalTime lt6 = ldt6.toLocalTime();
}

Instant

Instant对时间的建模方式是以UTC时区的1970年1月1日午夜时分开始所经历的秒数进行计算,它不包含时区信息。Instant类是为了方便计算机处理日期和时间而设计的。

Instant.now().toEpochMilli()  可以获取当前时间的时间戳, 另外, Instant 提供了类似 ofEpochMilli() 的方法根据某个时间戳获取 Instant 实例, isBefore()和isAfter() 则用来比较两个 Instant 的大小

    @Test
public void test2() {
long milli = Instant.now().toEpochMilli();//获取当前时间戳
Instant instant = Instant.ofEpochMilli(1572749169937L);//根据某个时间戳获取Instant实例
Instant instant2 = instant.minusSeconds(1000L);
System.out.println(instant.isAfter(instant2));//true
}

Duration和Period

Duration 对象用秒和纳秒来衡量时间的长短,如果想要对多个时间对象进行日期运算,可以用 Period 类

    @Test
public void test3() {
Duration d1 = Duration.between(LocalDateTime.of(2019, 10, 7, 15, 55, 55, 888), LocalDateTime.now());
Duration d2 = Duration.between(LocalTime.of(17, 55, 10), LocalTime.now());
Duration d3 = Duration.between(Instant.ofEpochMilli(1570544602000L), Instant.now());
System.out.println(d3.toHours());// 612
//Duration对象用秒和纳秒来衡量时间的长短,所以入参不能使用LocalDate类型, 否则抛UnsupportedTemporalTypeException: Unsupported unit: Seconds
//Duration.between(LocalDate.of(2019, 10, 7), LocalDate.now()); //如果想要对多个时间对象进行日期运算,可以用Period
Period p1 = Period.between(LocalDate.of(2018, 8, 30), LocalDate.now());
System.out.println(p1.getYears() + "\t" + p1.getMonths() + "\t" + p1.getDays());// 1 2 4
//工厂方法介绍
Duration threeMinutes = Duration.ofMinutes(3);
threeMinutes = Duration.of(3, ChronoUnit.MINUTES);
Period tenDays = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
}

Temporal接口

LocalDate、LocalTime、LocalDateTime、Instant类都实现了 Temporal 接口,有很多通用的处理日期和时间的方法,比如plus(), minus(), with()

    @Test
public void test4() {
LocalDate ld = LocalDate.of(2019, 10, 7);
//修改时间对象的某个属性值,返回一个新的对象
LocalDate ld2 = ld.withDayOfYear(365);//2019-12-31
LocalDate ld3 = ld.withDayOfMonth(18);//2019-10-18
LocalDate ld4 = ld.with(ChronoField.MONTH_OF_YEAR, 8);//2019-08-07
//对时间对象进行加减运算
LocalDate ld5 = ld.plusWeeks(2L);//2019-10-21
LocalDate ld6 = ld.minusYears(9L);//2010-10-07
LocalDate ld7 = ld.plus(Period.ofMonths(2));//2019-12-07
LocalDate ld8 = ld.plus(2L, ChronoUnit.MONTHS);//2019-12-07 LocalTime lt = LocalTime.parse("10:10:10.888");
LocalTime lt1 = lt.plus(Duration.ofHours(2L));//12:10:10.888
LocalTime lt2 = lt.plus(120L, ChronoUnit.MINUTES);//12:10:10.888
}

TemporalAdjuster接口

TemporalAdjuster 类提供了更多对日期定制化操作的功能, 诸如将日期调整到下个工作日、本月的最后的一天、今年的第一天等等。

    @Test
public void test5() {
LocalDate ld = LocalDate.of(2019, 10, 7);
LocalDate ld1 = ld.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));//2019-10-11
LocalDate ld2 = ld.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));//2019-10-07
LocalDate ld3 = ld.with(TemporalAdjusters.firstDayOfNextMonth());//2019-11-01 //自定义TemporalAdjuster, 来计算下一个工作日所在的日期
LocalDate ld4 = LocalDate.of(2019, 10, 11).with(temporal -> {
DayOfWeek now = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
long dayToAdd = now.equals(DayOfWeek.FRIDAY) ? 3L : now.equals(DayOfWeek.SATURDAY) ? 2L : 1L;
return temporal.plus(dayToAdd, ChronoUnit.DAYS);
});//2019-10-14
//对于经常复用的相同操作,可以将逻辑封装一个类中
TemporalAdjuster temporalAdjuster = TemporalAdjusters.ofDateAdjuster(temporal -> {
DayOfWeek now = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
long dayToAdd = now.equals(DayOfWeek.FRIDAY) ? 3L : now.equals(DayOfWeek.SATURDAY) ? 2L : 1L;
return temporal.plus(dayToAdd, ChronoUnit.DAYS);
});
}

DateTimeFormatter

DateTimeFormatter 用于进行可定制的日期时间格式化, 功能相当于以前的 SimpleDateFormat 类

    @Test
public void test6() {
//日期转字符串
LocalDate ld = LocalDate.of(2019, 10, 7);
String s1 = ld.format(DateTimeFormatter.BASIC_ISO_DATE);//
String s2 = ld.format(DateTimeFormatter.ISO_LOCAL_DATE);//2019-10-07
//字符串转日期
LocalDateTime ld1 = LocalDateTime.parse("2019-10-07 22:22:22.555", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
}

ZoneId时区

ZoneId 是老版本的 TimeZone 的替代品, ZonedDateTime 代表了相对于指定时区的时间点

    @Test
public void test7() {
//LocalDate、LocalDateTime、Instant 转 ZonedDateTime
ZonedDateTime zdt1 = LocalDate.of(2019, 10, 7).atStartOfDay(ZoneId.systemDefault());
ZonedDateTime zdt2 = LocalDateTime.of(2019, 10, 7, 15, 55, 55, 888).atZone(ZoneId.of("Asia/Shanghai"));
ZonedDateTime zdt3 = Instant.now().atZone(ZoneId.of("Asia/Yerevan")); //Instant转LocalDateTime
LocalDateTime ldt1 = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
//下面的两个栗子介绍了ZoneOffset,他是利用和 UTC/格林尼治时间的固定偏差计算时区,但不推荐使用,因为ZoneOffset并未考虑任何夏令时的影响
LocalDateTime ldt2 = LocalDateTime.ofInstant(Instant.now(), ZoneOffset.of("+8"));
//LocalDateTime转Instant
Instant instant = LocalDateTime.of(2019, 10, 7, 15, 55, 55).toInstant(ZoneOffset.of("+4"));
}

参考资料

Java8 实战

SimpleDateFormat线程不安全及解决办法

Java进阶(七)正确理解Thread Local的原理与适用场景

作者:张小凡
出处:https://www.cnblogs.com/qingshanli/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】。

Java8系列 (六) 新的日期和时间API的更多相关文章

  1. 《Java 8 in Action》Chapter 12:新的日期和时间API

    在Java 1.0中,对日期和时间的支持只能依赖java.util.Date类.同时这个类还有两个很大的缺点:年份的起始选择是1900年,月份的起始从0开始. 在Java 1.1中,Date类中的很多 ...

  2. java 8中新的日期和时间API

    java 8中新的日期和时间API 使用LocalDate和LocalTime LocalDate的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息.另外,它也不附带任何与时区相关的信 ...

  3. Java 8 &lpar;11&rpar; 新的日期和时间API

    在Java 1.0中,对日期和时间的支持只能依赖java.util.Date类.这个类只能以毫秒的精度表示时间.这个类还有很多糟糕的问题,比如年份的起始选择是1900年,月份的起始从0开始.这意味着你 ...

  4. Java8 新的日期和时间API&lpar;笔记&rpar;

    LocalDate LocalTime Instant duration以及Period 使用LocalDate和LocalTime //2017-03-20 LocalDate date = Loc ...

  5. Java8新特性--日期和时间API

    如何正确处理时间 现实生活的世界里,时间是不断向前的,如果向前追溯时间的起点,可能是宇宙出生时,又或是是宇宙出现之前, 但肯定是我们目前无法找到的,我们不知道现在距离时间原点的精确距离.所以我们要表示 ...

  6. Java8 日期与时间 API

    在 Java 中,想处理日期和时间时,通常都会选用 java.util.Date 这个类进行处理.不过不知道是设计者在当时没想好还是其它原因,在 Java 1.0 中引入的这个类,大部分的 API 在 ...

  7. 详解Java8特性之新的日期时间 API

    详解Java8特性之新的日期时间 API http://blog.csdn.net/timheath/article/details/71326329 Java8中时间日期库的20个常用使用示例 ht ...

  8. 详解Java8的日期和时间API

    详解Java8的日期和时间API 在JDK1.0的时候,Java引入了java.util.Date来处理日期和时间:在JDK1.1的时候又引入了功能更强大的java.util.Calendar,但是C ...

  9. Java8 &comma;LocalDate,LocalDateTime处理日期和时间工具类,

    Java8 ,LocalDate,LocalDateTime处理日期和时间工具类 1.获取今天的日期 2.在Java 8 中获取年.月.日信息 3.在Java 8 中处理特定日期 4.在Java 8 ...

随机推荐

  1. csv表格处理(上)-- JS 与 PHP 协作导入导出

    CSV简介 在开发后台管理系统的时候,几乎无可避免的会遇到需要导入导出Excel表格的需求.csv也是表格的一种,其中文名为“逗号分隔符文件”.在Excel中打开如下图左边所示,在记事本打开如下图右边 ...

  2. 使用 PHP 限制下载速度

    使用 PHP 限制下载速度 [来源] 达内    [编辑] 达内   [时间]2012-12-12 经常遇到一个问题,那就是有人再办公室下载东西,影响大家上网.办公.同样的问题,要是出现在了服务器上面 ...

  3. SDUT2087 离散事件模拟-银行管理&lpar;模拟&rpar;

    题目链接. 分析: 模拟. 果然模拟什么的最讨厌了. 用e1,e2分别记录队列1,队列2的结束时间. 每个结点的s记录开始时间,e一开是记录逗留时间,进队列的时候,改成离开的时间.时刻记录总时间就可以 ...

  4. 又见thrift异常之TApplicationException&colon; Internal error processing&period;&period;

    客户端调用获取商户提现产品手续费的接口,出现异常org.apache.thrift.TApplicationException: Internal error processing getMercha ...

  5. Js中处理日期加减天数

    Js的处理日期还是很方便的. 一. 格式化日期为2017-07-04的格式 function formatTime(date) { var year = date.getFullYear(); var ...

  6. Xcode 常用代码段

    weak_shortcut /** <#注释#> */ @property(nonatomic,weak) <#class#> *<#name#>; copy_sh ...

  7. Eclipse 打包Web项目

    使用插件fatjar Fat jar插件 http://kurucz-grafika.de/fatjar eclipse菜单栏 help->install new software...-&gt ...

  8. JavaScript设置粘贴板

    设置复制 document.body.oncopy = function(){ alert('不许复制'); return false; }; 设置粘贴 document.getElementById ...

  9. python基础&equals;&equals;&equals;100盏灯的问题

    闪存里有人这样提问这样: 第一轮操作所有电灯,第二轮操作第2盏,第4盏开关,以此类推,第三轮改变编号为3的倍数的电灯,第3盏,第6盏,如果原来那盏灯是亮的,就熄灭它,如果原来是灭的,就点亮它,以此类推 ...

  10. storyboard三种sugue 和 跳转场景的三种方式 以及控制器之间的传值

    Storyboard引入了2个概念:1. scene:一个场景,由一个viewController和相关的xib表示. 2. segue:在这是用于连接scenes,其有多种类型,iphone包括:P ...