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](https://link.juejin.cn?target=https%3A%2F%2Fstackoverflow.com%2Fquestions%2F27454025%2Funable-to-obtain-localdatetime-from-temporalaccessor-when-parsing-localdatetime "https://stackoverflow.com/questions/27454025/unable-to-obtain-localdatetime-from-temporalaccessor-when-parsing-localdatetime") \[2\] [Java: UnsupportedTemporalTypeException when formatting Instant to String \| Stack Overflow - stackoverflow.com](https://link.juejin.cn?target=https%3A%2F%2Fstackoverflow.com%2Fquestions%2F25229124%2Funsupportedtemporaltypeexception-when-formatting-instant-to-string "https://stackoverflow.com/questions/25229124/unsupportedtemporaltypeexception-when-formatting-instant-to-string")

相关推荐
MSTcheng.1 小时前
【C++】C++异常
java·数据库·c++·异常
大模型玩家七七2 小时前
基于语义切分 vs 基于结构切分的实际差异
java·开发语言·数据库·安全·batch
寻星探路7 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
曹牧10 小时前
Spring Boot:如何测试Java Controller中的POST请求?
java·开发语言
爬山算法10 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
kfyty72511 小时前
集成 spring-ai 2.x 实践中遇到的一些问题及解决方案
java·人工智能·spring-ai
猫头虎11 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven
李少兄11 小时前
在 IntelliJ IDEA 中修改 Git 远程仓库地址
java·git·intellij-idea
忆~遂愿11 小时前
ops-cv 算子库深度解析:面向视觉任务的硬件优化与数据布局(NCHW/NHWC)策略
java·大数据·linux·人工智能