將ISO 8601兼容的字符串轉換為java.util.Date



Answers

好的,這個問題已經得到解答,但我仍然會放棄我的答案。 它可能有助於某人。

我一直在尋找Android解決方案 (API 7)。

  • 喬達無法解決問題 - 這是巨大的,並且受到初始化緩慢的影響。 對於這個特定的目的來說,這似乎也是一個重大的過火。
  • 涉及javax.xml答案在Android API 7上不起作用。

完成這個簡單的課程。 它涵蓋ISO 8601字符串的最常見形式 ,但在某些情況下(當您確信輸入將採用格式時)應該足夠了。

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

/**
 * Helper class for handling a most common subset of ISO 8601 strings
 * (in the following format: "2008-03-01T13:00:00+01:00"). It supports
 * parsing the "Z" timezone, but many other less-used features are
 * missing.
 */
public final class ISO8601 {
    /** Transform Calendar to ISO 8601 string. */
    public static String fromCalendar(final Calendar calendar) {
        Date date = calendar.getTime();
        String formatted = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
            .format(date);
        return formatted.substring(0, 22) + ":" + formatted.substring(22);
    }

    /** Get current date and time formatted as ISO 8601 string. */
    public static String now() {
        return fromCalendar(GregorianCalendar.getInstance());
    }

    /** Transform ISO 8601 string to Calendar. */
    public static Calendar toCalendar(final String iso8601string)
            throws ParseException {
        Calendar calendar = GregorianCalendar.getInstance();
        String s = iso8601string.replace("Z", "+00:00");
        try {
            s = s.substring(0, 22) + s.substring(23);  // to get rid of the ":"
        } catch (IndexOutOfBoundsException e) {
            throw new ParseException("Invalid length", 0);
        }
        Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(s);
        calendar.setTime(date);
        return calendar;
    }
}

性能說明:我每次都實例化新的SimpleDateFormat,以避免Android 2.1中的錯誤 。 如果你和我一樣驚訝,看看這個謎語 。 對於其他Java引擎,您可以將實例緩存在私有靜態字段中(使用ThreadLocal,以確保線程安全)。

Question

我試圖將ISO 8601格式的字符串轉換為java.util.Date。

如果使用Locale(比較樣本),我發現模式“yyyy-MM-dd'T'HH:mm:ssZ”符合ISO8601標準。 但是,使用java.text.SimpleDateFormat,我無法轉換格式正確的字符串“2010-01-01T12:00:00 + 01:00”。 我必須首先將它轉換為“2010-01-01T12:00:00 + 0100”,而不用冒號。 所以,目前的解決方案是

SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMANY);
String date = "2010-01-01T12:00:00+01:00".replaceAll("\\+0([0-9]){1}\\:00", "+0$100");
System.out.println(ISO8601DATEFORMAT.parse(date));

這顯然不是那麼好。 我錯過了什麼,或者有更好的解決方案嗎?

回答

感謝JuanZe的評論,我發現了Joda-Time魔法, 這裡也有描述 。 所以,解決方案是

DateTimeFormatter parser2 = ISODateTimeFormat.dateTimeNoMillis();
String jtdate = "2010-01-01T12:00:00+01:00";
System.out.println(parser2.parseDateTime(jtdate));

或者更簡單地說,通過構造函數使用默認解析器:

DateTime dt = new DateTime( "2010-01-01T12:00:00+01:00" ) ;

對我來說,這很好。




Java 7+的解決方法是使用SimpleDateFormat:
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);

此代碼可以解析ISO8601格式,如:

  • 2017-05-17T06:01:43.785Z
  • 2017-05-13T02:58:21.391+01:00

但在Java6上, SimpleDateFormat不能理解X字符並且會拋出
IllegalArgumentException: Unknown pattern character 'X'
我們需要使用SimpleDateFormat Java 6中可讀的格式。

