当 SimpleDateFormat 也出现在 String 中时忽略 TimeZone

SimpleDateFormat ignoring TimeZone when also present in String

Java 的 SimpleDateFormat allows you to specify a TimeZone to be used when parsing a String to a Date.

当字符串不包含时区时,这会像您预期的那样工作,但当存在时区时,它似乎什么都不做。

该文档似乎也没有真正解释如何使用 TimeZone。

示例代码:

public class DateFormatTest {
    public static void main(final String[] args) throws ParseException {
         testBoth("HH:mm", "13:40");
         testBoth("HH:mm z", "13:40 UTC");
    }

    private static void testBoth(final String dateFormatString, final String dateString) throws ParseException {
        // First, work with the "raw" date format
        DateFormat dateFormat = new SimpleDateFormat(dateFormatString);
        parse(dateFormat, dateString);

        // Now, set the timezone to something else and try again
        dateFormat = new SimpleDateFormat(dateFormatString);
        dateFormat.setTimeZone(TimeZone.getTimeZone("PST"));
        parse(dateFormat, dateString);
    }

    private static void parse(final DateFormat dateFormat, final String dateString) throws ParseException {
        System.out.println(MessageFormat.format("Parsed \"{0}\" with timezone {1} to {2}", dateString,
        dateFormat.getTimeZone().getDisplayName(), dateFormat.parse(dateString)));
    }
}

示例输出:

Parsed "13:40" with timezone Greenwich Mean Time to 01/01/70 13:40
Parsed "13:40" with timezone Pacific Standard Time to 01/01/70 22:40
Parsed "13:40 UTC" with timezone Greenwich Mean Time to 01/01/70 14:40
Parsed "13:40 UTC" with timezone Pacific Standard Time to 01/01/70 14:40

请注意第一个示例的日期如何更改 - 但对于第二个示例,日期没有更改。

类型错误

您使用了错误的数据类型,试图将 time-of-day 值放入包含 time-of-day 和日期以及 offset-from-UTC(为零)的类型中。 Square peg, round hole.

此外,java.util.Date class 的设计和实现非常糟糕。它在多年前被现代的 java.time classes 取代,并采用了 JSR 310。

Time-of-day: LocalTime

"13:40"

简单解析为 LocalTime 对象。

LocalTime lt = LocalTime.parse( "13:40" ) ;

如果要结合日期和时区来确定时刻,请应用 LocalDateZoneId 生成 ZonedDateTime 对象。

ZoneId z = ZoneId.of( "America/Los_Angeles" ) ;
LocalDate today = LocalDate.now( z ) ;
ZonedDateTime zdt = ZonedDateTime.of( today , lt , z ) ;

要以 UTC 格式查看同一时刻,请提取 Instant

Instant instant = zdt.toInstant() ; 

Time-of-day 与 offset-from-UTC: OffsetTime

"13:40 UTC"

time-of-day 和 time zone or offset-from-UTC 实际上没有意义。没有日期,就没有有意义的方式来思考与特定时区相关的时间。没有人能够向我解释一个例子,说明这在逻辑上是如何有意义的。我听到的每一次争论实际上都涉及一个隐含的日期。

尽管如此,SQL 标准委员会明智地决定定义一个 TIME WITH TIME ZONE 数据类型。因此,为了支持这一点,java.time classes 包含匹配的 class、OffsetTime

不幸的是,我找不到可以解析您输入末尾的 SPACE 和 UTC 的格式化模式。因此,作为一种解决方法,我建议将这些字符替换为单个 Z 字符。所以 "13:40 UTC" 变成 "13:40Z"Z 表示 UTC,发音为“Zulu”。默认情况下处理此格式,因此无需指定格式模式。

String input = "13:40 UTC".replace( " UTC" , "Z" ) ;  // "13:40 UTC" becomes "13:40Z".
OffsetTime ot = OffsetTime.parse( input ) ;

在 2019 年,没有人应该关心 SimpleDateFormatTimeZone classes 的行为方式,因为我们应该放弃使用那些 classes 很多几年前。 Basil Bourque 已经给了你应该想要的答案。这个答案会尽量满足你的好奇心,但请不要用它来让 SimpleDateFormat 表现出来。使用 class.

而不是 会更好

我假设您的 JVM 时区是 Europe/London(我们会看到这很重要)。当我以这种方式设置我的时区并将我的区域设置为英国时,我可以准确地重现您的结果。

Parsed "13:40" with timezone Greenwich Mean Time to 01/01/1970, 13:40

在您的默认时区中解析 13:40 会在您的默认时区中得到 13:40,这不足为奇。由于英国在 1970 年冬天的 UTC 偏移量为 +01:00,因此此时间与 12:40 UTC 相同(未指定日期时,SimpleDateFormat 使用默认值 1970 年 1 月 1 日) .当输出显示 Greenwich Mean Time 时,这是一个错误。

Parsed "13:40" with timezone Pacific Standard Time to 01/01/1970, 22:40

1970 年美国西海岸比 UTC 晚 8 小时,因此比英国晚 9 小时。因此,当您告诉 SimpleDateFormat 假设 13:40 处于 America/Los_Angeles 时区时,它会解析为 13:40 PST 时间,与 21:40 UTC 或22:40 英国时间(America/Los_Angeles 是 TimeZone 解释 PST 的方式,但它已被弃用,不要依赖它。)MessageFormat 使用您的默认时区打印时间,因此 22:40 被打印出来。

Parsed "13:40 UTC" with timezone Greenwich Mean Time to 01/01/1970, 14:40

由于(如前所述)伦敦此时的偏移量为 +01:00,并且由于 MessageFormat 使用您的默认时区,因此 14:40 是正确且预期的输出。 dateFormat.parse(dateString) 解析为 Date,另一个设计糟糕且过时的 class。 Date 是一个时间点,不能保存 UTC 偏移量(与 Basil Bourque 正确使用的 OffsetTime class 相反)。您的观察是正确的:SimpleDateFormat 使用字符串中的 UTC 将 13:40 解释为一个时间点(不是其时区设置)。 MessageFormat 无法知道时间早先是从 13:40 UTC 解析的。

Parsed "13:40 UTC" with timezone Pacific Standard Time to 01/01/1970, 14:40

由于SimpleDateFormat使用字符串中的UTC将13:40解释为一个时间点,所以我们得到与上面相同的时间。