Android 转换日期时间解析错误(甚至尝试过 joda 时间)
Android converting date time parse error (even tried joda time)
我正在解析多个新闻提要,每个项目的 pubDate 都遵循相同的格式:
Sun, 11 Jun 2017 18:18:23 +0000
不幸的是,一个提要没有:
Sat, 10 Jun 2017 12:49:45 EST
我尝试使用 android java 日期和 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: Unparseable date: "Sat, 3 Jun 2017 19:53:09 EST" (at offset 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);
抛出错误:
Caused by: java.lang.IllegalArgumentException: Invalid format: "Sat, 10 Jun 2017 12:49:45 EST" is malformed at " EST"
有没有人遇到过这种情况?
首先,如果您要开始一个新项目,我建议您使用新的日期时间 API 而不是 joda-time(更多内容见下文)。无论如何,这是两者的解决方案。
乔达时间
问题是模式 Z
是偏移量(格式如 +0000
或 -0100
),但字符串 EST
是时区短名称,它由模式 z
解析(查看 jodatime javadoc 了解更多详情)。
因此,您需要一种带有可选部分的模式,可以同时接收一个或另一个。您可以使用 org.joda.time.format.DateTimeFormatterBuilder
class.
首先您需要创建 2 个 org.joda.time.format.DateTimeParser
实例(一个用于 Z
,另一个用于 z
),并将它们添加为可选解析器。然后使用下面的代码创建 org.joda.time.format.DateTimeFormatter
。请注意,我还使用了 java.util.Locale
,只是为了确保它正确解析工作日和月份名称(因此您不依赖于默认区域设置,默认区域设置可能因每个 system/machine 而异):
// 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
打印为 date/time,偏移量为 -0400
。那是因为 EST
在内部变成了 America/New_York
时区,现在是夏令时,它的偏移量是 -0400
(我可以通过 DateTimeZone.forTimeZone(TimeZone.getTimeZone("EST"))
来解决这个问题。问题是:这些 3 个字母的名称是 ambiguous and not standard,joda-time 只是为它们假设一个 "default"。所以,如果您不期望这个时区,并且您不想依赖默认值,您可以使用具有自定义值的地图,如下所示:
// 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
如 (and I didn't have time to write yesterday), joda-time is being replaced by the new Java's Date and Time API, which is far superior compared to the old Date
and SimpleDateFormat
classes 所述。
如果您使用的是 Java >= 8,则 java.time
包已经是 JDK 的一部分。对于 Java <= 7 有 ThreeTen Backport. And for Android, there's the ThreeTenABP (more on how to use it ).
如果您正在开始一个新项目,请考虑使用新的 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
),但是 classes和方法names是一样的。
思路与jodatime非常相似,略有不同:
- 您可以使用可选的部分分隔符
[]
- 需要一个具有自定义时区名称的集合(将
EST
映射到某个有效的无歧义时区)(因为 EST
未映射到任何默认值)
- 使用了一个新的class:
ZonedDateTime
,它代表一个带有时区的日期和时间(因此它涵盖了你的两种情况)
提醒一下,这些 class 在 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[America/New_York]
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
输出将纽约的 date/time 转换为 UTC:
2017-06-10T16:49:45Z
而不是 ZoneOffset.UTC
,当然你可以使用任何你想要的时区或偏移量(使用 ZoneId
和 ZoneOffset
classes,检查 javadoc了解更多详情)。
我正在解析多个新闻提要,每个项目的 pubDate 都遵循相同的格式:
Sun, 11 Jun 2017 18:18:23 +0000
不幸的是,一个提要没有:
Sat, 10 Jun 2017 12:49:45 EST
我尝试使用 android java 日期和 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: Unparseable date: "Sat, 3 Jun 2017 19:53:09 EST" (at offset 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);
抛出错误:
Caused by: java.lang.IllegalArgumentException: Invalid format: "Sat, 10 Jun 2017 12:49:45 EST" is malformed at " EST"
有没有人遇到过这种情况?
首先,如果您要开始一个新项目,我建议您使用新的日期时间 API 而不是 joda-time(更多内容见下文)。无论如何,这是两者的解决方案。
乔达时间
问题是模式 Z
是偏移量(格式如 +0000
或 -0100
),但字符串 EST
是时区短名称,它由模式 z
解析(查看 jodatime javadoc 了解更多详情)。
因此,您需要一种带有可选部分的模式,可以同时接收一个或另一个。您可以使用 org.joda.time.format.DateTimeFormatterBuilder
class.
首先您需要创建 2 个 org.joda.time.format.DateTimeParser
实例(一个用于 Z
,另一个用于 z
),并将它们添加为可选解析器。然后使用下面的代码创建 org.joda.time.format.DateTimeFormatter
。请注意,我还使用了 java.util.Locale
,只是为了确保它正确解析工作日和月份名称(因此您不依赖于默认区域设置,默认区域设置可能因每个 system/machine 而异):
// 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
打印为 date/time,偏移量为-0400
。那是因为EST
在内部变成了America/New_York
时区,现在是夏令时,它的偏移量是-0400
(我可以通过DateTimeZone.forTimeZone(TimeZone.getTimeZone("EST"))
来解决这个问题。问题是:这些 3 个字母的名称是 ambiguous and not standard,joda-time 只是为它们假设一个 "default"。所以,如果您不期望这个时区,并且您不想依赖默认值,您可以使用具有自定义值的地图,如下所示:// 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
如 Date
and SimpleDateFormat
classes 所述。
如果您使用的是 Java >= 8,则 java.time
包已经是 JDK 的一部分。对于 Java <= 7 有 ThreeTen Backport. And for Android, there's the ThreeTenABP (more on how to use it
如果您正在开始一个新项目,请考虑使用新的 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
),但是 classes和方法names是一样的。
思路与jodatime非常相似,略有不同:
- 您可以使用可选的部分分隔符
[]
- 需要一个具有自定义时区名称的集合(将
EST
映射到某个有效的无歧义时区)(因为EST
未映射到任何默认值) - 使用了一个新的class:
ZonedDateTime
,它代表一个带有时区的日期和时间(因此它涵盖了你的两种情况)
提醒一下,这些 class 在 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[America/New_York]
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
输出将纽约的 date/time 转换为 UTC:
2017-06-10T16:49:45Z
而不是 ZoneOffset.UTC
,当然你可以使用任何你想要的时区或偏移量(使用 ZoneId
和 ZoneOffset
classes,检查 javadoc了解更多详情)。