public static Date iso8601Format(String formattedDate) throws ParseException {
    try {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);
        return df.parse(formattedDate);
    } catch (IllegalArgumentException ex) {
        // error happen in Java 6: Unknown pattern character 'X'
        if (formattedDate.endsWith("Z")) formattedDate = formattedDate.replace("Z", "+0000");
        else formattedDate = formattedDate.replaceAll("([+-]\\d\\d):(\\d\\d)\\s*$", "$1$2");
        DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US);
        return df1.parse(formattedDate);
    }
}

在Java 6中發生錯誤時(您可以檢測Java版本並用if語句替換try / catch),將[ Z+0000 ]或[ +01:00+0100 ]替換。




對於Java版本7

您可以遵循Oracle文檔: SimpleDateFormat : SimpleDateFormat

X - 用於ISO 8601時區

TimeZone tz = TimeZone.getTimeZone("UTC");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
df.setTimeZone(tz);
String nowAsISO = df.format(new Date());

System.out.println(nowAsISO);

DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
//nowAsISO = "2013-05-31T00:00:00Z";
Date finalResult = df1.parse(nowAsISO);

System.out.println(finalResult);



使用字符串LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)







Java有十幾種不同的解析日期時間的方法,正如這裡出色的答案所示。 但令人驚訝的是,Java的時間類沒有一個完全實現ISO 8601!

在Java 8中,我建議:

ZonedDateTime zp = ZonedDateTime.parse(string);
Date date = Date.from(zp.toInstant());

這將處理UTC和例如“2017-09-13T10:36:40Z”或“2017-09-13T10:36:40 + 01:00”等偏移量的示例。 它將用於大多數用例。

但它不會處理像“2017-09-13T10:36:40 + 01”這樣的示例,這一個有效的ISO 8601日期時間。
它也不會處理日期,例如“2017-09-13”。

如果你必須處理這些,我建議首先使用正則表達式來嗅探語法。

這裡有一個很好的ISO 8601示例列表,其中有很多角落案例: https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ : https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/我是沒有意識到任何可以應對它們的Java類。




DatatypeConverter解決方案不適用於所有虛擬機。 以下適用於我:

javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar("2011-01-01Z").toGregorianCalendar().getTime()

我發現,喬達並沒有開箱即用(特別是我在上面給出的日期時區的例子,這應該是有效的)




我面臨同樣的問題,並通過下面的代碼解決它。

 public static Calendar getCalendarFromISO(String datestring) {
    Calendar calendar = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault()) ;
    SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
    try {
        Date date = dateformat.parse(datestring);
        date.setHours(date.getHours() - 1);
        calendar.setTime(date);

        String test = dateformat.format(calendar.getTime());
        Log.e("TEST_TIME", test);

    } catch (ParseException e) {
        e.printStackTrace();
    }

    return calendar;
}

早些時候我使用SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.getDefault());

但後來我發現這個例外的主要原因是yyyy-MM-dd'T'HH:mm:ss.SSSZ

所以我用了

SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());

它為我工作得很好。




解析ISO8601時間戳的另一種非常簡單的方法是使用org.apache.commons.lang.time.DateUtils

import static org.junit.Assert.assertEquals;

import java.text.ParseException;
import java.util.Date;
import org.apache.commons.lang.time.DateUtils;
import org.junit.Test;

public class ISO8601TimestampFormatTest {
  @Test
  public void parse() throws ParseException {
    Date date = DateUtils.parseDate("2010-01-01T12:00:00+01:00", new String[]{ "yyyy-MM-dd'T'HH:mm:ssZZ" });
    assertEquals("Fri Jan 01 12:00:00 CET 2010", date.toString());
  }
}






這似乎對我最有效:

public static Date fromISO8601_( String string ) {

    try {
            return new SimpleDateFormat ( "yyyy-MM-dd'T'HH:mm:ssXXX").parse ( string );
    } catch ( ParseException e ) {
        return Exceptions.handle (Date.class, "Not a valid ISO8601", e);
    }


}

我需要將JavaScript日期字符串轉換為Java。 我根據推薦找到了上述的作品。 有一些使用SimpleDateFormat的例子很接近,但它們似乎並不像以下推薦的那樣:

http://www.w3.org/TR/NOTE-datetime

