JSR310-新日期APIJSR310新日期API(完结篇)-生产实战
前提
前面通过五篇文章基本介绍完JSR-310常用的日期时间API以及一些工具类,这篇博文主要说说笔者在生产实战中使用JSR-310日期时间API的一些经验。
系列文章:
- JSR310新日期API(一)-时区与时间偏移量
- JSR310新日期API(二)-日期时间API
- JSR310新日期API(三)-日期时间格式化与解析
- JSR310新日期API(四)-日期时间常用计算工具
- JSR310新日期API(五)-在主流框架中使用新日期时间类
::: info
不经意间,JDK8发布已经超过6年了,如果还在用旧的日期时间API,可以抽点时间熟悉一下JSR-310的日期时间API。
:::
仿真场景
下面会结合一下仿真场景介绍具体的API选取,由于OffsetDateTime基本能满足大部分场景,因此挑选OffsetDateTime进行举例。
场景一:字符串输入转换为日期时间对象
一般在Web应用的表单提交或者Reuqest Body提交的内容中,需要把字符串形式的日期时间转换为对应的日期时间对象。Web应用多数情况下会使用SpringMVC,而SpringMVC的消息转换器在处理application/json类型的请求内容的时候会使用ObjectMapper(Jackson)进行反序列化。这里引入org.springframework.boot:spring-boot-starter-web:2.2.5.RELEASE做一个演示。

