DateTimeFormatter 与 SimpleDateFormat 不兼容。

10
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;


public class Test001 {

    public static void main(String[] args) {
        Date dt = new Date();
        LocalDateTime localDateTime = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS zzz").withZone(ZoneId.of("America/New_York"));
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS zzz");

        System.out.println(formatter.format(localDateTime));
        System.out.println(sdf.format(dt));
    }

}

输出

2016-04-19 11:25:34.917 ET
2016-04-19 11:25:34.637 EDT

我的笔记本电脑时区是"America/New York"(即美国/加拿大东部时间)。

我想知道如何使用DateTimeFormatter来获取"EDT"而不是"ET"。
我也想知道为什么DateTimeFormatter在解析/格式化模式方面与SimpleDataFormat不兼容(正如这个例子所展示的那样)。


@Mr.Polywhirl 好的,但是还有相当多的遗留代码存在。而且我们可能想要保留旧格式。 - peter.petrov
1
从技术上讲,“America/New York”不是EDT,而是ET。目前它也是EDT,但在冬季它将变成EST。因此它们显然不等同。 - assylias
1
@assylias 不是针对你,但我并不关心它的技术细节。我关心的是,我有大量使用Java 6、7编写的代码生成EST/EDT,现在我不知道如何使用Java 8的java.time生成相同的结果。我原以为java.time会与旧的方式兼容(至少在格式和解析方面)。 - peter.petrov
1
@peter.petrov 我明白 - 我的意思是EDT(或EST)并不是真正的时区 - 这就是为什么它们不可用的原因。所以你可能要坚持使用日期或手动更改时区(例如检查dst是否开启)。不过,也许有其他人有解决方案... - assylias
显示剩余3条评论
3个回答

10
您看到的“ET”格式在 Unicode Locale Data Markup Language (LDML)中被称为“通用非位置”格式。而您所需的“EDT”格式被称为“特定非位置”格式。 DateTimeFormatterBuilder 中的相关源代码检查日期时间对象是否可以提供 ChronoField.INSTANT_SECONDS。如果可以,则使用“特定非位置”格式,否则使用“通用非位置”格式。

由于您正在格式化一个不支持访问ChronoField.INSTANT_SECONDSLocalDateTime,因此您得到了“通用非位置”格式。为了获得所需的输出,请改用ZonedDateTimeOffsetDateTime。(需要瞬时时间来确定是夏令时还是冬令时。)

请注意,SimpleDateFormatDateTimeFormatter确实存在差异,不能假定它们的模式相同。决定将DateTimeFormatter与LDML规范重新同步,这将在未来带来好处。

这个答案提供了一个解决方案和解释,但是我必须指出JDK代码在这里过于严格。由于您提供了LocalDateTimeZoneId,代码应该能够做得更好,并动态确定ChronoField.INSTANT_SECONDS,从而使用“特定非位置”格式。因此,我认为这里存在一个边缘情况JDK问题


谢谢。根据您的回答,我会仔细查看。 - peter.petrov
如果您仔细研究LDML规范,您会发现模式符号“z”旨在表示特定的非位置格式,而不是通用变体。相反,LDML希望使用符号“v”表示通用非位置格式。显然,包括Java-8在内的库都不支持“v”。如果Java-8应该紧密遵循LDML,则“z”不应支持通用变体。因此,像LocalDateTime这样的本地类型与模式符号“z”的组合最好被拒绝。 - Meno Hochschild
当然,‘z’确实只是“特定”的格式。但LDML是基于仅使用java.util.Date/Instant/ZonedDateTime类型对象的概念编写的。在JSR-310中,我们有更多类型,LDML没有考虑到,比如LocalDateTime。JSR-310也没有声称完全匹配LDML-它是用作指南。因此,在数据不足时将规范泛化为使用“通用”形式是完全适当的。 - JodaStephen
我完全赞同你对LDML的分析,即集中在类似即时类型的内容,但我认为你的结论不是唯一的选择。即使是在本地类型(如LocalDateTime)的上下文中,如果用户只理解“z”为“特定的非位置格式”,那么这将更加清晰明了。为什么不支持符号“v”(=与LDML重新同步),而是混淆“z”的行为(泛化变更LMDL行为)?至少解释你的推理是好的。顺便说一句,我还没有看到任何关于“通用非位置格式”的API文档。 - Meno Hochschild

7

您不应该使用 LocalDateTime 进行操作。与 Date 几乎等同的是 Instant。您需要将 Instant 格式化为您所在时区的格式。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS zzz").withZone(ZoneId.of("America/New_York"));
Instant now = Instant.now();
System.out.println(formatter.format(now));

打印的功能

2016-04-19 12:07:57.684 EDT

或者,您可以在 DateTimeFormatter 中使用未设置 ZoneId ZonedDateTime

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS zzz");

ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/New_York")))
System.out.println(formatter.format(now));

它也会打印

2016-04-19 12:11:01.667 EDT

我没有这一切工作的文档,但要点是这些暂时的对象(您要格式化的日期对象)具有偏移量,其具有区域规则,可以是夏令时或非夏令时。格式化程序在其打印中使用它。 LocalDateTime 没有偏移量或时区。您的格式化程序覆盖了它,但偏移量不是夏令时。

1
你说得很对,java.util.Date 主要映射到 java.time.Instant。因此,如果 OP 抱怨两个格式引擎之间的不兼容性,他至少应该关心使用正确的等效类型来代替 java.util.Date。否则,在这种情况下,这种不兼容性声明就不太公平(撇开其他细节上的不兼容性,例如不同的模式符号)。 - Meno Hochschild

-2

最直接的方法(可能也会导致夏季/冬季时间方面的一些问题)如下:

    public static void main(String[] args) {
        Date dt = new Date();
        LocalDateTime localDateTime = LocalDateTime.now();

        String dateFormat;
        if (Calendar.getInstance().get(Calendar.DST_OFFSET) != 0) {
            dateFormat = "yyyy-MM-dd HH:mm:ss.SSS 'EDT'";
        } else {
            dateFormat = "yyyy-MM-dd HH:mm:ss.SSS 'EST'";
        }
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat).withZone(ZoneId.of("America/New_York"));
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);

        System.out.println(formatter.format(localDateTime));
        System.out.println(sdf.format(dt));
    }

所以当夏令时发生时,可怜的开发人员将会在半夜被叫起来即兴更改代码? - Tunaki
优化答案,使其考虑夏令时的影响。 - uniknow
1
@Tunaki 不,Calendar.getInstance()使用默认时区,考虑夏令时。因此,这个答案无疑非常笨拙和有限(对我来说不是很直截了当),但并不完全错误(尽管仅适用于纽约地区和英语环境)。我们可以说它是不够充分的。 - Meno Hochschild

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接