並由PLIST和JavaScript字符串等支持,這就是我所需要的。

這似乎是ISO8601字符串中最常見的形式,也是一個很好的子集。

The examples they give are:

1994-11-05T08:15:30-05:00 corresponds 
November 5, 1994, 8:15:30 am, US Eastern Standard Time.

 1994-11-05T13:15:30Z corresponds to the same instant.

I also have a fast version:

final static int SHORT_ISO_8601_TIME_LENGTH =  "1994-11-05T08:15:30Z".length ();
                                            // 01234567890123456789012
final static int LONG_ISO_8601_TIME_LENGTH = "1994-11-05T08:15:30-05:00".length ();


public static Date fromISO8601( String string ) {
    if (isISO8601 ( string )) {
        char [] charArray = Reflection.toCharArray ( string );//uses unsafe or string.toCharArray if unsafe is not available
        int year = CharScanner.parseIntFromTo ( charArray, 0, 4 );
        int month = CharScanner.parseIntFromTo ( charArray, 5, 7 );
        int day = CharScanner.parseIntFromTo ( charArray, 8, 10 );
        int hour = CharScanner.parseIntFromTo ( charArray, 11, 13 );

        int minute = CharScanner.parseIntFromTo ( charArray, 14, 16 );

        int second = CharScanner.parseIntFromTo ( charArray, 17, 19 );

        TimeZone tz ;

         if (charArray[19] == 'Z') {

             tz = TimeZone.getTimeZone ( "GMT" );
         } else {

             StringBuilder builder = new StringBuilder ( 9 );
             builder.append ( "GMT" );
             builder.append( charArray, 19, LONG_ISO_8601_TIME_LENGTH - 19);
             String tzStr = builder.toString ();
             tz = TimeZone.getTimeZone ( tzStr ) ;

         }
         return toDate ( tz, year, month, day, hour, minute, second );

    }   else {
        return null;
    }

}

...

public static int parseIntFromTo ( char[] digitChars, int offset, int to ) {
    int num = digitChars[ offset ] - '0';
    if ( ++offset < to ) {
        num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
        if ( ++offset < to ) {
            num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
            if ( ++offset < to ) {
                num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                if ( ++offset < to ) {
                    num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                    if ( ++offset < to ) {
                        num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                        if ( ++offset < to ) {
                            num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                            if ( ++offset < to ) {
                                num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                                if ( ++offset < to ) {
                                    num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return num;
}


public static boolean isISO8601( String string ) {
      boolean valid = true;

      if (string.length () == SHORT_ISO_8601_TIME_LENGTH) {
          valid &=  (string.charAt ( 19 )  == 'Z');

      } else if (string.length () == LONG_ISO_8601_TIME_LENGTH) {
          valid &=  (string.charAt ( 19 )  == '-' || string.charAt ( 19 )  == '+');
          valid &=  (string.charAt ( 22 )  == ':');

      } else {
          return false;
      }

    //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
    // "1 9 9 4 - 1 1 - 0 5 T 0 8 : 1 5 : 3 0 - 0 5 : 0 0

    valid &=  (string.charAt ( 4 )  == '-') &&
                (string.charAt ( 7 )  == '-') &&
                (string.charAt ( 10 ) == 'T') &&
                (string.charAt ( 13 ) == ':') &&
                (string.charAt ( 16 ) == ':');

    return valid;
}

I have not benchmarked it, but I am guess it will be pretty fast. It seems to work. :)

@Test
public void testIsoShortDate() {
    String test =  "1994-11-05T08:15:30Z";

    Date date = Dates.fromISO8601 ( test );
    Date date2 = Dates.fromISO8601_ ( test );

    assertEquals(date2.toString (), date.toString ());

    puts (date);
}

@Test
public void testIsoLongDate() {
    String test =  "1994-11-05T08:11:22-05:00";

    Date date = Dates.fromISO8601 ( test );
    Date date2 = Dates.fromISO8601_ ( test );

    assertEquals(date2.toString (), date.toString ());

    puts (date);
}






Links