可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have Service
fetch date string from web and then I want to pare it to Date
object. But somehow application crashes.
This is my string that I'm parsing: 2015-02-05T05:20:02+00:00
onStartCommand()
String datetime = "2015-02-05T05:20:02+00:00";
Date new_date = stringToDate(datetime);
stringToDate()
private Date stringToDate(String s){
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
try{
return df.parse(s);
}catch(ParseException e){
e.printStackTrace();
}
return null;
}
LogCat:
02-06 20:37:02.008: E/AndroidRuntime(28565): FATAL EXCEPTION: main
02-06 20:37:02.008: E/AndroidRuntime(28565): Process: com.dotmav.runescapenotifier, PID: 28565
02-06 20:37:02.008: E/AndroidRuntime(28565): java.lang.RuntimeException: Unable to start service com.dotmav.runescapenotifier.GEService@384655b5 with Intent { cmp=com.dotmav.runescapenotifier/.GEService }: java.lang.IllegalArgumentException: Unknown pattern character 'X'
02-06 20:37:02.008: E/AndroidRuntime(28565): at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2881)
02-06 20:37:02.008: E/AndroidRuntime(28565): at android.app.ActivityThread.access$2100(ActivityThread.java:144)
02-06 20:37:02.008: E/AndroidRuntime(28565): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1376)
02-06 20:37:02.008: E/AndroidRuntime(28565): at android.os.Handler.dispatchMessage(Handler.java:102)
02-06 20:37:02.008: E/AndroidRuntime(28565): at android.os.Looper.loop(Looper.java:135)
02-06 20:37:02.008: E/AndroidRuntime(28565): at android.app.ActivityThread.main(ActivityThread.java:5221)
02-06 20:37:02.008: E/AndroidRuntime(28565): at java.lang.reflect.Method.invoke(Native Method)
02-06 20:37:02.008: E/AndroidRuntime(28565): at java.lang.reflect.Method.invoke(Method.java:372)
02-06 20:37:02.008: E/AndroidRuntime(28565): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
02-06 20:37:02.008: E/AndroidRuntime(28565): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
02-06 20:37:02.008: E/AndroidRuntime(28565): Caused by: java.lang.IllegalArgumentException: Unknown pattern character 'X'
02-06 20:37:02.008: E/AndroidRuntime(28565): at java.text.SimpleDateFormat.validatePatternCharacter(SimpleDateFormat.java:314)
02-06 20:37:02.008: E/AndroidRuntime(28565): at java.text.SimpleDateFormat.validatePattern(SimpleDateFormat.java:303)
02-06 20:37:02.008: E/AndroidRuntime(28565): at java.text.SimpleDateFormat.<init>(SimpleDateFormat.java:356)
02-06 20:37:02.008: E/AndroidRuntime(28565): at java.text.SimpleDateFormat.<init>(SimpleDateFormat.java:249)
02-06 20:37:02.008: E/AndroidRuntime(28565): at com.dotmav.runescapenotifier.GEService.stringToDate(GEService.java:68)
02-06 20:37:02.008: E/AndroidRuntime(28565): at com.dotmav.runescapenotifier.GEService.onStartCommand(GEService.java:44)
02-06 20:37:02.008: E/AndroidRuntime(28565): at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2864)
02-06 20:37:02.008: E/AndroidRuntime(28565): ... 9 more
EDIT: onDestroy() set alarm for periodical update...
AlarmManager alarm = (AlarmManager)getSystemService(ALARM_SERVICE);
alarm.set(
AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + (1000 * 60),
PendingIntent.getService(this, 0, new Intent(this, GEService.class), 0)
);
回答1:
Remove "XXX" from
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
and everything would work fine.
Go through the list of symbols that can be used inside a SimpleDateFormat
constructor. Although the documentation shows the "XXX" format, this doesn't work on Android and will throw an IllegalArgumentException
.
Probably you are looking for "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
Change your code to
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
or
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); // if timezone is required
回答2:
The Android version of SimpleDateFormat
doesn't support the X
pattern so XXX
won't work but instead you can use ZZZZZ
which does the same and outputs the timezone in format +02:00
(or -02:00
depending on the local timezone).
回答3:
No one has mentioned about this error occurring on pre-nougat devices so I thought to share my answer and maybe it is helpful for those who reached this thread because of it.
This answer rightly mentions that "X" is supported only for Nougat+ devices. I still see that documentation suggests to use "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"
and not sure why they don't make this point explicit.
For me, yyyy-MM-dd'T'HH:mm:ssXXX
was working fine until I tried to test it on 6.0 device and it started crashing which led me to research on this topic. Replacing it with yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ
has resolved the issue and works on all 5.0+ devices.
回答4:
You are using the wrong date formatter.
Use this instead: DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
I think that android in contrast with Java 7 uses Z (as in Java 6) for timezones and not X. So, use this for your date formats.
回答5:
Since Z and XXX are different, I've implemented the following workaround:
// This is a workaround from Z to XXX timezone format
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") {
@Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) {
StringBuffer rfcFormat = super.format(date, toAppendTo, pos);
return rfcFormat.insert(rfcFormat.length() - 2, ":");
}
@Override
public Date parse(String text, ParsePosition pos) {
if (text.length() > 3) {
text = text.substring(0, text.length() - 3) + text.substring(text.length() - 2);
}
return super.parse(text, pos);
}
}
回答6:
Android SimpleDateFormat is different from Java 7 SDK and does not support 'X' to parse ISO 8601. You can use the 'Z' or 'ZZZZZ' styles to format and programatically set the time zone to UTC. Here is a util class:
public class DateUtil {
public static final String iso8601DatePattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ";
public static final DateFormat iso8601DateFormat = new SimpleDateFormat(iso8601DatePattern);
public static final TimeZone utcTimeZone = TimeZone.getTimeZone("UTC");
static {
iso8601DateFormat.setTimeZone(utcTimeZone);
}
public static String formatAsIso8601(Date date) {
return iso8601DateFormat.format(date);
}
}
回答7:
Simple solution:
Use this yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ
Instead of yyyy-MM-dd'T'HH:mm:ss.SSSXXX
Done.
回答8:
The error is saying that simpleDateFormat does not recognize the character X. If you are looking for milliseconds it is represented with the character S.
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
回答9:
tl;dr
long millisecondsSinceEpoch = OffsetDateTime.parse( "2015-02-05T05:20:02+00:00" ).plusHour( 1 ).toInstant().toEpochMilli() // Warning: Possible loss of data in truncating nanoseconds to milliseconds. But not in this particular case.
Details
Other answers are correct but now outdated. The old date-time classes are now legacy. Use java.time classes instead.
ISO 8601
The input String is in standard ISO 8601 format. Parse directly, no need to define a formatting pattern as the java.time classes use ISO 8601 formats by default.
OffsetDateTime
The input includes an offset-from-UTC with the +00:00
so we can parse as an OffsetDateTime
object.
String input = "2015-02-05T05:20:02+00:00" ;
OffsetDateTime odt = OffsetDateTime.parse( input );
Math
If you want to add an hour or a minute later to set an alarm, call the plus
methods.
OffsetDateTime minuteLater = odt.plusMinutes( 1 );
OffsetDateTime hourLater = odt.plusHours( 1 );
To get a count of milliseconds, go through the Instant
class. The Instant
class represents a moment on the timeline in UTC with a resolution of nanoseconds. Asking for milliseconds means possible data loss as the nine digits of a decimal fraction get truncated to the three digits of decimal fraction.
long millisecondsSinceEpoch = odt.toInstant().toEpochMilli(); // Warning: Possible loss of data in truncating nanoseconds to milliseconds.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the old troublesome date-time classes such as java.util.Date
, .Calendar
, & java.text.SimpleDateFormat
.
The Joda-Time project, now in maintenance mode, advises migration to java.time.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations.
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport and further adapted to Android in ThreeTenABP.
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time.
回答10:
Use a SimpleDateFormat
to produce a properly formatted String
output:
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
String formattedNow = simpleDateFormat.format(new Date(System.currentTimeMillis()));
Output : 2018-02-27T07:36:47.686Z
回答11:
The following class can be used to convert the string to date when pattern doesn't aware of it.
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* StringToDateFormater is a concrete class for formatting and parsing dates in a locale-sensitive manner. It allows for
* formatting (date → text), parsing (text → date), and normalization.
*
* This class is mainly used for convert the date from string. It should be used only when date pattern doesn't aware of
* it.
*
*/
public class StringToDateFormater extends SimpleDateFormat {
private static final List<String> DATE_SUPPORTED_FORMAT_LIST = Arrays.asList("yyyyMMddHHmmss", "yyyyMMddHHmm",
"yyyyMMddHHmm", "yyyyMMddHH", "yyyyMMdd", "yyyyMMddHHmmssSS");
/**
*
*/
private static final long serialVersionUID = -1732857502488169225L;
/**
* @param pattern
*/
public StringToDateFormater() {
}
@Override
public Date parse(String source) {
Date date = null;
SimpleDateFormat dateFormat = null;
for (String format : DATE_SUPPORTED_FORMAT_LIST) {
dateFormat = new SimpleDateFormat(format);
try {
return dateFormat.parse(source);
} catch (Exception exception) {
}
}
return date;
}
}
回答12:
according to android documentation zone offset with X
format is supporting in API level 24+
Letter Date or Time Component Supported (API Levels)
X Time zone 24+
so we can't use for lower APIs, I found a workaround for this issue:
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").format(date).let {
StringBuilder(it).insert(it.length - 2, ":").toString()
}
回答13:
Based on idea from Alexander K, I optimize it and support parsing from and to UTC timezone format like 1970-01-01T00:00:00Z
, to make all behaviour exactly same as yyyy-MM-dd'T'HH:mm:ssXXX
.
public class IsoSimpleDateFormatBeforeNougat extends SimpleDateFormat {
public IsoSimpleDateFormatBeforeNougat() {
super("yyyy-MM-dd'T'HH:mm:ssZ");
}
public IsoSimpleDateFormatBeforeNougat(Locale locale) {
super("yyyy-MM-dd'T'HH:mm:ssZ", locale);
}
public IsoSimpleDateFormatBeforeNougat(DateFormatSymbols formatSymbols) {
super("yyyy-MM-dd'T'HH:mm:ssZ", formatSymbols);
}
@Override
public Date parse(String text, ParsePosition pos) {
if (text.endsWith("Z")) {
return super.parse(text.substring(0, text.length() - 1) + "+0000", pos);
}
if (text.length() > 3 && text.substring(text.length() - 3, text.length() - 2).equals(":")) {
text = text.substring(0, text.length() - 3) + text.substring(text.length() - 2);
}
return super.parse(text, pos);
}
@Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) {
StringBuffer rfcFormat = super.format(date, toAppendTo, pos);
if (rfcFormat.substring(rfcFormat.length() - 5).equals("+0000")) {
return rfcFormat.replace(rfcFormat.length() - 5, rfcFormat.length(), "Z");
}
return rfcFormat.insert(rfcFormat.length() - 2, ":");
}
}
Test code:
@Test
public void test() throws ParseException {
//SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
SimpleDateFormat sdf = new IsoSimpleDateFormatBeforeNougat();
sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));
assertEquals("1970-01-01T08:00:00+08:00", sdf.format(new Date(0)));
assertEquals(0L, sdf.parse("1970-01-01T08:00:00+08:00").getTime());
sdf.setTimeZone(TimeZone.getTimeZone("GMT+0"));
assertEquals("1970-01-01T00:00:00Z", sdf.format(new Date(0)));
assertEquals(0L, sdf.parse("1970-01-01T00:00:00Z").getTime());
}