【问题标题】:Convert string to date (CEST works fine, GMT+02:00 doesn't work)将字符串转换为日期(CEST 工作正常,GMT+02:00 不工作)
【发布时间】:2017-06-20 14:55:33
【问题描述】:

问题

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

我发现了一些类似的问题,但没有一个对我有帮助。

旁注

在我的项目中,我有一些在计算机上运行的 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 应用中,我无法正确解析 String str1= "Tue Jun 20 15:56:29 CEST 2017"

java.text.ParseException:无法解析的日期:“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 应用程序

但是,我的 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();
    }

三十解决方案

本地日期时间字符串

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

    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 到字符串

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

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

【问题讨论】:

  • 仅供参考,麻烦的旧日期时间类,如 java.util.Datejava.util.Calendarjava.text.SimpleTextFormat 现在是 legacy,被 java.time 类取代。见Tutorial by Oracle。对于 Android,请参阅 ThreeTen-Backport 项目。
  • 如果你能得到一个不包含四个字母时区缩写的字符串,那将是最好的。三个字母和四个字母的缩写通常是模棱两可的,有时 - 与 CEST 一样 - 只有半个时区。对于 CEST,如果日期是在未使用 CEST 的冬季,我不知道会发生什么结果。

标签: java android datetime datetime-parsing


【解决方案1】:

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

无论如何,我可以建议您避免使用那些旧课程吗?这些旧类(DateCalendarSimpleDateFormat)有 lots of problemsdesign issues,它们正在被新的 API 取代。

如果您使用的是 Java 8,请考虑使用 new java.time API。更简单,less bugged and less error-prone than the old APIs

如果您使用的是 Java ,则可以使用 ThreeTen Backport,这是 Java 8 新日期/时间类的一个很好的向后移植。对于Android,还有ThreeTenABP(更多关于如何使用它here)。

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

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

另一个细节是像CETCEST 这样的短名称是ambiguous and not standard。新 API 使用 IANA timezones names(始终采用 Continent/City 格式,如 America/Sao_PauloEurope/Berlin)。

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

由于新的 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[欧洲/柏林]

请注意,根据我们创建的集合,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[欧洲/柏林]


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


如果您仍需要使用 java.util.Date,您可以在新 API 之间进行转换。

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

// 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 backport(和 Android)中,您可以使用 org.threeten.bp.DateTimeUtils 类:

// 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"));

【讨论】:

  • 感谢您的帮助。到目前为止,我在我的 Android-App ThreeTenABP 和我的 Java-App ThreeTen 中实现了。现在还有小问题。
  • Android-App 用点打印日期,Java-App 不打印。但是,我想没有简单的解决方案。我必须使用正则表达式并构建自己的字符串。
  • 您是指秒后的点吗?在我的示例中,我使用日期对象调用println(它调用toString 并打印秒的分数)。如果要更改输出格式,请使用DateTimeFormatter
  • 当天后面没有出现圆点。但仅在 Android-App 中。 (查看我编辑的问题 -> 新问题)
  • 您正在创建一个没有语言环境的格式化程序,因此它使用 JVM 的默认值。尝试改用DateTimeFormatter.ofPattern("EE MMM dd HH:mm:ss 'GMT' yyyy", Locale.ENGLISH)(或Locale.US,可能两者都可以)。
【解决方案2】:

java.time

java.util 日期时间 API 及其格式化 API SimpleDateFormat 已过时且容易出错。建议完全停止使用,改用modern Date-Time API*

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

请注意,从 Java SE 8 开始,用户被要求迁移到 java.time (JSR-310) - JDK 的核心部分,它取代了这个项目。

时区不要使用固定文本:

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

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

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,它将大部分 java.time 功能向后移植到 Java 6 和 7 . 如果您正在为一个 Android 项目工作并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaringHow to use ThreeTenABP in Android Project

【讨论】: