工作这么久了, 对于 Java 中时间日期的操作一直很蛋疼, 一会用 Date, 一会用 Calendar 一会用 LocalDateTime, 始终没有认真总结过它们的联系与区别. 迷迷糊糊用了好几年了, 今天终于搞清楚了!
一, Java8 日期时间 API 产生的前因后果
1.1 为什么要重新定义一套日期时间 API
操作不方便: java 中最初的 Date 不能直接对指定字段进行加减操作也不支持国际化, 后来新增了 Calendar, 但是 Calendar 又不支持格式化操作, 需要转换成 Date 再进行格式化, 总之一直在填坑, 使用起来一点都不够优雅.
线程不安全: Date,Caleandar,SimpleDateFormat 都是可变的, 线程不安全的, 所以你需要编写额外的代码处理线程安全问题.
1.2 Java8 重新定义
对时间日期相关操作进行细分: 时间, 日期, 日期 & 时间, 时间戳, 时间段, 日期段, 格式化等
所有类都是不可变的, 线程安全
兼容旧的日期时间
1.3Java8 兼容就版本的 Date, 同时也规范了日期时间的转换流程.
一, 给人读的( LocalDateTime & LocalDate & LocalTime)
java8 中将时间和日期进行的区分, 用 LocalDateTime 表示日期和时间, LocalDate 用来表示日期而 LocalTime 表示时间. 内部实现也非常好理解, LocalDateTime = LocalDate + LocalTime, 并且他们的内部 API 也一致, 所以笔者就结合工作中的经验, 介绍他们最常见的用法.
1.1 获取当前时间
- LocalDateTime localDateTime = LocalDateTime.now();
- // 打印结果: 2019-12-02T22:09:20.503
1.2 获取指定时间
- // 获取 2019 年 12 月 02 号 23 : 59 : 59
- LocalDateTime localDateTime2 = LocalDateTime.of(2019, 12, 2, 23, 59, 59);
- // 打印结果: 2019-12-02T13:20:20
1.3 日期 / 时间加减操作
- // localDateTime2 的基础上加 1 天零 1s
- LocalDateTime localDateTime3 = localDateTime2.plusDays(1).plusSeconds(1);
- // 打印结果: 2019-12-04T00:00
1.4 获取指定的字段(年月日时分秒, 纳秒, 不支持毫秒)
- System.out.println("现在是:" + localDateTime.getYear() + "年中的第" + localDateTime.getDayOfYear() +"天");
- // 打印结果: 现在是: 2019 年中的第 336 天
- // 画外音: 快过年了呀, 感觉这一年又没啥收获
二, 给计算机读的(Instant)
小知识: 地球上不同地区经度不同会划分时区, 以零度经线上为准 (格林尼治天文台旧址, UTC 时区) 为准, 将地球上各个部分分为了 24 个时区. 向西走, 每过一个时区, 就要把表拨慢 1 个小时; 同理每向东走一个时区, 就要把表拨快 1 个小时. 最后, 中国处于东 8 区.
2.1 获取 UTC 时间(格林尼治时间)
- Instant instant = Instant.now();
- // 打印结果: 2019-12-02T14:31:41.661Z
2.2 获取北京时间(东 8 区)
- // OffsetTime 表示有时差的时间, 除了 UTC 时间, 都是 OffsetTime
- OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
- // 打印结果: 2019-12-02T22:31:41.661+08:00
2.3 获取毫秒数(1970-01-01T00:00:00Z 开始计算)
- long epochMilli = instant.toEpochMilli()
- // 打印结果: 1575297101661
2.4 定义时间戳
- Instant instant1 = Instant.ofEpochSecond(59);
- // 打印结果: 1970-01-01T00:00:59Z
- instant2 = instant1.plusSeconds(99)
- // 打印结果: 2019-12-02T14:43:00.402Z
三, 时间间隔(Duration)
3.1 计算日期间隔(参数位置影响结果哦)
- Instant instant1 = Instant.now();
- Instant instant2 = instant1.plusSeconds(99);
- Duration duration1 = Duration.between(instant1, instant2);
- Duration duration2 = Duration.between(instant2, instant1);
- // 打印结果 duration1:PT1M39S
- // 打印结果 duration2:PT-1M-39S
- long duration1Seconds = duration1.getSeconds();
- long duration2Seconds = duration1.getSeconds();
- // 打印结果 duration1Seconds: 90
- // 打印结果 duration2Seconds: -90
3.2 操作时间间隔
- Duration duration3 = duration1.plusDays(1);
- // 打印结果: PT24H1M39S
注意 : 仅支持时间操作(Instant, LocalTime,LocalDateTime), 不支持日期(LocalDate)
四, 日期间隔(Period)
- LocalDate localDate1 = LocalDate.now();
- LocalDate localDate2 = localDate1.plusDays(1);
- Period period = Period.between(localDate1, localDate2);
- long days = period.getDays();
- // 打印结果 peroid: P1D
- // 打印结果 days: 1
五, 日期 / 时间校正器(TemporalAdjuster)
5.1 获取指定日期或时间
- LocalDateTime localDateTime1 = LocalDateTime.now();
- LocalDateTime localDateTime2 = localDateTime.withDayOfMonth(20);
- // 打印结果 localDateTime1:2019-12-02T22:57:47.674
- // 打印结果 localDateTime2:2019-12-20T22:57:47.674
5.2 获取下一个固定日期(下一个星期天)
- LocalDateTime localDateTime3 = localDateTime.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
- // 打印结果 localDateTime33:2019-12-08T23:00:43.101
5.3 自定义矫正器
- // 获取下一个工作日
- LocalDateTime localDateTime4 = localDateTime.with((tempDateTime) -> {
- LocalDateTime localDateTime5 = (LocalDateTime) tempDateTime;
- DayOfWeek dayOfWeek = localDateTime5.getDayOfWeek();
- if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
- return localDateTime5.plusDays(3);
- } else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
- return localDateTime5.plusDays(2);
- } else {
- return localDateTime5.plusDays(1);
- }
- });
- // 打印结果 localDateTime4:2019-12-03T23:00:43.101
六, 日期时间格式化
- DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
- LocalDateTime localDateTime = LocalDateTime.now();
- String dateStr =dateTimeFormatter.format(localDateTime);
- // 打印结果: 2019-12-02 23:08:55
- LocalDateTime localDateTime2 = LocalDateTime.parse(dateStr, dateTimeFormatter);
- // 打印结果: 2019-12-02T23:08:55
- LocalDate localDate = LocalDate.parse(dateStr, dateTimeFormatter);
- // 打印结果: 2019-12-02
七, 基于 Instant 进行转换
java8api 对于时间戳, 日期时间以及老版本的 Date 对象之间的转换也进行了兼容和适配, 所有的转换操作都可以基于 Instant 对象进行. 由于 LocalDate,LocalTime 和 LocalDateTime 三个类的操作完全一样, 所以下文仍使用 LocalDateTime 演示.
7.1 时间戳转 LocalDate,LocalDate,LocalDateTime
- long timestamp = Instant.now().toEpochMilli();
- LocalDateTime localDateTime = Instant.ofEpochMilli(timestamp).atOffset(ZoneOffset.ofHours(8)).toLocalDateTime();
- // 打印结果: 2019-12-02T23:20:25.791
7.2 LocalDate,LocalDate,LocalDateTime 转时间戳
- LocalDateTime localDateTime = LocalDateTime.now();
- long timestamp = localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
- // 打印结果: 1575300368099
7.3 兼容就版本 Date
- LocalDateTime localDateTime3 = LocalDateTime.now();Date date =
- Date.from(localDateTime.atZone(ZoneOffset.ofHours(8)).toInstant());LocalDateTime localDateTime4 =
- localDateTime3.atZone(ZoneOffset.ofHours(8)).toLocalDateTime();
- // 打印结果 date:Mon Dec 02 23:32:53 CST 2019
- // 打印结果 lcoalDateTime4:2019-12-02T23:32:53.188
八, Q&A
上一篇问题: 在 java 中通常使用 synchronized 来实现方法同步, AQS 中通过 CAS 保证了修改同步状态的一致性问题, 那么对比 synchronized,cas 有什么优势不同与优势呢? 你还知道其他无锁并发的策略吗?
8.1 Answer
Java 中的无锁并发策略可以分为三种:
基于乐观锁的 CAS 操作
Copy On Write: 写时复制是指: 在并发访问的情景下, 当需要修改 JAVA 中 Containers 的元素时, 不直接修改该容器, 而是先复制一份副本, 在副本上进行修改. 修改完成之后, 将指向原来容器的引用指向新的容器(副本容器)
ThreadLocal: 线程本地存储, 就是为每一个线程创建一个变量, 只有本线程可以在该变量中查看和修改值.
8.2 Question
这是一道送分题: 正如上文提到的, Java8 之前的日期时间以及格式化类是线程不安全的, 你知道怎么编写测试代码吗?
如果优雅得获取昨天 0 点整的毫秒值?
学习 Java 过程中可能遇到问题和困惑, 关注我 vx 公众号 "cruder" , 后台留言, 笔者帮你一起解决!(需要学习资料的请关注后后台留言, 主要都是 java 相关, java 基础, 并发, MySQL,Redis,es,mq 等都都有!)
来源: https://www.cnblogs.com/liqiangchn/p/11974355.html