将字符串转换为日期(CEST 工作正常,GMT+02:00 不工作)

Convert string to date (CEST works fine, GMT+02:00 doesn't work)

问题

为什么我的 Android 应用无法解析 String str1= "Tue Jun 20 15:56:29 CEST 2017"

我发现了一些类似的问题,但其中 none 对我有所帮助。

旁注

在我的项目中,我有一些 Java 应用程序在计算机上 运行 和一些 Android 应用程序。他们能够相互交流。

消息中有时间戳。但是,我的 Java 应用程序正在以 String str1= "Tue Jun 20 15:56:29 CEST 2017" 格式发送时间戳,而我的 Android 应用程序发送 String str2 = "Tue Jun 20 13:40:37 GMT+02:00 2017" 格式的时间戳。要保存包含时间的消息,我必须将传入时间解析为日期。

我的Android-App

在我的 Android 应用程序中,我无法正确解析 String str1= "Tue Jun 20 15:56:29 CEST 2017"

java.text.ParseException: Unparseable date: "Tue Jun 20 15:56:29 CEST 2017"

String str2 = "Tue Jun 20 13:40:37 GMT+02:00 2017"工作正常。

代码:

    String str1 = "Tue Jun 20 14:53:08 CEST 2017";
    SimpleDateFormat formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
    try {
        Date date = formatter.parse(str1);
    } catch (ParseException e) {
        e.printStackTrace();
    }
    // test
    String str2 = "Tue Jun 20 13:40:37 GMT+02:00 2017";
    formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
    try {
        Date date = formatter.parse(str2);
    } catch (ParseException e) {
        e.printStackTrace();
    }

我的Java-App

但是,我的 Java 应用程序可以正确解析这两个字符串。

代码:

    String str = "Tue Jun 20 14:53:08 CEST 2017";
    SimpleDateFormat formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
    try {
        Date date = formatter.parse(str);
    } catch (ParseException e) {
        e.printStackTrace();
    }
    // test
    str = "Tue Jun 20 13:40:37 GMT+02:00 2017";
    formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
    try {
        Date date = formatter.parse(str);
    } catch (ParseException e) {
        e.printStackTrace();
    }

ThreeTen 解决方案

字符串到 LocalDateTime

要转换传入的字符串,我使用以下代码:

    String time = "Mon Jun 26 15:42:51 GMT 2017";
    DateTimeFormatter gmtDateTimeFormatter = DateTimeFormatter.ofPattern("EE MMM dd HH:mm:ss 'GMT' yyyy", Locale.ENGLISH));
    LocalDateTime timestamp = LocalDateTime.parse(time, gmtDateTimeFormatter);

LocalDateTime 到 String

为了将我的 LocalDateTime 转换为字符串,我使用了这个:

    LocalDateTime timestamp = LocalDateTime.now();
    DateTimeFormatter gmtDateTimeFormatter = DateTimeFormatter.ofPattern("EE MMM dd HH:mm:ss 'GMT' yyyy", Locale.ENGLISH));
    String time = gmtDateTimeFormatter.format(timestamp); 

也许 Android 处理 zzzz 模式的方式不同(可能 Java 的实现比 Android 处理得更好,所以它 "guesses" 正确的时区,Android 没有)。我不知道。

无论如何,我可以建议您避免使用那些旧的 classes 吗?这些旧的 classes(DateCalendarSimpleDateFormat)有 lots of problems and design issues,它们正在被新的 APIs 取代。

如果您正在使用 Java 8,请考虑使用 new java.time API. It's easier, less bugged and less error-prone than the old APIs.

如果您使用 Java <= 7,您可以使用 ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, there's the ThreeTenABP (more on how to use it ).

下面的代码适用于两者。 唯一的区别是包名称(在 Java 8 中是 java.time,在 ThreeTen Backport(或 Android 的 ThreeTenABP)中是 org.threeten.bp),但是 classes 和方法 names 相同。

要解析这两种格式,您可以使用带有可选部分的 DateTimeFormatter。这是因为 CEST 是一个时区短名称,而 GMT+02:00 是一个 UTC 偏移量,所以如果你想用相同的格式化程序解析两者,你需要为每种格式使用一个可选部分。

另一个细节是 CETCEST 等短名称是 ambiguous and not standard. The new API uses IANA timezones names(始终采用 Continent/City 格式,例如 America/Sao_PauloEurope/Berlin).

