JSR310新日期API(完结篇)-生产实战

简介: 前面通过五篇文章基本介绍完JSR-310常用的日期时间API以及一些工具类,这篇博文主要说说笔者在生产实战中使用JSR-310日期时间API的一些经验。

前提



前面通过五篇文章基本介绍完JSR-310常用的日期时间API以及一些工具类,这篇博文主要说说笔者在生产实战中使用JSR-310日期时间API的一些经验。


系列文章:



::: info 不经意间,JDK8发布已经超过6年了,如果还在用旧的日期时间API,可以抽点时间熟悉一下JSR-310的日期时间API。 :::


仿真场景



下面会结合一下仿真场景介绍具体的API选取,由于OffsetDateTime基本能满足大部分场景,因此挑选OffsetDateTime进行举例。


场景一:字符串输入转换为日期时间对象


一般在Web应用的表单提交或者Reuqest Body提交的内容中,需要把字符串形式的日期时间转换为对应的日期时间对象。Web应用多数情况下会使用SpringMVC,而SpringMVC的消息转换器在处理application/json类型的请求内容的时候会使用ObjectMapperJackson)进行反序列化。这里引入org.springframework.boot:spring-boot-starter-web:2.2.5.RELEASE做一个演示。


微信截图_20220512211540.png


引入spring-boot-starter-web的最新版本之后,内置的Jackson已经引入了JSR-310相关的两个依赖。SpringBoot中引入在装载ObjectMapper通过Jackson2ObjectMapperBuilder中的建造器方法加载了JavaTimeModuleJdk8Module,实现了对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-100:00:0023: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);
    }
}
复制代码


上面的只是伪代码(而且例子比较不合理,其实一个SUM就可以搞定),不能直接执行,使用的是基于日期时间和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);
    }
}


相关文章
|
1月前
|
存储 供应链 监控
1688商品数据实战:API搜索接口开发与供应链分析应用
本文详细介绍了如何通过1688开放API实现商品数据的获取与应用,涵盖接入准备、签名流程、数据解析存储及商业化场景。开发者可完成智能选品、价格监控和供应商评级等功能,同时提供代码示例与问题解决方案,确保法律合规与数据安全。适合企业开发者快速构建供应链管理系统。
|
9天前
|
存储 人工智能 Java
Spring AI与DeepSeek实战四:系统API调用
在AI应用开发中,工具调用是增强大模型能力的核心技术,通过让模型与外部API或工具交互,可实现实时信息检索(如天气查询、新闻获取)、系统操作(如创建任务、发送邮件)等功能;本文结合Spring AI与大模型,演示如何通过Tool Calling实现系统API调用,同时处理多轮对话中的会话记忆。
217 57
|
1天前
|
JSON API 数据格式
一文读懂天猫商品详情 API 接口:功能、调用与实战攻略
天猫商品详情API为电商从业者、开发者和数据分析人员提供高效的商品数据获取途径。通过输入商品ID,可获取商品基本信息(名称、品牌等)、价格信息(售价、促销价等)、库存状态、商品描述及图片链接等详细内容。本文还提供了Python调用示例,包含签名生成、参数构建与请求发送等功能,帮助用户快速集成API,满足定价优化、市场分析等需求。使用时需替换示例中的AppKey与商品ID,并遵守平台规范。
50 16
|
1天前
|
数据挖掘 API 开发者
深度解析!淘宝商品详情 API 接口的高效调用与实战应用
淘宝商品详情API为开发者提供高效获取商品信息的途径,支持名称、价格、销量等详细数据的提取。接口通过GET/POST请求方式调用,需携带商品ID与授权信息(如AppKey)。其特点包括数据全面、实时性强及安全性高,满足电商应用、数据分析等需求。本文还提供了Python调用示例,涵盖签名生成、参数构建及请求发送全流程,助力开发者快速集成淘宝商品数据至自身系统中。
|
1月前
|
存储 编解码 资源调度
鸿蒙相机开发实战:从设备适配到性能调优 —— 我的 ArkTS 录像功能落地手记(API 15)
本文分享鸿蒙相机开发经验,从环境准备到核心逻辑实现,涵盖权限声明、模块导入、Surface关联与分辨率匹配,再到录制控制及设备适配法则。通过实战案例解析,如旋转补偿、动态帧率调节和编解码优化,帮助开发者掌握功能实现、设备适配与体验设计三大要点,减少开发坑点。适合鸿蒙新手及希望深化硬件交互能力的工程师参考收藏。
92 2
|
1月前
|
机器学习/深度学习 设计模式 API
Python 高级编程与实战:构建 RESTful API
本文深入探讨了使用 Python 构建 RESTful API 的方法,涵盖 Flask、Django REST Framework 和 FastAPI 三个主流框架。通过实战项目示例,详细讲解了如何处理 GET、POST 请求,并返回相应数据。学习这些技术将帮助你掌握构建高效、可靠的 Web API。
|
1月前
|
存储 监控 API
1688平台API接口实战:Python实现店铺全量商品数据抓取
本文介绍如何使用Python通过1688开放平台的API接口自动化抓取店铺所有商品数据。首先,开发者需在1688开放平台完成注册并获取App Key和App Secret,申请“商品信息查询”权限。接着,利用`alibaba.trade.product.search4trade`接口,构建请求参数、生成MD5签名,并通过分页机制获取全量商品数据。文中详细解析了响应结构、存储优化及常见问题处理方法,还提供了竞品监控、库存预警等应用场景示例和完整代码。
|
1月前
|
机器学习/深度学习 开发框架 API
Python 高级编程与实战:深入理解 Web 开发与 API 设计
在前几篇文章中,我们探讨了 Python 的基础语法、面向对象编程、函数式编程、元编程、性能优化、调试技巧以及数据科学和机器学习。本文将深入探讨 Python 在 Web 开发和 API 设计中的应用,并通过实战项目帮助你掌握这些技术。
|
1月前
|
缓存 监控 搜索推荐
【实战解析】smallredbook.item_get_video API:小红书视频数据获取与电商应用指南
本文介绍小红书官方API——`smallredbook.item_get_video`的功能与使用方法。该接口可获取笔记视频详情,包括无水印直链、封面图、时长、文本描述、标签及互动数据等,并支持电商场景分析。调用需提供`key`、`secret`和`num_iid`参数,返回字段涵盖视频链接、标题、标签及用户信息等。同时,文章提供了电商实战技巧,如竞品监控与个性化推荐,并列出合规注意事项及替代方案对比。最后解答了常见问题,如笔记ID获取与视频链接时效性等。
|
1月前
|
存储 前端开发 安全
如何在自己的网站接入API接口获取数据?分步指南与实战示例
将第三方API(如微店API)接入网站是扩展功能和获取实时数据的关键。流程包括注册开发者账号、申请API权限、设置认证机制(OAuth 2.0或AppKey签名)、调用API实现前后端协作、处理数据与错误、优化安全性能,并解决常见问题。确保遵循最佳实践,保障系统稳定与安全。通过这些步骤,开发者可高效整合数据,提升应用功能。

热门文章

最新文章