Android解析字符串到日期 - 未知模式字符'X'

时间:2015-02-06 19:49:18

标签: java android date simpledateformat

我有Service从网络获取日期字符串,然后我想将其削减到Date对象。但不知何故应用程序崩溃。 这是我正在解析的字符串: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

编辑: onDestroy()为定期更新设置闹钟...

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)
);

11 个答案:

答案 0 :(得分:26)

中删除“XXX”
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");

一切都会正常。

浏览可在SimpleDateFormat构造函数中使用的符号列表。这是documentation

可能您正在寻找"yyyy-MM-dd'T'HH:mm:ss.SSSZ"

将您的代码更改为

DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); 

DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); // if timezone is required

答案 1 :(得分:11)

您使用的是错误的日期格式化程序。

请改用:DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");

我认为与Java 7相比,android使用Z(如在Java 6中)用于时区而不是X.因此,使用this作为日期格式。

答案 2 :(得分:8)

由于Z和XXX不同,我实施了以下解决方法:

// 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);
    }
}

答案 3 :(得分:6)

Android SimpleDateFormat与Java 7 SDK不同,不支持&#39; X&#39;解析ISO 8601.您可以使用&#39; Z&#39;或者&#39; ZZZZZ&#39;样式格式化并以编程方式将时区设置为UTC。这是一个util类:

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);
    }
}

答案 4 :(得分:6)

没有人提及牛轧糖前设备上发生的此错误,因此我想与他人分享我的答案,也许这对因为此而到达此线程的人有所帮助。

answer正确地提到,仅Nougat +设备支持“ X”。我仍然看到documentation建议使用"yyyy-MM-dd'T'HH:mm:ss.SSSXXX",但不确定为什么他们没有明确指出这一点。

对我来说,yyyy-MM-dd'T'HH:mm:ssXXX一直运行良好,直到我尝试在6.0设备上对其进行测试,然后它开始崩溃,这促使我对该主题进行了研究。将其替换为yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ已解决了该问题,并且可以在所有5.0+设备上使用。

答案 5 :(得分:2)

错误在于simpleDateFormat无法识别字符X.如果您要查找毫秒数,则表示字符为S.

DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");

答案 6 :(得分:2)

API级别24+支持根据X格式的android documentation区域偏移量

Letter  Date or Time Component      Supported (API Levels)
X       Time zone                   24+

所以我们不能使用较低的API,我找到了解决此问题的方法:

SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").format(date).let {
    StringBuilder(it).insert(it.length - 2, ":").toString()
}

答案 7 :(得分:1)

TL;博士

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.

详细

其他答案是正确的,但现在已经过时了。旧的日期时间类现在是遗留的。请改用java.time类。

ISO 8601

输入字符串采用标准ISO 8601格式。直接解析,无需定义格式化模式,因为java.time类默认使用ISO 8601格式。

OffsetDateTime

输入包含与+00:00的UTC偏移量,因此我们可以解析为OffsetDateTime个对象。

String input = "2015-02-05T05:20:02+00:00" ;
OffsetDateTime odt = OffsetDateTime.parse( input );

数学

如果您想在一小时或一分钟后添加设置闹钟,请调用plus方法。

OffsetDateTime minuteLater = odt.plusMinutes( 1 );
OffsetDateTime hourLater = odt.plusHours( 1 );

要获得毫秒数,请浏览Instant课程。 Instant类代表UTC中时间轴上的一个时刻,分辨率为nanoseconds。要求毫秒意味着可能的数据丢失,因为小数部分的九位数被截断为小数部分的三位数。

long millisecondsSinceEpoch = odt.toInstant().toEpochMilli();  // Warning: Possible loss of data in truncating nanoseconds to milliseconds.

关于java.time

java.time框架内置于Java 8及更高版本中。这些类取代了旧的麻烦日期时间类,例如java.util.Date.Calendar和&amp; java.text.SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。

大部分java.time功能都被反向移植到Java 6&amp; ThreeTen-Backport中的7,并进一步适应Android中的ThreeTenABP

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。

答案 8 :(得分:1)

简单的解决方案:

使用yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ

取代 yyyy-MM-dd'T'HH:mm:ss.SSSXXX

完成。

答案 9 :(得分:1)

基于Alexander K的想法,我对其进行了优化,并支持往返于1970-01-01T00:00:00Z之类的UTC时区格式的解析,以使所有行为与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
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());
}

答案 10 :(得分:0)

当pattern不知道时,可以使用以下类将字符串转换为日期。

    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;
    }
}