Java 8:如何设计一个 Instant 与 String 互转的工具类?

大家好,我是磊磊落落,目前我在技术上主要关注:Java、Golang、架构设计、云原生和自动化测试。欢迎来我的博客(leileiluoluo.com)获取我的最近更新!

本文首先将介绍在 Java 8 之前,传统的 DateString 相互转换的工具类是怎么实现的;接着再探索在 Java 8 新引入 Instant 后,如何实现 InstantString 的互转,以及新的工具类的实现。

1 传统日期转换工具类设计

在 Java 8 之前,我们常使用 Date 来表示日期与时间。设计 DateString 互转的工具类时,只要借助一下 SimpleDateFormat,即可轻松的实现。

示例代码如下:

java 复制代码
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtil {

    public static Date str2Date(String dateStr, String pattern) {
        try {
            return new SimpleDateFormat(pattern).parse(dateStr);
        } catch (ParseException e) {
            throw new RuntimeException("date parse failed");
        }
    }

    public static String date2Str(Date date, String pattern) {
        return new SimpleDateFormat(pattern).format(date);
    }

    public static void main(String[] args) {
        // string to date
        Date date = DateUtil.str2Date("2023-11-15", "yyyy-MM-dd");

        // date to string
        String str = DateUtil.date2Str(date, "yyyy/MM");
        System.out.println(str);
    }

}

可以看到,如上 DateUtil 工具类的 str2Datedate2Str 方法可以分别实现 StringDate,以及 DateString 的转换。

2 Java 8:Instant 与 String 转换工具类设计

Java 8 中新引入了一个 Instant 类来表示时间线上的一个点(瞬间)。如何设计一个 InstantString 互转的工具类呢?

先看一个错误示例,然后再看一下修正后的正确示例。

Java 8 中,需要借助 DateTimeFormatter 来实现 InstantString 的互转。

下面即是一个未考虑周全的错误示例。

2.1 错误示例

下面尝试封装一下 InstantString 互转的工具类,因其存在一些问题,所以起名 FatalInstantUtil。该工具类的 str2Instant 方法用于 StringInstant 的转换;instant2Str 方法用于 InstantString 的转换。

java 复制代码
// 错误示例
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

public class FatalInstantUtil {

    public static Instant str2Instant(String dateTimeStr, String pattern) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);

        return LocalDateTime.parse(dateTimeStr, formatter)
                .atZone(ZoneId.systemDefault())
                .toInstant();
    }

    public static String instant2Str(Instant instant, String pattern) {
        return DateTimeFormatter.ofPattern(pattern)
                .format(instant);
    }

    public static void main(String[] args) {
        // string to instant
        Instant instant = str2Instant("2023-11-15", "yyyy-MM-dd");
        System.out.println(instant);

        // instant to string
        String str = instant2Str(Instant.now(), "yyyy-MM");
        System.out.println(str);
    }

}

使用如上工具类的 str2Instant 方法进行 StringInstant 的转换时,如下写法都是可以正常执行的:

java 复制代码
str2Instant("2023-11-15 17:23:56", "yyyy-MM-dd HH:mm:ss");
str2Instant("2023-11-15 17:23", "yyyy-MM-dd HH:mm");
str2Instant("2023-11-15 17", "yyyy-MM-dd HH");

但如下写法会执行失败:

java 复制代码
str2Instant("2023-11-15", "yyyy-MM-dd");
str2Instant("2023-11", "yyyy-MM");
str2Instant("2023", "yyyy");

执行 str2Instant("2023-11-15", "yyyy-MM-dd"); 时的报错信息如下:

text 复制代码
Exception in thread "main" java.time.format.DateTimeParseException: Text '2023-11-15' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {},ISO resolved to 2023-11-15 of type java.time.format.Parsed
	at java.base/java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:2023)
	at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1958)
	at java.base/java.time.LocalDateTime.parse(LocalDateTime.java:494)
	at FatalInstantUtil.str2Instant(FatalInstantUtil.java:11)
	at FatalInstantUtil.main(FatalInstantUtil.java:23)
Caused by: java.time.DateTimeException: Unable to obtain LocalDateTime from TemporalAccessor: {},ISO resolved to 2023-11-15 of type java.time.format.Parsed
	at java.base/java.time.LocalDateTime.from(LocalDateTime.java:463)
	at java.base/java.time.format.Parsed.query(Parsed.java:241)
	at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1954)
	... 3 more
Caused by: java.time.DateTimeException: Unable to obtain LocalTime from TemporalAccessor: {},ISO resolved to 2023-11-15 of type java.time.format.Parsed
	at java.base/java.time.LocalTime.from(LocalTime.java:433)
	at java.base/java.time.LocalDateTime.from(LocalDateTime.java:459)
	... 5 more

这个异常信息说的不是很清楚,其实原因出在未给 DateTimeFormatter 设置月、日、时、分、秒的默认值;这样,如果这些值缺省时,DateTimeFormatterparse 的时候不知怎么处理这些值,就会抛出 DateTimeParseException 异常。

