Android转换日期时间解析错误(甚至尝试了joda时间)

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

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

不幸的是,一个Feed没有:

2017年6月10日星期六美国东部时间12:49:45

我试图使用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时间:

 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:格式无效:“星期六,2017年6月10日12:49:45 EST”在“EST”格式错误

有没有人遇到过这个?

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


乔达时间

问题是模式Z是偏移量(格式为+0000-0100 ),但字符串EST是时区短名称,由模式z解析(有关详细信息,请查看jodatime javadoc )。

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

首先,您需要创建2个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"))解决这个问题。 DateTimeZone.forTimeZone(TimeZone.getTimeZone("EST"))问题是:这些3个字母的名称含糊不清,而且不是标准的 ,joda-time只是假定它们是“默认”。所以,如果你不期望这个时区,并且你不想依赖默认值,你可以使用地图自定义值,如下所示:

     // mapping EST to some other timezone (I know it's wrong and Chicago is not EST, it's just an example) Map 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")); 

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

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

如果上面的第一个代码出错,你也可以使用它,将EST映射到所需的时区(取决于你正在使用的jodatime和Java的版本, EST可能没有映射到默认值并抛出exception,所以使用自定义地图避免这种情况)。


新的日期时间API

正如在@Ole VV的评论中所说的那样 (我昨天没有时间写),joda-time正在被新的Java的Date and Time API取代, 与旧的DateSimpleDateFormat类相比 ,它远远优于Java 。

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

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

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

这个想法与jodatime非常相似,但有一点不同:

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

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

 // set with custom timezone names Set 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

当然,您可以使用所需的任何时区或偏移量来ZoneId ZoneOffset (使用ZoneIdZoneOffset类,请查看javadoc以获取更多详细信息)。