问题
I'm parsing a number of news feeds and each item's pubDate follows the same format:
Sun, 11 Jun 2017 18:18:23 +0000
Unfortunately one feed does not:
Sat, 10 Jun 2017 12:49:45 EST
I have tried to parse the date with no luck using androids java date and 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();
}
Which throws and error:
java.text.ParseException: Unparseable date: "Sat, 3 Jun 2017 19:53:09 EST" (at offset 26)
I've also tried to do this using 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);
Which throws an error:
Caused by: java.lang.IllegalArgumentException: Invalid format: "Sat, 10 Jun 2017 12:49:45 EST" is malformed at " EST"
Has anyone encountered this before?
回答1:
First of all, if you're starting a new project, I suggest you to use the new date-time API instead of joda-time (more on that below). Anyway, here's a solution for both.
Joda Time
The problem is that the pattern Z
is the offset (in formats like +0000
or -0100
), but the string EST
is the timezone short name, which is parsed by the pattern z
(take a look at jodatime javadoc for more details).
So, you need a pattern with optional sections, that can receive one or another at the same time. You can do that with the org.joda.time.format.DateTimeFormatterBuilder
class.
First you need to create 2 instances of org.joda.time.format.DateTimeParser
(one for Z
, and other for z
), and add them as optional parsers. Then you create the org.joda.time.format.DateTimeFormatter
using the code below. Note that I also used java.util.Locale
, just to make sure it parses the weekday and month names correctly (so you don't depend upon default locale, which can vary on each 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);
The output will be:
2017-06-10T12:49:45.000-04:00
2017-06-11T18:18:23.000Z
If they're not exactly like you were expecting (or you're still getting errors), please see the notes below.
Notes:
Note that
EST
was printed as a date/time with offset-0400
. That's becauseEST
internally becameAmerica/New_York
timezone, which is now in Daylight Saving Time and its offset is-0400
(I could figure this out by doingDateTimeZone.forTimeZone(TimeZone.getTimeZone("EST"))
. The problem is: these 3-letter names are ambiguous and not standard, and joda-time just assumes a "default" for them. So, if you were not expecting this timezone, and you don't want to rely on defaults, you can use a map with custom values, like this:// 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"));
This will parse EST
as America/Chicago
(I know it's wrong and Chicago is not EST
, it's just an example of how you can change the defaults using a map), and the output will be:
2017-06-10T12:49:45.000-05:00
If you got an error with the first code above, you can also use this, mapping EST
to the desired timezone (depending on the version of jodatime and Java you're using, EST
might not be mapped to a default value and throws an exception, so using a custom map avoids this).
New Date-time API
As told in @Ole V.V.'s comment (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.
If you're using Java >= 8, the java.time
package is already part of the JDK. For Java <= 7 there's the ThreeTen Backport. And for Android, there's the ThreeTenABP (more on how to use it here).
If you're starting a new project, please consider the new API instead of joda-time, because in joda's website it says: Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310).
The code below works for both. The only difference is the package names (in Java 8 is java.time
and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp
), but the classes and methods names are the same.
The idea is very similar to jodatime, with minor differences:
- you can use the optional section delimiters
[]
- a set with custom timezone names (to map
EST
to some valid non-ambiguous timezone) is required (asEST
is not mapped to any default) - a new class is used:
ZonedDateTime
, which represents a date and time with a timezone (so it covers both of your cases)
Just reminding that these classes are in java.time
package (or in org.threeten.bp
depending on what Java version you're using, as explained above):
// 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
The output will be:
2017-06-10T12:49:45-04:00[America/New_York]
2017-06-11T18:18:23Z
Note that in the first case, EST
was set to America/New_York
(as configured by the custom set). The appendZoneText
does the trick, using the values in the custom set to resolve ambiguous cases.
And the second case was set to UTC, as the offset is +0000
.
If you want to convert the first object to UTC, it's straighforward:
System.out.println(est.withZoneSameInstant(ZoneOffset.UTC)); // 2017-06-10T16:49:45Z
The output will be the New York's date/time converted to UTC:
2017-06-10T16:49:45Z
Instead of ZoneOffset.UTC
, of course you can use any timezone or offset you want (using ZoneId
and ZoneOffset
classes, check the javadoc for more details).
来源:https://stackoverflow.com/questions/44487282/android-converting-date-time-parse-error-even-tried-joda-time