因此,您需要选择一个适合您需要的时区。在下面的示例中,我刚刚选择了 CEST (Europe/Berlin) 中的时区,但您可以根据需要更改它 - 您可以使用 ZoneId.getAvailableZoneIds().[ 获取所有名称的列表。 =51=]

由于新的 API 没有解析 CEST(因为它的歧义),我需要创建一个带有首选时区的集合以便正确解析输入:

// when parsing, if finds ambiguous CET or CEST, it uses Berlin as prefered timezone
Set<ZoneId> set = new HashSet<>();
set.add(ZoneId.of("Europe/Berlin"));

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // your pattern (weekday, month, day, hour/minute/second)
    .appendPattern("EE MMM dd HH:mm:ss ")
    // optional timezone short name (like "CST" or "CEST")
    .optionalStart().appendZoneText(TextStyle.SHORT, set).optionalEnd()
    // optional GMT offset (like "GMT+02:00")
    .optionalStart().appendPattern("OOOO").optionalEnd()
    // year
    .appendPattern(" yyyy")
    // create formatter (using English locale to make sure it parses weekday and month names correctly)
    .toFormatter(Locale.US);

要解析 Tue Jun 20 14:53:08 CEST 2017,只需使用格式化程序:

ZonedDateTime z1 = ZonedDateTime.parse("Tue Jun 20 14:53:08 CEST 2017", fmt);
System.out.println(z1);

输出为:

2017-06-20T14:53:08+02:00[Europe/Berlin]

请注意,根据我们创建的集合,CEST 被映射到 Europe/Berlin

要解析Tue Jun 20 13:40:37 GMT+02:00 2017,我们可以使用相同的格式化程序。但是 GMT+02:00 可以在很多不同的地区,所以 API 不能将它映射到一个时区。要将其转换为正确的时区,我需要使用 withZoneSameInstant() 方法:

// parse with UTC offset
ZonedDateTime z2 = ZonedDateTime.parse("Tue Jun 20 13:40:37 GMT+02:00 2017", fmt)
    // convert to Berlin timezone
    .withZoneSameInstant(ZoneId.of("Europe/Berlin"));
System.out.println(z2);

输出为:

2017-06-20T13:40:37+02:00[Europe/Berlin]


PS: 第一种情况 (z1) 在 Java 8 中有效,但在 ThreeTen Backport 中它没有将时区设置为柏林。要修复它,只需调用 .withZoneSameInstant(ZoneId.of("Europe/Berlin")) 就像我们对 z2 所做的那样。


如果您仍然需要使用 java.util.Date,您可以转换为新的 API。

java.time中添加了新方法Date class:

// convert ZonedDateTime to Date
Date date = Date.from(z1.toInstant());

// convert back to ZonedDateTime (using Berlin timezone)
ZonedDateTime z = date.toInstant().atZone(ZoneId.of("Europe/Berlin")); 

在 ThreeTen 向后移植(和 Android)中,您可以使用 org.threeten.bp.DateTimeUtils class:

// convert ZonedDateTime to Date
Date date = DateTimeUtils.toDate(z1.toInstant());

// convert back to ZonedDateTime (using Berlin timezone)
ZonedDateTime z = DateTimeUtils.toInstant(date).atZone(ZoneId.of("Europe/Berlin"));

java.time

java.util 日期时间 API 及其格式 API、SimpleDateFormat 已过时且容易出错。建议完全停止使用它们并切换到 modern Date-Time API*.

此外,下面引用的是来自 home page of Joda-Time 的通知:

Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.

不要为时区使用固定文本:

不要像您所做的那样为时区使用固定文本(例如 'GMT'),因为该方法对于其他语言环境可能会失败。

解决方案使用 java.time,现代日期时间 API:

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        // Test
        System.out.println(parse("Tue Jun 20 14:53:08 CEST 2017"));
        System.out.println(parse("Tue Jun 20 13:40:37 GMT+02:00 2017"));
    }

    static ZonedDateTime parse(String strDateTime) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("E MMM d H:m:s z u", Locale.ENGLISH);
        return ZonedDateTime.parse(strDateTime, dtf);
    }
}

输出:

2017-06-20T14:53:08+02:00[Europe/Paris]
2017-06-20T13:40:37+02:00[GMT+02:00]

ONLINE DEMO

Trail: Date Time.

了解有关现代日期时间 API 的更多信息

* 无论出于何种原因,如果您必须坚持Java 6 或Java 7,您可以使用ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and