تحويل سلسلة متوافقة مع ISO 8601 إلى java.util.Date



11 Answers

حسنًا ، تم الإجابة على هذا السؤال بالفعل ، ولكنني سأسقط إجابتي على أي حال. قد يساعد شخص ما.

لقد كنت أبحث عن حل للأندرويد (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 في كل مرة كوسيلة لتجنب خلل في الروبوت 2.1. إذا كنت مندهشًا كما كنت ، انظر هذا اللغز . بالنسبة لمحركات Java الأخرى ، يمكنك تخزين المثيل في حقل ثابت خاص (باستخدام ThreadLocal ، ليكون مؤشر الترابط آمنًا).

Question

أحاول تحويل سلسلة منسقة ISO 8601 إلى java.util.Date.

لقد وجدت النمط "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 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" ) ;

بالنسبة لي ، هذا لطيف.




لا يعمل حل DatatypeConverter في جميع أجهزة VM. الأعمال التالية بالنسبة لي:

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

لقد وجدت أن joda لا يعمل من خارج منطقة الجزاء (تحديدًا للمثال الذي قدمته أعلاه مع المنطقة الزمنية في تاريخ ، والذي يجب أن يكون صالحًا)




لجافا الإصدار 7

يمكنك اتباع وثائق أوراكل: 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);



هناك طريقة أخرى بسيطة للغاية لتعديل الطوابع الزمنية 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 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());

انها عملت بشكل جيد بالنسبة لي .













استخدام سلسلة مثل LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)




يستخدم الحل البديل لـ 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'
نحن بحاجة إلى تطبيع تاريخ ISO 8601 إلى التنسيق القابل للقراءة في Java 6 باستخدام SimpleDateFormat .

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

الطريقة أعلاه لاستبدال [ Z with +0000 ] أو [ +01:00 with +0100 ] عند حدوث خطأ في Java 6 (يمكنك الكشف عن إصدار Java واستبدال try / catch مع if statement).




يحتوي 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/ لا علم من أي فئة جافا يمكن أن تتعامل مع كل منهم.




يبدو أن هذا أفضل بالنسبة لي:

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


}

كنت بحاجة إلى تحويل إلى / جذر سلاسل جافا سكريبت إلى جافا. لقد وجدت الأعمال المذكورة أعلاه مع التوصية. كانت هناك بعض الأمثلة باستخدام SimpleDateFormat التي كانت قريبة ولكن لم يبدو أنها المجموعة الفرعية على النحو الموصى به من قبل:

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

وبدعم من PLIST وجافا سكريبت ، وهذا هو ما احتاجه.

يبدو أن هذا هو الشكل الأكثر شيوعًا لسلسلة 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);
}



Related