【问题标题】:Android converting date time parse error (even tried joda time)Android转换日期时间解析错误(甚至尝试过joda时间)
【发布时间】:2017-11-13 05:05:46
【问题描述】:

我正在解析许多新闻提要,每个项目的 pubDate 都遵循相同的格式:

2017 年 6 月 11 日星期日 18:18:23 +0000

不幸的是,一个提要没有:

2017 年 6 月 10 日星期六 12:49:45 EST

我尝试使用 androids java date 和 SimpleDateFormat 解析日期,但没有成功:

try {
    Calendar cal = Calendar.getInstance();
    TimeZone tz = cal.getTimeZone();
    SimpleDateFormat readDate = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z");
    readDate.setTimeZone(TimeZone.getTimeZone("UTC"));
    Date date = readDate.parse(rssDateTime);
    SimpleDateFormat writeDate = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z");
    writeDate.setTimeZone(tz);
    parsedDate = writeDate.format(date);
} catch (ParseException e) {
    e.printStackTrace();
}

哪个抛出和错误:

java.text.ParseException:无法解析的日期:“星期六,2017 年 6 月 3 日 19:53:09 EST”(偏移量 26)

我也尝试过使用 joda time 来做到这一点:

DateTime dtUTC = null;
DateTimeZone timezone = DateTimeZone.getDefault();
DateTimeFormatter formatDT = DateTimeFormat.forPattern("EEE, d MMM yyyy HH:mm:ss Z");
DateTime dtRssDateTime = formatDT.parseDateTime(rssDateTime);
DateTime now = new DateTime();
DateTime nowUTC = new LocalDateTime(now).toDateTime(DateTimeZone.UTC);

long instant = now.getMillis();
long instantUTC = nowUTC.getMillis();
long offset = instantUTC - instant;
dtUTC = dtRssDateTime.withZoneRetainFields(timezone);
dtUTC = dtUTC.minusMillis((int) offset);
String returnTimeDate = "";
returnTimeDate = dtUTC.toString(formatDT);

这会引发错误:

原因:java.lang.IllegalArgumentException:无效格式:“Sat, 10 Jun 2017 12:49:45 EST”在“EST”格式错误

有人遇到过这种情况吗?

【问题讨论】:

  • 如果您对外部库感到满意,Joda-Time 可能不是最糟糕的选择,但您是否知道 Joda-Time 已进一步发展为现代 Java 数据和时间 API ? Joda-Time 的人正式推荐迁移,该 API 可在 ThreeTenABP 库中用于 Android。
  • @OleV.V.你说得对,我昨天只是没有时间写新的日期时间 API。无论如何,我已经更新了下面的my answer,包括一个使用新 API 的示例。谢谢!

标签: java jodatime datetime-parsing android-jodatime


【解决方案1】:

首先,如果您要开始一个新项目,我建议您使用新的日期时间 API 而不是 joda-time(更多内容见下文)。无论如何,这是两者的解决方案。


乔达时间

问题在于模式Z是偏移量(格式如+0000-0100),但字符串EST是时区短名称,由模式z解析(更多详情请查看jodatime javadoc)。

因此,您需要一个带有可选部分的模式,它可以同时接收一个或另一个。您可以使用 org.joda.time.format.DateTimeFormatterBuilder 类来做到这一点。

首先,您需要创建两个org.joda.time.format.DateTimeParser 实例(一个用于Z,另一个用于z),并将它们添加为可选解析器。然后使用下面的代码创建org.joda.time.format.DateTimeFormatter。请注意,我还使用了java.util.Locale,只是为了确保它正确解析工作日和月份名称(这样您就不必依赖默认语言环境,这可能因每个系统/机器而异):

// offset parser (for "+0000")
DateTimeParser offsetParser = new DateTimeFormatterBuilder().appendPattern("Z").toParser();
// timezone name parser (for "EST")
DateTimeParser zoneNameParser = new DateTimeFormatterBuilder().appendPattern("z").toParser();
// formatter for both patterns
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // append common pattern
    .appendPattern("EEE, d MMM yyyy HH:mm:ss ")
    // optional offset
    .appendOptional(offsetParser)
    // optional timezone name
    .appendOptional(zoneNameParser)
    // create formatter (use English Locale to make sure it parses weekdays and month names independent of JVM config)
    .toFormatter().withLocale(Locale.ENGLISH)
    // make sure the offset "+0000" is parsed
    .withOffsetParsed();

// parse the strings
DateTime est = fmt.parseDateTime("Sat, 10 Jun 2017 12:49:45 EST");
DateTime utc = fmt.parseDateTime("Sun, 11 Jun 2017 18:18:23 +0000");
System.out.println(est);
System.out.println(utc);

输出将是:

2017-06-10T12:49:45.000-04:00
2017-06-11T18:18:23.000Z

如果它们与您的预期不完全一样(或者您仍然遇到错误),请参阅下面的说明。