引入spring-boot-starter-web的最新版本之后,内置的Jackson已经引入了JSR-310相关的两个依赖。SpringBoot中引入在装载ObjectMapper通过Jackson2ObjectMapperBuilder中的建造器方法加载了JavaTimeModule和Jdk8Module,实现了对JSR-310特性的支持。值得注意的是JavaTimeModule中和日期时间相关的格式化器DateTimeFormatter都使用了内置的实现,如日期时间使用的是DateTimeFormatter.ISO_OFFSET_DATE_TIME,无法解析yyyy-MM-dd HH:mm:ss模式的字符串。例如:
public class Request {
private OffsetDateTime createTime;
public OffsetDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(OffsetDateTime createTime) {
this.createTime = createTime;
}
}
@PostMapping(path = "/test")
public void test(@RequestBody Request request) throws Exception {
LOGGER.info("请求内容:{}", objectMapper.writeValueAsString(request));
}
请求如下:
curl --location --request POST 'localhost:9091/test' \
--header 'Content-Type: application/json' \
--data-raw '{
"createTime": "2020-03-01T21:51:03+08:00"
}'
// 请求内容:{"createTime":"2020-03-01T13:51:03Z"}
如果执意要选用yyyy-MM-dd HH:mm:ss模式的字符串,那么属性的类型只能选用LocalDateTime并且要重写对应的序列化器和反序列化器,覆盖JavaTimeModule中原有的实现,参考前面的一篇文章。
场景二:查询两个日期时间范围内的数据
笔者负责的系统中,经常有定时调度的场景,举个例子:每天凌晨1点要跑一个定时任务,查询T-1日或者上一周的业务数据,更新到对应的业务统计表中,以便第二天早上运营的同事查看报表数据。查询T-1日的数据,实际上就是查询T-1日00:00:00到23:59:59的数据。这里举一个案例,计算T-1日所有订单的总金额:
@Slf4j
public class Process {
static ZoneId Z = ZoneId.of("Asia/Shanghai");
static DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
JdbcTemplate jdbcTemplate;
@Data
private static class Order {
private Long id;
private String orderId;
private BigDecimal amount;
private OffsetDateTime createTime;
}
public void processTask() {
// 这里的时区要按实际情况选择
OffsetDateTime now = OffsetDateTime.now(Z);
OffsetDateTime start = now.plusDays(-1L).withHour(0).withMinute(0).withSecond(0).withNano(0);
OffsetDateTime end = start.withHour(23).withMinute(59).withSecond(59).withNano(0);
BigDecimal totalAmount = BigDecimal.ZERO;
int limit = 500;
long maxId = 0L;
while (true) {
List<Order> orders = selectPendingProcessingOrders(start, end, limit, maxId);
if (!orders.isEmpty()) {
totalAmount = totalAmount.add(orders.stream().map(Order::getAmount).reduce(BigDecimal::add)
.orElse(BigDecimal.ZERO));
maxId = orders.stream().map(Order::getId).max(Long::compareTo).orElse(Long.MAX_VALUE);
} else {
break;
}
}
log.info("统计[{}-{}]的订单总金额为:{}", start.format(F), end.format(F), totalAmount);
}
static ResultSetExtractor<List<Order>> MANY = r -> {
List<Order> orders = new ArrayList<>();
while (r.next()) {
Order order = new Order();
orders.add(order);
order.setId(r.getLong("id"));
order.setOrderId(r.getString("order_id"));
order.setAmount(r.getBigDecimal("amount"));
order.setCreateTime(OffsetDateTime.ofInstant(r.getTimestamp("create_time").toInstant(), Z));
}
return orders;
};
private List<Order> selectPendingProcessingOrders(OffsetDateTime start, OffsetDateTime end, int limit, long id) {
return jdbcTemplate.query("SELECT * FROM t_order WHERE create_time >= ? AND create_time <= ? AND id > ? LIMIT ?",
p -> {
p.setTimestamp(1, Timestamp.from(start.toInstant()));
p.setTimestamp(2, Timestamp.from(end.toInstant()));
p.setLong(3, id);
p.setInt(4, limit);
}, MANY);
}
}
上面的只是伪代码,不能直接执行,使用的是基于日期时间和ID翻页的设计,在保证效率的同时可以降低IO,常用于查询比较多的定时任务或者数据迁移。
场景三:计算两个日期时间之间的差值
计算两个日期时间之间的差值也是很常见的场景,笔者遇到过的场景就是:运营需要导出一批用户数据,主要包括用户ID、脱敏信息、用户注册日期时间以及注册日期时间距当前日期的天数。
| 用户ID | 用户姓名 | 注册日期时间 | 注册距今天数 |
|---|---|---|---|
| 1 | 张小狗 | 2019-01-03 12:11:23 | x |
| 2 | 张大狗 | 2019-10-02 23:22:13 | y |
设计的伪代码如下:
@Data
private static class CustomerDto {
private Long id;
private String name;
private OffsetDateTime registerTime;
private Long durationInDay;
}
@Data
private static class Customer {
private Long id;
private String name;
private OffsetDateTime registerTime;
}
static ZoneId Z = ZoneId.of("Asia/Shanghai");
static OffsetDateTime NOW = OffsetDateTime.now(Z);
public List<CustomerDto> processUnit() {
return Optional.ofNullable(select()).filter(Objects::nonNull)
.map(list -> {
List<CustomerDto> result = new ArrayList<>();
list.forEach(x -> {
CustomerDto dto = new CustomerDto();
dto.setId(x.getId());
dto.setName(x.getName());
dto.setRegisterTime(x.getRegisterTime());
Duration duration = Duration.between(x.getRegisterTime(), NOW);
dto.setDurationInDay(duration.toDays());
result.add(dto);
});
return result;
}).orElse(null);
}
private List<Customer> select() {
// 模拟查询
return null;
}
通过Duration可以轻松计算两个日期时间之间的差值,并且可以轻松转换为不同的时间计量单位。
场景四:计算特殊节假日的日期
利用日期时间校准器TemporalAdjuster可以十分方便地计算XX月YY日是ZZ节这种日期形式的节日。例如:五月第二个星期日是母亲节,六月的第三个星期日是父亲节。
public class X {
public static void main(String[] args) throws Exception {
OffsetDateTime time = OffsetDateTime.now();
System.out.println(String.format("%d年母亲节是:%s", time.getYear(),
time.withMonth(5).with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.SUNDAY)).toLocalDate().toString()));
System.out.println(String.format("%d年父亲节是:%s", time.getYear(),
time.withMonth(6).with(TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.SUNDAY)).toLocalDate().toString()));
time = time.plusYears(1);
System.out.println(String.format("%d年母亲节是:%s", time.getYear(),
time.withMonth(5).with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.SUNDAY)).toLocalDate().toString()));
System.out.println(String.format("%d年父亲节是:%s", time.getYear(),
time.withMonth(6).with(TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.SUNDAY)).toLocalDate().toString()));
}
}
// 输出结果
2020年母亲节是:2020-05-10
2020年父亲节是:2020-06-21
2021年母亲节是:2021-05-09
2021年父亲节是:2021-06-20
有些定时调度或者提醒消息发送需要在这类特定的日期时间触发,那么通过TemporalAdjuster就可以相对简单地计算出具体的日期。
小结
关于JSR-310的日期时间API就介绍这么多,笔者最近从事数据方面的工作,不过肯定会持续和JSR-310打交道。
附录
这里贴一个工具类OffsetDateTimeUtils:
@Getter
@RequiredArgsConstructor
public enum TimeZoneConstant {
CHINA(ZoneId.of("Asia/Shanghai"), "上海-中国时区");
private final ZoneId zoneId;
private final String description;
}
public enum DateTimeUtils {
// 单例
X;
public static final DateTimeFormatter L_D_T_F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static final DateTimeFormatter S_D_F = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static final DateTimeFormatter S_D_M_F = DateTimeFormatter.ofPattern("yyyy-MM");
public static final DateTimeFormatter S_T_F = DateTimeFormatter.ofPattern("HH:mm:ss");
public OffsetDateTime getCurrentOffsetDateTime() {
return OffsetDateTime.now(TimeZoneConstant.CHINA.getZoneId());
}
public OffsetDateTime getDeltaDayOffsetDateTimeStart(long delta) {
return getCurrentOffsetDateTime().plusDays(delta).withHour(0).withMinute(0).withSecond(0).withNano(0);
}
public OffsetDateTime getDeltaDayOffsetDateTimeEnd(long delta) {
return getCurrentOffsetDateTime().plusDays(delta).withHour(23).withMinute(59).withSecond(59).withNano(0);
}
public OffsetDateTime getYesterdayOffsetDateTimeStart() {
return getDeltaDayOffsetDateTimeStart(-1L);
}
public OffsetDateTime getYesterdayOffsetDateTimeEnd() {
return getDeltaDayOffsetDateTimeEnd(-1L);
}
public long durationInDays(OffsetDateTime start, OffsetDateTime end) {
return Duration.between(start, end).toDays();
}
public OffsetDateTime getThisMonthOffsetDateTimeStart() {
OffsetDateTime offsetDateTime = getCurrentOffsetDateTime();
return offsetDateTime.with(TemporalAdjusters.firstDayOfMonth()).withHour(0).withMinute(0).withSecond(0).withNano(0);
}
public OffsetDateTime getThisMonthOffsetDateTimeEnd() {
OffsetDateTime offsetDateTime = getCurrentOffsetDateTime();
return offsetDateTime.with(TemporalAdjusters.lastDayOfMonth()).withHour(23).withMinute(59).withSecond(59).withNano(0);
}
}
(本文完 c-3-d e-a-20200302)

