Jsr310 new date API (conclusion) – production practice

premise

The previous five articles have basically introduced the date and time API commonly used by jsr-310 and some tool classes. This blog mainly talks about the author's experience in using jsr-310 date and time API in production practice.

Series of articles:

::: Info inadvertently, jdk8 has been released for more than 6 years. If you still use the old date and time API, you can take some time to get familiar with the date and time API of jsr-310.::

Simulation scenario

The following will introduce the specific API selection in combination with the simulation scenario. Since offsetdatetime can basically meet most scenarios, offsetdatetime is selected as an example.

Scenario 1: convert string input to date time object

Generally, in the form submission of web application or the content submitted by reuqest body, it is necessary to convert the date and time in string form into the corresponding date and time object. In most cases, web applications will use spring MVC, and the message converter of spring MVC will use objectmapper (Jackson) for deserialization when processing application / JSON type request content. Here, org.springframework.boot: spring boot starter Web: 2.2.5.release is introduced for a demonstration.

After introducing the latest version of spring boot starter web, the built-in Jackson has introduced two dependencies related to jsr-310. Springboot introduces the loading of objectmapper, loads javatimemodule and jdk8module through the builder method in jackson2objectmapperbuilder, and realizes the support for jsr-310 features. It should be noted that datetimeformatter, a formatter related to date and time in javatimemodule, uses built-in implementation. For example, datetimeformatter is used for date and time ISO_ OFFSET_ DATE_ Time, unable to parse string in yyyy MM DD HH: mm: SS mode. For example:

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));
}

The request is as follows:

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"} 

If you insist on selecting the string of yyyy MM DD HH: mm: SS mode, the type of the attribute can only be localdatetime, and the corresponding serializer and deserializer should be rewritten to overwrite the original implementation in javatimemodule. Refer to the previous article.

Scenario 2: query data within two date and time ranges

In the system that the author is responsible for, there are often scheduled scheduling scenarios. For example, I have to run a scheduled task at 1 a.m. every day, query the business data of T-1 day or last week, and update it to the corresponding business statistics table, so that the colleagues operating the next morning can view the report data. Querying the data on T-1 is actually querying the data from 00:00:00 to 23:59:59 on T-1. Here is an example to calculate the total amount of all orders on day 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);
    }
}

The above is only pseudo code and cannot be executed directly. It uses a page turning design based on date, time and ID, which can reduce IO while ensuring efficiency. It is often used to query more scheduled tasks or data migration.

Scenario 3: calculate the difference between two dates and times

It is also a common scenario to calculate the difference between two dates and times. The scenario I have encountered is that the operation needs to export a batch of user data, mainly including user ID, desensitization information, user registration date and time, and the number of days from the registration date and time to the current date.

The pseudo code of the design is as follows:

@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;
}

With duration, you can easily calculate the difference between two dates and times, and easily convert it to different time measurement units.

Scenario 4: calculate the date of special holidays

Using the date time calibrator temporaladjuster, it is very convenient to calculate that YY day of XX month is a festival in the form of ZZ Festival. For example, the second Sunday in May is mother's day and the third Sunday in June is father's day.

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.withMonth(6).with(TemporalAdjusters.dayOfWeekInMonth(3,DayOfWeek.SUNDAY)).toLocalDate().toString()));
        time = time.plusYears(1);
        System.out.println(String.format("%d年母亲节是:%s",DayOfWeek.SUNDAY)).toLocalDate().toString()));
    }
}

// 输出结果
2020年母亲节是:2020-05-10
2020年父亲节是:2020-06-21
2021年母亲节是:2021-05-09
2021年父亲节是:2021-06-20

Some scheduled scheduling or reminder message sending needs to be triggered at such specific date and time, so the specific date can be calculated relatively simply through temporary adjuster.

Summary

There is so much about the date and time API of jsr-310. The author has been working on data recently, but will certainly continue to deal with jsr-310.

appendix

A tool class offsetdatetimeutils is pasted here:

@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)

The official account of Technology (Throwable Digest), which is not regularly pushed to the original technical article (never copied or copied):

Entertainment official account ("sand sculpture"), select interesting sand sculptures, videos and videos, push them to relieve life and work stress.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>