注意事项

  • 请注意,EST 被打印为带有偏移量 -0400 的日期/时间。那是因为EST 内部变成了America/New_York 时区,现在是夏令时,它的偏移量是-0400(我可以通过DateTimeZone.forTimeZone(TimeZone.getTimeZone("EST")) 来解决这个问题。问题是:这些三个字母的名称是@987654322 @,而 joda-time 只是为它们假设一个“默认值”。因此,如果您不期望这个时区,并且您不想依赖默认值,您可以使用具有自定义值的地图,如下所示:

    // mapping EST to some other timezone (I know it's wrong and Chicago is not EST, it's just an example)
    Map<String, DateTimeZone> map = new LinkedHashMap<>();
    map.put("EST", DateTimeZone.forID("America/Chicago"));
    // parser for my custom map
    DateTimeParser customTimeZoneParser = new DateTimeFormatterBuilder().appendTimeZoneShortName(map).toParser();
    DateTimeFormatter fmt = new DateTimeFormatterBuilder()
        // append common pattern
        .appendPattern("EEE, d MMM yyyy HH:mm:ss ")
        // optional offset
        .appendOptional(offsetParser)
        // optional custom timezone name
        .appendOptional(customTimeZoneParser)
        // optional timezone name (accepts all others that are not in the map)
        .appendOptional(zoneNameParser)
        // create formatter (use English Locale to make sure it parses weekdays and month names independent of JVM config)
        .toFormatter().withLocale(Locale.ENGLISH)
        // make sure the offset "+0000" is parsed
        .withOffsetParsed();
    System.out.println(fmt.parseDateTime("Sat, 10 Jun 2017 12:49:45 EST"));
    

这会将EST 解析为America/Chicago(我知道这是错误的,芝加哥不是EST,这只是一个示例,说明如何使用地图更改默认值),输出将是:

2017-06-10T12:49:45.000-05:00

如果您在上面的第一个代码中遇到错误,您也可以使用它,将 EST 映射到所需的时区(取决于您使用的 jodatime 和 Java 版本,EST 可能不会映射到默认值并引发异常,因此使用自定义映射可以避免这种情况)。


新的日期时间 API

正如@Ole V.V.'s comment 中所说的(我昨天没有时间写),joda-time 正在被new Java's Date and Time API 取代,这要优于compared to the old Date and SimpleDateFormat classes

如果您使用的是 Java >= 8,则 java.time 包已经是 JDK 的一部分。对于 Java ThreeTen Backport。对于Android,还有ThreeTenABP(更多关于如何使用它here)。

如果您要开始一个新项目,请考虑使用新 API 而不是 joda-time,因为在 joda's website 中它说:请注意,Joda-Time 被认为是一个基本上“完成”的项目。没有计划进行重大改进。如果使用 Java SE 8,请迁移到 java.time (JSR-310)

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

这个想法与jodatime非常相似,只是有细微的差别:

  • 您可以使用可选的部分分隔符[]
  • 需要一组具有自定义时区名称(将EST 映射到某个有效的非模糊时区)(因为EST 未映射到任何默认值)
  • 使用了一个新类:ZonedDateTime,它表示带有时区的日期和时间(因此它涵盖了您的两种情况)

只是提醒这些类在 java.time 包中(或在 org.threeten.bp 中,具体取决于您使用的 Java 版本,如上所述):

// set with custom timezone names
Set<ZoneId> set = new HashSet<>();
// when parsing, ambiguous EST uses to New York
set.add(ZoneId.of("America/New_York"));

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // append pattern, with optional offset (delimited by [])
    .appendPattern("EEE, d MMM yyyy HH:mm:ss[ Z]")
    // append optional timezone name with custom set for EST
    .optionalStart().appendLiteral(" ").appendZoneText(TextStyle.SHORT, set).optionalEnd()
    // create formatter using English locale to make sure it parses weekdays and month names correctly
    .toFormatter(Locale.ENGLISH);

ZonedDateTime est = ZonedDateTime.parse("Sat, 10 Jun 2017 12:49:45 EST", fmt);
ZonedDateTime utc = ZonedDateTime.parse("Sun, 11 Jun 2017 18:18:23 +0000", fmt);
System.out.println(est); // 2017-06-10T12:49:45-04:00[America/New_York]
System.out.println(utc); // 2017-06-11T18:18:23Z

输出将是:

2017-06-10T12:49:45-04:00[美国/纽约]
2017-06-11T18:18:23Z

请注意,在第一种情况下,EST 被设置为 America/New_York(由自定义集配置)。 appendZoneText 可以做到这一点,它使用自定义集中的值来解决模棱两可的情况。

第二种情况设置为UTC,因为偏移量是+0000

如果您想将第一个对象转换为 UTC,这很简单:

System.out.println(est.withZoneSameInstant(ZoneOffset.UTC)); // 2017-06-10T16:49:45Z

输出将是纽约的日期/时间转换为 UTC:

2017-06-10T16:49:45Z

除了ZoneOffset.UTC,您当然可以使用任何您想要的时区或偏移量(使用ZoneIdZoneOffset 类,查看javadoc 了解更多详细信息)。

【讨论】:

  • 非常感谢您的出色响应,更改 DateTimeZone.forID 效果很好。毫无疑问,我将来也会使用非常详细的演练:D:D
猜你喜欢
  • 1970-01-01
  • 2016-09-12
  • 2020-01-05
  • 2015-03-13
  • 2018-05-27
  • 1970-01-01
  • 1970-01-01
  • 2021-10-07
  • 2016-11-16
相关资源
最近更新 更多