此外,使用如上工具类的 instant2Str 方法进行 InstantString 的转换时也会报错。

如,执行 instant2Str(Instant.now(), "yyyy-MM"); 时的报错信息如下:

text 复制代码
Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: YearOfEra
	at java.base/java.time.Instant.getLong(Instant.java:604)
	at java.base/java.time.format.DateTimePrintContext.getValue(DateTimePrintContext.java:308)
	at java.base/java.time.format.DateTimeFormatterBuilder$NumberPrinterParser.format(DateTimeFormatterBuilder.java:2763)
	at java.base/java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2402)
	at java.base/java.time.format.DateTimeFormatter.formatTo(DateTimeFormatter.java:1849)
	at java.base/java.time.format.DateTimeFormatter.format(DateTimeFormatter.java:1823)
	at FatalInstantUtil.instant2Str(FatalInstantUtil.java:18)
	at FatalInstantUtil.main(FatalInstantUtil.java:27)

这个异常信息说的也不是很清楚,其实原因出在未给 DateTimeFormatter 指定时区;这样其在 format 的时候不知道转换到哪个时区的格式就会抛出异常。

知道了异常出现的原因后,下面修正一下,看一下正确的示例。

2.2 正确示例

修正后的正确示例代码如下:

java 复制代码
// 正确示例
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;

public class InstantUtil {

    public static Instant str2Instant(String dateTimeStr, String pattern) {
        DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern(pattern)
                .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
                .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
                .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
                .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
                .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
                .toFormatter();

        return LocalDateTime.parse(dateTimeStr, formatter)
                .atZone(ZoneId.systemDefault())
                .toInstant();
    }

    public static String instant2Str(Instant instant, String pattern) {
        return DateTimeFormatter.ofPattern(pattern)
                .withZone(ZoneId.systemDefault())
                .format(instant);
    }

    public static void main(String[] args) {
        // string to instant
        Instant instant = str2Instant("2023-11-15", "yyyy-MM-dd");
        System.out.println(instant);

        // instant to string
        String str = instant2Str(Instant.now(), "yyyy-MM");
        System.out.println(str);
    }

}

如上修正后的代码中:str2Instant 方法,使用了 DateTimeFormatterBuilder 来构造 DateTimeFormatter,其使用 parseDefaulting 来指定了月、日、时、分、秒的默认值,这样这些值缺省时,会使用指定的默认值来填充,就不会抛异常了。

改造后的 str2Instant 方法,对于如下各种时间与格式的解析都没有问题了:

java 复制代码
str2Instant("2023-11-15 17:23:56.345", "yyyy-MM-dd HH:mm:ss.SSS");
str2Instant("2023-11-15 17:23:56", "yyyy-MM-dd HH:mm:ss");
str2Instant("2023-11-15 17:23", "yyyy-MM-dd HH:mm");
str2Instant("2023-11-15 17", "yyyy-MM-dd HH");
str2Instant("2023-11-15", "yyyy-MM-dd");
str2Instant("2023-11", "yyyy-MM");
str2Instant("2023", "yyyy");

如上修正后的 instant2Str 方法,为 DateTimeFormatter 指定了时区,这样 InstantString 转换也不会抛异常了。

至此,一个可用的 InstantString 互转的工具类就实现好了。

本文所涉及的全部代码已托管至本人 GitHub,欢迎关注或 Fork。

参考资料

1 Java: Unable to obtain LocalDateTime from TemporalAccessor when parsing LocalDateTime | Stack Overflow - stackoverflow.com

2 Java: UnsupportedTemporalTypeException when formatting Instant to String | Stack Overflow - stackoverflow.com

相关推荐
allnlei2 小时前
s6-overlay - 装在 Docker 容器里的轻量级管家
java·docker·容器
IT_Octopus7 小时前
IntelliJ 本地日志路径自定义:`-DLOG_PATH=./logs` 为什么总“不听话“
java·log4j·intellij-idea
长征coder8 小时前
【无标题】
java·性能优化
新时代牛马9 小时前
PCI与PCIe 硬件原理、配置空间/BAR 与 Linux 驱动完整篇:从 LTSSM、TLP 到 ECAM 与probe
java·linux·服务器
Bs_MoneyMagnet9 小时前
基于springboot+vue的医疗健康便民服务平台的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring
sunshine22 girl10 小时前
Java学习一 环境配置2 Idea的入门使用和maven配置,项目启动脚本配置
java·maven
有点。10 小时前
*C++哈夫曼树与哈夫曼编码
java·c++·servlet
CRMEB系统商城11 小时前
CRMEB标准版系统(Java)v3.1正式发布
java·spring·微信小程序·php·教育电商
写后端的胖头鱼11 小时前
【高频面试题】Java 基础类型 + 包装类 + String 互相转换
java·开发语言
weixin_4604435611 小时前
企业考试系统从.NET迁移到Java+Linux:数据库迁移、接口兼容与灰度切换实践
java·煤矿培训考试系统·煤矿在线考试系统·煤矿安全培训·煤矿考试系统·一人一档