Date/Time Printing Can Be Elegant Too

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

我把我在Stack Overflow上积累的相对较高的声望归功于我几年前发布的一个问题:如何在Java中打印ISO 8601日期?自那以后,这个问题收到了很多点赞和20多个答案,包括我的回答。说真的,为什么Java这样一个丰富的生态系统没有一个内置的、开箱即用的简单解决方案来完成这个基本任务?我认为这是因为Java SDK的设计者们既聪明到不会在Date类中创建一个print()方法,又不够聪明以一种优雅的方式为我们提供一个可扩展的类和接口集来解析和打印日期。

根据我所知,基本上有三种方法在JDK中分担解析和打印的责任:

第一种情况是当某个东西负责打印和解析,而对象只是一个数据持有者。有一个名为SimpleDateFormat的类,首先必须进行配置,设置正确的时区和格式化模式。然后它可以用于打印:

要解析回来,可以使用parse()方法:

这是一个DTO和实用类的经典组合。DTO是Date对象,而实用类是SimpleDateFormat。日期对象通过一些getter方法公开所有所需的数据属性,而实用类则打印日期。日期对象对这个过程没有影响。它实际上并不是一个对象,而只是一个数据容器。这根本不是面向对象的编程。

Java 8 引入了类 Instant,其中包含方法 toString(),该方法以 ISO-8601 格式返回时间。

要解析它,可以使用同一类Instant中的静态方法parse()

这种方法看起来更加面向对象,但问题在于无法以任何方式修改打印模式(例如,删除毫秒或完全更改格式)。此外,parse() 方法是静态的,这意味着没有多态性,我们无法改变解析逻辑。由于 Instant 是一个最终类而不是接口,我们也无法更改打印逻辑。

如果我们只需要 ISO 8601 日期/时间字符串,这个设计听起来还可以。但一旦我们决定以某种方式进行扩展,就会遇到麻烦。

在Java 8中还有DateTimeFormatter,它引入了处理日期/时间对象的第三种方式。要将日期打印到一个String中,我们需要创建一个”formatter”的实例,并将其传递给时间对象。

要解析回来,我们需要将formatter与要解析的文本一起发送给静态方法parse()

它们如何进行通信,LocalDateTimeDateTimeFormatter?时间对象是一个TemporalAccessor,具有一个get()方法,允许任何人提取其中的内容。换句话说,又是一个数据传输对象(DTO)。格式化程序仍然是一个实用类(甚至不是接口),它期望接收DTO,提取其中的内容并打印出来。

它们如何解析?方法parse()读取模板,并构建并返回另一个TemporalAccessor DTO。

封装方面呢?“这次不是这样,”JDK设计者说。

以下是我将如何设计它。首先,我会创建一个通用的不可变的“Template”类,具有如下接口:

它将会被使用如下方式:

这个模板内部根据封装的模式决定如何打印传入的数据。下面是Date如何打印自身的方式:

这是解析的工作方式(通常将代码放入构造函数中是一个不好的主意,但对于这个实验来说是可以的)。

假设我们想将时间打印为“13-е января 2019 года”(这是俄语)。我们要如何做到这一点?我们不创建一个新的Template,而是对现有的模板进行多次装饰。首先,我们创建一个我们已有的实例:

这将会打印出类似以下内容的内容:

Date不会将MMMM的值传递给它,所以它无法正确地替换文本。我们必须对它进行修饰:

现在,要从一个Date对象中获取一个俄罗斯日期,我们可以这样做:

假设我们想要在不同的时区打印日期。我们创建另一个修饰符,它拦截带有“HH”的调用,并减去(或加上)时差。

这段代码将打印出莫斯科(UTC+3)时间的俄文表示。

我们可以根据需要进行装饰,使Template变得足够强大。这种方法的优雅之处在于Date类与Template完全解耦,使它们都可以被替换和多态化。

也许有人会有兴趣按照这些原则创建一个面向Java的开源日期和时间打印与解析库?

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-17 at 16:09

sixnines availability badge   GitHub stars