JSR310-新日期APIJSR310新日期API(完结篇)-生产实战的更多相关文章
- [转]JavaSE 8—新的时间和日期API
为什么我们需要一个新的时间日期API Java开发中一直存在一个问题,JDK提供的时间日期API一直对开发者没有提供良好的支持. 比如,已有的的类(如java.util.Date和SimpleDate ...
- 【转】Java 8新特性(四):新的时间和日期API
Java 8另一个新增的重要特性就是引入了新的时间和日期API,它们被包含在java.time包中.借助新的时间和日期API可以以更简洁的方法处理时间和日期. 在介绍本篇文章内容之前,我们先来讨论Ja ...
- Java 8新特性(四):新的时间和日期API
Java 8另一个新增的重要特性就是引入了新的时间和日期API,它们被包含在java.time包中.借助新的时间和日期API可以以更简洁的方法处理时间和日期. 在介绍本篇文章内容之前,我们先来讨论Ja ...
- 为什么不建议使用Date,而是使用Java8新的时间和日期API?
Java 8:新的时间和日期API 在Java 8之前,所有关于时间和日期的API都存在各种使用方面的缺陷,因此建议使用新的时间和日期API,分别从旧的时间和日期的API的缺点以及解决方法.Java ...
- 【java】JDK1.8时间日期库 新特性 所有java中时间Date的使用
除了lambda表达式,stream以及几个小的改进之外,Java 8还引入了一套全新的时间日期API,在本篇教程中我们将通过几个简单的任务示例来学习如何使用java 8的这套API.Java对日期, ...
- 第17课-数据库开发及ado.net 聚合函数,模糊查询like,通配符.空值处理.order by排序.分组group by-having.类型转换-cast,Convert.union all; Select 列 into 新表;字符串函数;日期函数
第17课-数据库开发及ado.net 聚合函数,模糊查询like,通配符.空值处理.order by排序.分组group by-having.类型转换-cast,Convert.union all; ...
- Android 新老两代 Camera API 大起底
https://blog.csdn.net/Byeweiyang/article/details/80515192 0.背景简介 最近有一部分相机相关的需求,专注于对拍摄的照片.视频的噪点.色温.明暗 ...
- [译]试用新的System.Text.Json API
译注 可能有的小伙伴已经知道了,在.NET Core 3.0中微软加入了对JSON的内置支持. 一直以来.NET开发者们已经习惯使用Json.NET这个强大的库来处理JSON. 那么.NET为什么要增 ...
- JDK 8之前日期和时间的API
JDK 8之前日期和时间的API(1) System类中的currentTimeMillis():返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差.称为时间戳. java.util ...
随机推荐
- Android 自定义dialog类
首先定制style样式 styles.xml 加入自定义样式 <style name="CustomLoadingDialog"> <item name=&quo ...
- SpringBoot项目后台对接微信支付开发——微信统一下单接口开发
开始没找到微信支付的sdk.自己根据官方给的接口文档纯手写,各种xml转JSON,JSON转xml,加密解密,签名....整个人都是崩溃的 开发的第三天,发现有官方的sdk.心情一下子豁然开朗,整个人 ...
- Fastjson主要接口和类库说明
2.主要的使用入口 Fastjson API入口类是com.alibaba.fastjson.JSON,常用的序列化操作都可以在JSON类上的静态方法直接完成. public static final ...
- The Queen's Super-circular Patio 求栏杆
Input The first line of input contains a single integer P, (1 ≤ P ≤ 1000), which is the number of da ...
- Python迭代器和关键字 global ,nonlocal
1.关键字 global : 可以修改全局变量 可以在局部作用域声明一个全局变量,剪切 : 此时局部作用域没有该变量,全局作用域中有 name = 1 def func(): global name ...
- 吴裕雄--天生自然python学习笔记:Python MongoDB
MongoDB 是目前最流行的 NoSQL 数据库之一,使用的数据类型 BSON(类似 JSON). PyMongo Python 要连接 MongoDB 需要 MongoDB 驱动,这里我们使用 P ...
- linux kill进程没有立刻停止
前些天在执行restart脚本的时候遇到了一个奇怪的问题:1.第一次执行进程不见了,启动失败2.第二次重启进程成功,但是在kill的时候提示进程不存在需要重启两次进程才能成功 查看日志文件:第一次重启 ...
- JVM组成与作用
class loader 类加载器:加载类文件到内存.Class loader只管加载,只要符合文件结构就加载,至于能否运行,它不负责,那是有Exectution Engine 负责的.exectio ...
- CAD安装未完成,某些产品无法安装的解决方法
CAD提示安装未完成,某些产品无法安装该怎样解决呢?,一些朋友在win7或者win10系统下安装CAD失败提示CAD安装未完成,某些产品无法安装,也有时候想重新安装CAD的时候会出现本电脑window ...
- 使用junit测试springMVC项目提示ServletContext找不到定义错误
原文链接:https://blog.csdn.net/liu_gan/article/details/78400627 @RunWith(SpringJUnit4ClassRunner.class) ...