java - 将字符串转换为日期(CEST 工作正常,GMT+02 :00 doesn't work)

标签 java android datetime datetime-parsing

问题

为什么我的 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: 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 应用

但是,我的 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 到字符串

为了将我的 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 更好地处理它,因此它以 Android 不会的方式“猜测”正确的时区)。我不知道。

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

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

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

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

要解析这两种格式,您可以使用带有可选部分的 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[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 类添加了新方法:

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

关于java - 将字符串转换为日期(CEST 工作正常,GMT+02 :00 doesn't work),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44656519/

相关文章:

java - 使用 Stream API 编码任务的更有效解决方案?

Android ListView 视频列表

android - 导航栏中的项目未显示

python - Pandas 按日期时间分组

MySQL 插入到 DATETIME:使用 ISO::8601 格式安全吗?

java - 同一数据的多个轴

java - 从命中/命中迁移到 TopDocs/TopDocCollector

android - HTML5 Android Phonegap Web 应用离线缓存 list

c# - Convert.ToDateTime ,任何描述月份和年份字段 C# 的方法?

java - 计数元素出现在链表Java中的次数