การแปลงสตริงที่สอดคล้องกับ ISO 8601 เป็น java.util.Date


668

ฉันพยายามที่จะแปลงISO 8601การจัดรูปแบบ String java.util.Dateไป

ฉันพบรูปแบบที่yyyy-MM-dd'T'HH:mm:ssZสอดคล้องกับ ISO8601 หากใช้กับ Locale (เปรียบเทียบตัวอย่าง)

อย่างไรก็ตามการใช้java.text.SimpleDateFormatฉันไม่สามารถแปลง 2010-01-01T12:00:00+01:00String ฉันต้องแปลงมันเป็นครั้งแรก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" ) ;

สำหรับฉันนี่เป็นสิ่งที่ดี


243
เตรียมพร้อมที่จะรับคำตอบ "Use JodaTime" จำนวนมาก ...
JuanZe

3
@ Ice09: หากเอกสาร API สำหรับ DateTimeFormat นั้นถูกต้อง (เอกสารของ JoDa อาจทำให้เข้าใจผิดผิดหรือไม่สมบูรณ์แม้ว่า) รูปแบบที่คุณใช้ใน "คำตอบ" ของคุณเองนั้นไม่เข้ากันกับ ISO8601
jarnbjo

21
ฉันไม่แน่ใจว่าเมื่อมีการเพิ่มสิ่งนี้ แต่ 'X' จะปรากฏขึ้นเพื่อแก้ปัญหานี้ใน SimpleDateFormat รูปแบบ "yyyy-MM-dd'T'HH: mm: ssX" แยกวิเคราะห์ตัวอย่างในคำถามได้สำเร็จ
mlohbihler

12
'X' เปิดให้บริการตั้งแต่ Java 7
Lars Grammel

3
Java 8 ทำให้มันง่าย! Adam มีอัญมณีซ่อนเร้นอยู่ในคำตอบด้านล่าง: stackoverflow.com/a/27479533/1262901
Fabian Keller

คำตอบ:


477

ขออภัยรูปแบบเขตเวลาที่มีให้กับSimpleDateFormat (Java 6 และรุ่นก่อนหน้า) ไม่สอดคล้องกับISO 8601 SimpleDateFormat เข้าใจสตริงของเขตเวลาเช่น "GMT + 01: 00" หรือ "+0100" ซึ่งเป็นหลังตามRFC # 822822

แม้ว่า Java 7 จะเพิ่มการรองรับตัวบอกเขตเวลาตามมาตรฐาน ISO 8601 แต่ SimpleDateFormat ก็ยังไม่สามารถแยกสตริงวันที่ที่สมบูรณ์ได้เนื่องจากมันไม่รองรับส่วนเสริม

การจัดรูปแบบสตริงข้อมูลของคุณใหม่โดยใช้ regexp นั้นเป็นไปได้อย่างหนึ่ง แต่กฎการเปลี่ยนไม่ง่ายอย่างที่เป็นในคำถามของคุณ:

  • เขตเวลาบางโซนไม่เต็มเวลานอกUTCดังนั้นสตริงไม่จำเป็นต้องลงท้ายด้วย ": 00"
  • ISO8601 อนุญาตให้รวมจำนวนชั่วโมงเท่านั้นในเขตเวลาดังนั้น "+01" จึงเท่ากับ "+01: 00"
  • ISO8601 อนุญาตให้ใช้ "Z" เพื่อระบุ UTC แทน "+00: 00"

วิธีแก้ปัญหาที่ง่ายกว่าอาจใช้ตัวแปลงชนิดข้อมูลใน JAXB เนื่องจาก JAXB จะต้องสามารถแยกสตริงวันที่ ISO8601 ตามข้อกำหนด XML Schema javax.xml.bind.DatatypeConverter.parseDateTime("2010-01-01T12:00:00Z")จะให้Calendarวัตถุและคุณสามารถใช้ getTime () กับมันถ้าคุณต้องการDateวัตถุ

คุณอาจใช้Joda-Time ได้เช่นกัน แต่ฉันไม่รู้ว่าทำไมคุณถึงต้องกังวล


18
JAXB-solution เป็นแนวทางที่สร้างสรรค์จริงๆ! มันทำงานได้ดีฉันได้ทดสอบกับตัวอย่างของฉัน อย่างไรก็ตามสำหรับใครก็ตามที่ประสบปัญหาและได้รับอนุญาตให้ใช้ JodaTime ฉันจะแนะนำให้ใช้เพราะมันให้ความรู้สึกเป็นธรรมชาติมากกว่า แต่โซลูชันของคุณไม่ต้องการไลบรารีเพิ่มเติม (อย่างน้อยกับ Java 6)
Ice09

36
นี่คือสิ่งที่ตรงกันข้าม: ปฏิทิน c = GregorianCalendar.getInstance (); c.setTime (aDate); ส่งคืน javax.xml.bind.DatatypeConverter.printDateTime (c);
Alexander Ljungberg

4
จริงๆแล้วมันไม่ใช่เรื่องง่ายเลยที่คุณต้องเริ่มต้น jaxb datatypeConverter ฉันสิ้นสุดการใช้ DatatypeFactory ตัวเองเป็น DataTypeConverterImpl ทำภายใน ปวดหัวอะไร
gtrak

3
@Simon: ไม่เขตเวลาของหลักสูตรจะไม่ถูกละเว้น คุณต้องทำอะไรผิดพลาด หากคุณพิมพ์อักขระมากกว่าสองสามตัวแล้วบอกเราว่าคุณทำอะไรจริงๆใครบางคนอาจอธิบายให้คุณรู้
jarnbjo

4
@jarnbjo คุณเป็นคนแรกและคนเดียวที่ฉันได้พบที่ชอบมาตรฐานก่อน 1.8 คลาสเรียนวันที่ java มากกว่าเวลา joda ฉันพบว่า joda-time เป็นความสุขที่แท้จริงที่จะใช้โดยเฉพาะอย่างยิ่งเมื่อเปรียบเทียบกับ API มาตรฐานซึ่งเป็นสิ่งที่น่ารังเกียจ
NimChimpsky

245

วิธีที่ได้รับพรจากเอกสาร Java 7 :

DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
String string1 = "2001-07-04T12:08:56.235-0700";
Date result1 = df1.parse(string1);

DateFormat df2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
String string2 = "2001-07-04T12:08:56.235-07:00";
Date result2 = df2.parse(string2);

คุณสามารถค้นหาตัวอย่างเพิ่มเติมได้ในหัวข้อตัวอย่างที่SimpleDateFormat javadocJavadoc

UPD 02/13/2020:มีวิธีใหม่ในการทำเช่นนี้ใน Java 8


7
คำตอบของคุณช่วยฉันในการแปลง ISODate ของ MongoDB เป็นวันที่ท้องถิ่น ความนับถือ.
บลูสกาย

9
@ b.long Java เพิ่มมากกว่าค่าคงที่สำหรับรูปแบบที่สอดคล้องกับ ISO 8601 Java มีกรอบงานใหม่ทั้งหมดสำหรับงานวันที่ซึ่งมีการสนับสนุนค่าเริ่มต้นในตัวสำหรับรูปแบบดังกล่าว ดูjava.timeเฟรมเวิร์กใหม่ใน Java 8 ซึ่งได้รับแรงบันดาลใจจากJoda-Timeแทนที่คลาส java.util.Date, .Calendar และ SimpleDateFormat ที่มีปัญหา
Basil Bourque

2
นี่หมายความว่าคุณไม่จำเป็นต้องรู้รูปแบบวันที่ล่วงหน้าหรือ ถ้าคุณต้องยอมรับstring1และstring2ไม่ทราบว่าคุณจะได้รับอะไร
Timmmm

16
'Z' ต้องอยู่ในเครื่องหมายคำพูด
kervin

7
@ kervin หาก Z อยู่ในเครื่องหมายอัญประกาศแล้วฟอร์แมตเตอร์จะไม่ค้นหาอักขระ Z โดยเฉพาะไม่ใช่สตริงออฟเซ็ตทั้งหมดที่สามารถเป็นตัวแทนได้ใช่ไหม ดูเหมือนว่าการอ้างอิง Z จะทำงานโดยบังเอิญเท่านั้นหากสตริงวันที่ของคุณเกิดขึ้นใน UTC
spaaarky21

201

โอเคคำถามนี้ได้รับคำตอบแล้ว แต่ฉันจะตอบคำถามต่อไป มันอาจช่วยใครซักคน

ฉันกำลังมองหาโซลูชันสำหรับ Android (API 7)

  • โจดาหมดคำถาม - มันใหญ่และทนทุกข์ทรมานจากการเริ่มต้นช้า นอกจากนี้ยังดูเหมือนว่าเป็น overkill ที่สำคัญสำหรับวัตถุประสงค์เฉพาะนั้น
  • คำตอบที่เกี่ยวข้อง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 เพื่อให้ปลอดภัยต่อเธรด)


2
บางทีสิ่งนี้ควรถูกทำให้เป็นคำถามของตัวเองพร้อมคำตอบของตัวเอง?
Thorbear

5
นี่เป็นหน้าแรกที่ฉันตะลึงเมื่อฉันมองหาคำตอบดังนั้นมันจึงเหมาะสม สำหรับนักพัฒนาจาวาส่วนใหญ่ Android นั้นไม่ใช่จาวาอย่างแน่นอน อย่างไรก็ตามในกรณีส่วนใหญ่หนึ่งทำงานได้เหมือนกันอื่น ๆ นักพัฒนา Android จำนวนมากจะค้นหา "java" เมื่อค้นหาสิ่งนี้
wrygiel

1
โปรดทราบว่านี่ไม่ได้มีไว้สำหรับการแก้ปัญหาเป็นมิลลิวินาที เพิ่มได้ง่าย
Sky Kelsey

6
ฉันต้องเพิ่ม. SSS เป็นวินาทีเศษส่วน แต่ใช้งานได้ดีมาก ทำไมคุณถึงทำs = s.substring(0, 22) + s.substring(23);- ฉันไม่เห็นประเด็นในนี้
Dori

1
input = input.replaceAll ("[Zz]", "+0000"); จะทำงานด้วยและการดำเนินการซับสตริงสามารถหลีกเลี่ยงได้
Javanator

115

java.time

java.time API (ที่สร้างขึ้นใน Java 8 และต่อมา) นี้จะทำให้ง่ายขึ้นเล็กน้อย

หากคุณทราบว่าอินพุตอยู่ในUTCเช่นZ(สำหรับ Zulu) ในตอนท้ายInstantชั้นสามารถแยกวิเคราะห์

java.util.Date date = Date.from( Instant.parse( "2014-12-12T10:39:40Z" ));

หากอินพุตของคุณอาจเป็นค่าออฟเซ็ตจาก UTCแทนค่าUTC ที่ระบุโดยZ(ซูลู) ในตอนท้ายให้ใช้OffsetDateTimeคลาสเพื่อแยกวิเคราะห์

OffsetDateTime odt = OffsetDateTime.parse( "2010-01-01T12:00:00+01:00" );

จากนั้นแยกInstantและแปลงไปโดยการเรียกjava.util.Datefrom

Instant instant = odt.toInstant();  // Instant is always in UTC.
java.util.Date date = java.util.Date.from( instant );

8
คำตอบนี้ทำงานหนักเกินไป java.util.Date ตามคำนิยามไม่มีเขตเวลา ดังนั้นไม่จำเป็นต้องสำหรับทุกรหัสที่โซนเวลาที่เกี่ยวข้องคือLocalDateTimeและและZoneId atZoneหนึ่งซับง่ายนี้จะทำ:java.util.Date date = Date.from( ZonedDateTime.parse( "2014-12-12T10:39:40Z" ).toInstant() );
Basil Bourque

5
@BasilBourque สิ่งนี้มีความซับซ้อนเกินความจำเป็น: Date.from(Instant.parse("2014-12-12T10:39:40Z" ));ก็เพียงพอแล้ว
assylias

3
@assylias คุณถูกต้อง แต่จะใช้งานได้เฉพาะเมื่อสตริงวันที่เป็นโซน UTC, ISO8601 ช่วยให้เขตเวลาใด ๆ ...
Adam

2
@ อดัมฉันไม่ดี - ฉันไม่ได้ตระหนักว่าคำถามนั้นกว้างกว่าตัวอย่างของคุณ ความคิดเห็นด้านข้างและOffsetDateTimeเพียงพอที่จะวิเคราะห์ ISO8601 (ซึ่งไม่มีข้อมูลเขตเวลา แต่เป็นการชดเชยเท่านั้น)
assylias

1
@assylias ขอบคุณสำหรับความคิดเห็นของคุณเกี่ยวกับการให้Instantทำการแยกวิเคราะห์ แม้ว่าจะไม่เพียงพอสำหรับคำถามนี้ แต่ก็เป็นข้อแตกต่างที่สำคัญที่ชี้ให้เห็น ดังนั้นฉันจึงเพิ่มตัวอย่างที่สองของรหัส อ๊ะเพิ่งสังเกตเห็นว่านี่ไม่ใช่คำตอบของฉัน ฉันหวังว่าอดัมอนุมัติ
Basil Bourque

67

ห้องสมุดแจ็คสัน DataBindนอกจากนี้ยังมีระดับ ISO8601DateFormatที่ไม่ว่า (การดำเนินงานที่เกิดขึ้นจริงใน ISO8601Utils

ISO8601DateFormat df = new ISO8601DateFormat();
Date d = df.parse("2010-07-28T22:25:51Z");

2015-08-11T13:10:00มันล้มเหลวที่จะแยกวันนี้: String index out of range: 19ฉันได้รับ ดูรหัสดูเหมือนว่าจะต้องระบุมิลลิวินาทีและเขตเวลา สิ่งเหล่านั้นควรเป็นทางเลือก
Timmmm

2
[yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh:mm]]ที่จะพูดเอกสารรูปแบบแยกเป็น: กล่าวอีกนัยหนึ่งมิลลิวินาทีเป็นตัวเลือก แต่เขตเวลาเป็นสิ่งจำเป็น
david_p

2
อาใช่จริง ๆ แล้วดูเหมือนว่าคุณจะถูก ถึงกระนั้นฉันก็ค่อนข้างมั่นใจว่า ISO8601 ช่วยให้คุณสามารถข้ามเขตเวลาได้ JodaTime ทำงานแม้ว่า:new DateTime("2015-08-11T13:10:00").toDate()
Timmmm

3
คลาสนั้นเลิกใช้แล้วอันใหม่คือ StdDateFormat มิฉะนั้นมันจะทำงานเหมือนเดิม
JohnEye

51

TL; DR

OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" )

ใช้ java.time

java.timeใหม่แพ็คเกจใน Java 8 และใหม่กว่าได้รับแรงบันดาลใจจาก Joda-Time

OffsetDateTimeชั้นหมายถึงช่วงเวลาในระยะเวลาที่มีการชดเชยจาก UTCโซนเวลา แต่ไม่

OffsetDateTime odt = OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" );

การโทรtoStringสร้างสตริงในรูปแบบ ISO 8601 มาตรฐาน:

2010-01-01T12: 00 + 01: 00

หากต้องการดูค่าเดียวกันผ่านเลนส์ของ UTC ที่แยกInstantหรือปรับชดเชยจากการ+01:0000:00

Instant instant = odt.toInstant();  

…หรือ…

OffsetDateTime odtUtc = odt.withOffsetSameInstant( ZoneOffset.UTC );

ปรับเป็นเขตเวลาหากต้องการ โซนเวลาเป็นประวัติศาสตร์ของการชดเชยจากที่ UTCค่าสำหรับภูมิภาคที่มีชุดของกฎสำหรับการจัดการความผิดปกติเช่นปรับเวลาตามฤดูกาล (DST) ดังนั้นให้ใช้เขตเวลาแทนการชดเชยเมื่อทำได้

ZonedDateTime zonedDateTimeMontréal = odt.atZoneSameInstant( ZoneId.of( "America/Montreal" ) );

เกี่ยวกับjava.time

java.timeกรอบถูกสร้างขึ้นใน Java 8 และต่อมา ชั้นเรียนเหล่านี้แย่งลำบากเก่ามรดกเรียนวันที่เวลาเช่นjava.util.Date, และCalendarSimpleDateFormat

Joda เวลาโครงการขณะนี้อยู่ในโหมดการบำรุงรักษาให้คำแนะนำแก่การโยกย้ายไปยังjava.timeชั้นเรียน

ต้องการเรียนรู้เพิ่มเติมโปรดดูที่ออราเคิลกวดวิชา และค้นหา Stack Overflow สำหรับตัวอย่างและคำอธิบายมากมาย สเปกJSR 310

คุณสามารถแลกเปลี่ยนวัตถุjava.timeโดยตรงกับฐานข้อมูลของคุณ ใช้ไดรเวอร์ JDBC ที่สอดคล้องกับJDBC 4.2หรือใหม่กว่า ไม่จำเป็นต้องใช้สตริงไม่จำเป็นต้องมีjava.sql.*คลาส

จะรับคลาส java.time ได้ที่ไหน?

  • Java SE 8 , Java SE 9 , Java SE 10และใหม่กว่า
    • Built-in
    • เป็นส่วนหนึ่งของ Java API มาตรฐานที่มีการใช้งานแบบรวม
    • Java 9 เพิ่มคุณสมบัติและการแก้ไขเล็กน้อย
  • Java SE 6และ Java SE 7
    • มากของการทำงาน java.time จะกลับรังเพลิง Java 6 และ 7 ในThreeTen-ย้ายกลับ
  • Android
    • การใช้งานชุดบันเดิล Android รุ่นต่อมาของคลาส java.time
    • สำหรับก่อนหน้านี้ Android (<26) ที่ThreeTenABPโครงการปรับThreeTen-ย้ายกลับ (ดังกล่าวข้างต้น) ดูวิธีการใช้ ThreeTenABP … .

โครงการThreeTen-Extraขยาย java.time ด้วยคลาสเพิ่มเติม โครงการนี้เป็นพื้นฐานที่พิสูจน์ได้สำหรับการเพิ่มเติมในอนาคตไปยัง java.time คุณอาจพบว่าการเรียนที่มีประโยชน์บางอย่างที่นี่เช่นInterval, YearWeek, YearQuarterและอื่น ๆ อีกมากมาย



27

สำหรับ Java เวอร์ชัน 7

คุณสามารถปฏิบัติตามเอกสารของ Oracle: http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

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

ซึ่งหมายความว่าเขตจะต้อง ตามมาตรฐาน ISO 8601 มันเป็นตัวเลือก เช่นเดียวกับวินาทีเป็นต้นดังนั้นนี่จึงแยกวิเคราะห์ชุดย่อยเฉพาะของ ISO 8601 เท่านั้น
Timmmm

1
ใช้งานได้ดีกับ Java 1.8
Thiago Pereira

20

โซลูชัน DatatypeConverter ใช้ไม่ได้กับ VMs ทั้งหมด งานต่อไปนี้สำหรับฉัน:

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

ฉันพบว่า joda ไม่ทำงานนอกกรอบ (โดยเฉพาะสำหรับตัวอย่างที่ฉันให้ไว้ข้างต้นกับเขตเวลาในวันที่ซึ่งควรจะถูกต้อง)


15

ฉันคิดว่าเราควรใช้

DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")

สำหรับวันที่ 2010-01-01T12:00:00Z


5
ทำไมนี่เป็นคำตอบที่ดีกว่าคำตอบอื่น ๆ รวมถึงคำตอบที่ยอมรับกับ upvotes 76?
Erick Robertson

3
@ErickRobertson: มันง่ายไม่ซับซ้อนไม่มีการแปลงและคนส่วนใหญ่ไม่สนใจเขตเวลา
TWiStErRob

7
จุดไม่ค่อยทำงานกับเวลาถ้าคุณไม่สนใจเขตเวลา!
Dori

16
นี้ละเว้นเขตสมบูรณ์ ใช้สิ่งนี้จนกระทั่งฉันรู้ว่าสิ่งนี้กำลังเกิดขึ้นดังนั้นฉันจึงเปลี่ยนไปใช้ JodaTime
Joshua Pinter

3
การทิ้งเขตเวลาจะทำให้เกิดข้อผิดพลาดในบางจุด
Bart van Kuik

11

เริ่มต้นจาก Java 8 มีวิธีใหม่ที่สนับสนุนอย่างเป็นทางการในการทำสิ่งนี้:

    String s = "2020-02-13T18:51:09.840Z";
    TemporalAccessor ta = DateTimeFormatter.ISO_INSTANT.parse(s);
    Instant i = Instant.from(ta);
    Date d = Date.from(i);

2
หากสตริงอยู่ในรูปแบบทันทีโดยมีส่วนท้ายZเป็นออฟเซ็ตเราไม่จำเป็นต้องระบุอย่างชัดเจน Instant i = Instant.parse(s);เพียงแค่ สตริงในคำถามมี+01:00ซึ่งในกรณีDateTimeFormatter.ISO_INSTANTนี้ไม่ทำงาน (อย่างน้อยไม่ได้อยู่ใน Java 11 ของฉัน)
Ole VV

2
@ OleV.V คุณสามารถใช้ISO_OFFSET_DATE_TIMEเพื่อจัดรูปแบบวันที่ด้วยออฟเซ็ตเช่น+01:00( docs.oracle.com/javase/8/docs/api/java/time/format/ … )
Lucas Basquerotto

1
นั่นเป็นความจริง @LucasBasquerotto แม้ว่าจะไม่ได้กล่าวถึงตัวจัดรูปแบบนั้นอย่างชัดเจนคำตอบของ Adamและโดย Basil Bourqueก็ทำสิ่งที่คล้ายกันอยู่แล้ว
Ole VV

10

อีกวิธีที่ง่ายมากในการวิเคราะห์การประทับเวลา 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());
  }
}

6

java.time

โปรดทราบว่าใน Java 8 คุณสามารถใช้คลาสjava.time.ZonedDateTimeและparse(CharSequence text)วิธีการคงที่


สตริงอินพุตในคำถามมีเพียงออฟเซ็ตจาก UTC ไม่ใช่โซนเต็มเวลา ดังนั้นInstantและมีความเหมาะสมที่นี่ไม่ได้ZonedDateTime ZonedDateTime
Basil Bourque

6

วิธีแก้ปัญหาสำหรับ 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'
เราจำเป็นที่จะปรับวัน ISO8601 การอ่านรูปแบบใน Java SimpleDateFormat6

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ด้วย+0000] หรือ [ +01:00ด้วย+0100] เมื่อเกิดข้อผิดพลาดใน Java 6 (คุณสามารถตรวจจับเวอร์ชัน Java และแทนที่ try / catch ด้วย if statement)


ไม่คลาสเรียนนอกเวลาเก่าที่มีปัญหาเช่นDateและSimpleDateFormatได้รับการออกแบบไม่ดีสับสนและมีข้อบกพร่อง ตอนนี้เป็นแบบดั้งเดิมแทนที่ด้วยคลาส java.time ที่สร้างขึ้นใน Java 8 และใหม่กว่า สำหรับ Java 6 และ Java 7 มากของการทำงาน java.time คือกลับรังเพลิงในThreeTen-ย้ายกลับโครงการ ดีกว่าการเพิ่มไลบรารี่นั้นในแอปของคุณดีกว่าการใช้คลาสเดิม โซลูชันบรรทัดเดียวใน java.time:OffsetDateTime.parse( "2010-01-01T12:00:00+01:00" )
Basil Bourque

5

ฉันประสบปัญหาเดียวกันและแก้ไขได้ด้วยรหัสต่อไปนี้

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

มันทำงานได้ดีสำหรับฉัน


สิ่งที่ฉันต้องการโดยไม่ต้องใช้ joda-time, XML api หรืออย่างอื่น เพียงแค่รูปแบบที่ถูกต้อง
Philippe Gioseffi

4

นอกจากนี้คุณสามารถใช้คลาสต่อไปนี้ -

org.springframework.extensions.surf.util.ISO8601DateFormat


Date date = ISO8601DateFormat.parse("date in iso8601");

ลิงก์ไปยัง Java Doc - ลำดับชั้นสำหรับแพ็กเกจ org.springframework.extensions.surf.maven.plugin.util


เลิกใช้แล้ว: ใช้ com.fasterxml.jackson.databind.util.StdDateFormat แทน
vesion

4

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"

หากคุณต้องจัดการกับสิ่งเหล่านั้นฉันขอแนะนำให้ใช้ regex ก่อนเพื่อดมกลิ่นไวยากรณ์

มีรายการตัวอย่างที่ดีของ ISO 8601 ที่นี่พร้อมตัวเรือนจำนวนมาก: https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ฉัน ไม่ทราบว่ามีคลาส Java ใดที่สามารถรับมือกับมันได้ทั้งหมด


OffsetDateTimeจะทำและจับคู่แนวคิดวันที่และเวลากับการชดเชยที่ดีกว่า
Ole VV

เฮ้ @ OleV.V ขอบคุณสำหรับคำแนะนำ น่าเศร้าที่ไม่มี: OffsetDateTime.parse () จะส่งข้อยกเว้นสำหรับสตริง ISO 8601 ที่ถูกต้องเช่น "2017-09-13T10: 36: 40 + 01" หรือ "2017-09-13"
Daniel Winterstein

ฉันหมายเพียงแค่จะบอกว่าจับตัวอย่างที่คุณจัดการกับOffsetDateTime ZonedDateTimeฉันเชื่อว่ามันไม่ได้จัดการกับตัวอย่างใด ๆ ที่ZonedDateTimeไม่ได้ทำ ในแง่นั้นมันไม่ได้ปรับปรุงเลย (แต่ก็ไม่แย่ไปกว่านั้น) ขอโทษฉันไม่ชัดเจนอย่างสมบูรณ์
Ole VV

1
นี่ควรเป็นคำตอบที่ได้รับการยอมรับในปี 2020 เนื่องจากสภาพของสิ่งต่าง ๆ
slashCoder

3

Apache Jackrabbitใช้รูปแบบ ISO 8601 สำหรับวันที่คงอยู่และมีคลาสตัวช่วยในการวิเคราะห์:

org.apache.jackrabbit.util.ISO8601

มาพร้อมกับjackrabbit-JCR-คอมมอนส์


ในขณะที่ชุดย่อยของ Jackrabbit อาจทำงานได้ แต่ก็เหมาะสมกว่าที่จะใช้ไลบรารีที่สร้างขึ้นโดยมีวัตถุประสงค์เต็มรูปแบบ ใน Java ที่หมายถึง Joda-Time หรือ java.time
Basil Bourque

3

ตามที่คนอื่น ๆ ได้กล่าวถึง Android ไม่มีวิธีที่ดีในการสนับสนุนการแยก / จัดรูปแบบวันที่ ISO 8601 โดยใช้คลาสที่รวมอยู่ใน SDK ฉันเขียนโค้ดนี้หลายครั้งดังนั้นในที่สุดฉันก็สร้าง Gist ซึ่งรวมคลาส DateUtils ที่รองรับการจัดรูปแบบและแยกวิเคราะห์ ISO 8601 และ RFC 1123 สรุปสาระสำคัญรวมถึงกรณีทดสอบแสดงสิ่งที่สนับสนุน

https://gist.github.com/mraccola/702330625fad8eebe7d3


2

SimpleDateFormat สำหรับ JAVA 1.7 มีรูปแบบเท่ ๆ สำหรับรูปแบบ ISO 8601

คลาส SimpleDateFormat

นี่คือสิ่งที่ฉันทำ:

Date d = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
         Locale.ENGLISH).format(System.currentTimeMillis());

2
Zในสตริงรูปแบบไม่ใช่เขตเวลา ISO 8601 คุณควรใช้X(หรือXXหรือXXX) ถ้าคุณต้องการเขตเวลา ISO 8601
Vojta

d เป็นประเภท String
Tim Child

1

ทำเช่นนี้:

public static void main(String[] args) throws ParseException {

    String dateStr = "2016-10-19T14:15:36+08:00";
    Date date = javax.xml.bind.DatatypeConverter.parseDateTime(dateStr).getTime();

    System.out.println(date);

}

นี่คือผลลัพธ์:

พุธ 19 ตุลาคม 15:15:36 CST 2016



1

ฉันกำลังประหลาดใจที่ไม่ได้ Java ห้องสมุดหนึ่งสนับสนุนทุกมาตรฐาน ISO 8601 รูปแบบวันที่ตามhttps://en.wikipedia.org/wiki/ISO_8601 Joda DateTime ให้การสนับสนุนพวกเขาส่วนใหญ่ แต่ไม่ใช่ทั้งหมดและด้วยเหตุนี้ฉันจึงเพิ่มตรรกะที่กำหนดเองเพื่อจัดการกับพวกเขาทั้งหมด นี่คือการดำเนินการของฉัน

import java.text.ParseException;
import java.util.Date;

import org.apache.commons.lang3.time.DateUtils;
import org.joda.time.DateTime;

public class ISO8601DateUtils {
	
	/**
	 * It parses all the date time formats from https://en.wikipedia.org/wiki/ISO_8601 and returns Joda DateTime.
	 * Zoda DateTime does not support dates of format 20190531T160233Z, and hence added custom logic to handle this using SimpleDateFormat.
	 * @param dateTimeString ISO 8601 date time string
	 * @return
	 */
	public static DateTime parse(String dateTimeString) {
		try {
			return new DateTime( dateTimeString );
		} catch(Exception e) {
			try {
				Date dateTime = DateUtils.parseDate(dateTimeString, JODA_NOT_SUPPORTED_ISO_DATES);
				return new DateTime(dateTime.getTime());
			} catch (ParseException e1) {
				throw new RuntimeException(String.format("Date %s could not be parsed to ISO date", dateTimeString));
			}
		}
	}
  
  	private static String[] JODA_NOT_SUPPORTED_ISO_DATES = new String[] {
			// upto millis
			"yyyyMMdd'T'HHmmssSSS'Z'",
			"yyyyMMdd'T'HHmmssSSSZ",
			"yyyyMMdd'T'HHmmssSSSXXX",
			
			"yyyy-MM-dd'T'HHmmssSSS'Z'",
			"yyyy-MM-dd'T'HHmmssSSSZ",
			"yyyy-MM-dd'T'HHmmssSSSXXX",
			
			// upto seconds
			"yyyyMMdd'T'HHmmss'Z'",
			"yyyyMMdd'T'HHmmssZ",
			"yyyyMMdd'T'HHmmssXXX",
			
			"yyyy-MM-dd'T'HHmmss'Z'", 
			"yyyy-MM-dd'T'HHmmssZ",
			"yyyy-MM-dd'T'HHmmssXXX",
			
			// upto minutes
			"yyyyMMdd'T'HHmm'Z'",
			"yyyyMMdd'T'HHmmZ",
			"yyyyMMdd'T'HHmmXXX",

			"yyyy-MM-dd'T'HHmm'Z'",
			"yyyy-MM-dd'T'HHmmZ",
			"yyyy-MM-dd'T'HHmmXXX",
			
			//upto hours is already supported by Joda DateTime
	};
}


1

การทดสอบเล็กน้อยที่แสดงวิธีแยกวิเคราะห์วันที่ใน ISO8601 และ LocalDateTime ไม่ได้จัดการ DST

 @Test
    public void shouldHandleDaylightSavingTimes() throws ParseException {

        //ISO8601 UTC date format
        SimpleDateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");

        // 1 hour of difference between 2 dates in UTC happening at the Daylight Saving Time
        Date d1 = utcFormat.parse("2019-10-27T00:30:00.000Z");
        Date d2 = utcFormat.parse("2019-10-27T01:30:00.000Z");

        //Date 2 is before date 2
        Assert.assertTrue(d1.getTime() < d2.getTime());
        // And there is 1 hour difference between the 2 dates
        Assert.assertEquals(1000*60*60, d2.getTime() - d1.getTime());

        //Print the dates in local time
        SimpleDateFormat localFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm z Z", Locale.forLanguageTag("fr_CH"));
        localFormat.setTimeZone(TimeZone.getTimeZone("Europe/Zurich"));

        //Both dates are at 02h30 local time (because of DST), but one is CEST +0200 and the other CET +0100 (clock goes backwards)
        Assert.assertEquals("2019-10-27 02:30 CEST +0200", localFormat.format(d1));
        Assert.assertEquals("2019-10-27 02:30 CET +0100", localFormat.format(d2));

        //Small test that shows that LocalDateTime does not handle DST (and should not be used for storing timeseries data)
        LocalDateTime ld1 = LocalDateTime.ofInstant(d1.toInstant(), ZoneId.of("Europe/Zurich"));
        LocalDateTime ld2 = LocalDateTime.ofInstant(d2.toInstant(), ZoneId.of("Europe/Zurich"));

        //Note that a localdatetime does not handle DST, therefore the 2 dates are the same
        Assert.assertEquals(ld1, ld2);

        //They both have the following local values
        Assert.assertEquals(2019, ld1.getYear());
        Assert.assertEquals(27, ld1.getDayOfMonth());
        Assert.assertEquals(10, ld1.getMonthValue());
        Assert.assertEquals(2, ld1.getHour());
        Assert.assertEquals(30, ld1.getMinute());
        Assert.assertEquals(0, ld1.getSecond());

    }

3
FYI, ลำบากชะมัดเรียนวันที่เวลาเช่นjava.util.Date, java.util.Calendarและjava.text.SimpleDateFormatตอนนี้มรดกแทนที่โดยjava.timeชั้นเรียนที่สร้างขึ้นใน Java 8 และต่อมา ดูการสอนโดยออราเคิล
Basil Bourque

คุณถูกต้องที่LocalDateTimeไม่ได้จัดการเวลาฤดูร้อน (DST) เนื่องจากไม่ได้จัดการโซนเวลาเลย ZonedDateTimeเพื่อที่เราจำเป็นที่จะต้อง การเสนอDateและSimpleDateFormatเป็น - IMHO ไม่ดี
Ole VV

1
ZonedDateTime ใช้งานได้จริง และ java.time.Instant ยังเป็นทางเลือกที่ดีในการจัดการ DST ฉันรู้ว่า java.util.Date เลิกใช้แล้วและไม่ควรใช้ แต่ฉันเพิ่งตอบคำถามเดิม: วิธีแปลงสตริงใน 8601 เป็น java.util.date ....
ddtxra

0

ฉันมีความต้องการที่คล้ายกัน: ฉันต้องสามารถแยกวิเคราะห์วันที่ที่สอดคล้องกับ ISO8601 โดยไม่ทราบรูปแบบที่แน่นอนล่วงหน้าและฉันต้องการโซลูชันที่มีน้ำหนักเบาซึ่งจะทำงานบน Android ด้วย

เมื่อฉันตอบสนองความต้องการของฉันฉันสะดุดกับคำถามนี้และสังเกตว่า AFAIU ไม่มีคำตอบที่ตรงกับความต้องการของฉันทั้งหมด ดังนั้นฉันจึงพัฒนาjISO8601และผลักมันไปที่ศูนย์กลางของ maven

เพียงเพิ่มคุณpom.xml:

<dependency>
  <groupId>fr.turri</groupId>
  <artifactId>jISO8601</artifactId>
  <version>0.2</version>
</dependency>

แล้วคุณจะไปดี:

import fr.turri.jiso8601.*;
...
Calendar cal = Iso8601Deserializer.toCalendar("1985-03-04");
Date date = Iso8601Deserializer.toDate("1985-03-04T12:34:56Z");

หวังว่ามันจะช่วย


0

หากต้องการจัดรูปแบบวันที่แบบนี้สิ่งต่อไปนี้ใช้ได้สำหรับฉันในแอปพลิเคชันที่ใช้ Java 6 มีDateFormatคลาสJacksonThymeleafISO8601DateFormatในโครงการ thymeleaf ซึ่งแทรกโคลอนที่หายไป:

https://github.com/thymeleaf/thymeleaf/blob/40d27f44df7b52eda47d1bc6f1b3012add6098b3/src/main/java/org/thymeleaf/standard/serializer/StandardJavaScriptSerializer.java

ฉันใช้มันสำหรับความเข้ากันได้กับรูปแบบวันที่ ECMAScript


-1

ฟังก์ชันการทำงานพื้นฐาน: @wrygiel

ฟังก์ชันนี้สามารถแปลงรูปแบบ ISO8601 เป็น Java Date ซึ่งสามารถจัดการค่าออฟเซ็ต ตามคำนิยามของ ISO 8601การชดเชยสามารถถูกกล่าวถึงในรูปแบบที่แตกต่างกัน

±[hh]:[mm]
±[hh][mm]
±[hh]

Eg:  "18:30Z", "22:30+04", "1130-0700", and "15:00-03:30" all mean the same time. - 06:30PM UTC

ชั้นนี้มีวิธีการแบบคงที่ในการแปลง

  • สตริง ISO8601 ถึงวันที่ (Local TimeZone) วัตถุ
  • วันที่สตริง ISO8601
  • การปรับเวลาตามฤดูกาลจะคำนวณโดยอัตโนมัติ

ตัวอย่างสตริง ISO8601

/*       "2013-06-25T14:00:00Z";
         "2013-06-25T140000Z";
         "2013-06-25T14:00:00+04";
         "2013-06-25T14:00:00+0400";
         "2013-06-25T140000+0400";
         "2013-06-25T14:00:00-04";
         "2013-06-25T14:00:00-0400";
         "2013-06-25T140000-0400";*/


public class ISO8601DateFormatter {

private static final DateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
private static final DateFormat DATE_FORMAT_2 = new SimpleDateFormat("yyyy-MM-dd'T'HHmmssZ");
private static final String UTC_PLUS = "+";
private static final String UTC_MINUS = "-";

public static Date toDate(String iso8601string) throws ParseException {
    iso8601string = iso8601string.trim();
    if(iso8601string.toUpperCase().indexOf("Z")>0){
        iso8601string = iso8601string.toUpperCase().replace("Z", "+0000");
    }else if(((iso8601string.indexOf(UTC_PLUS))>0)){
        iso8601string = replaceColon(iso8601string, iso8601string.indexOf(UTC_PLUS));
        iso8601string = appendZeros(iso8601string, iso8601string.indexOf(UTC_PLUS), UTC_PLUS);
    }else if(((iso8601string.indexOf(UTC_MINUS))>0)){
        iso8601string = replaceColon(iso8601string, iso8601string.indexOf(UTC_MINUS));
        iso8601string = appendZeros(iso8601string, iso8601string.indexOf(UTC_MINUS), UTC_MINUS);
    }

    Date date = null;
    if(iso8601string.contains(":"))
        date = DATE_FORMAT_1.parse(iso8601string);
    else{
        date = DATE_FORMAT_2.parse(iso8601string);
    }
    return date;
}

public static String toISO8601String(Date date){
    return DATE_FORMAT_1.format(date);
}

private static String replaceColon(String sourceStr, int offsetIndex){
    if(sourceStr.substring(offsetIndex).contains(":"))
        return sourceStr.substring(0, offsetIndex) + sourceStr.substring(offsetIndex).replace(":", "");
    return sourceStr;
}

private static String appendZeros(String sourceStr, int offsetIndex, String offsetChar){
    if((sourceStr.length()-1)-sourceStr.indexOf(offsetChar,offsetIndex)<=2)
        return sourceStr + "00";
    return sourceStr;
}

}


2
ระวัง - DateFormat และคลาสที่ได้รับไม่รองรับมัลติเธรด! การใช้วัตถุ SimpleDateFormat คงที่เช่น DATE_FORMAT_1 และ DATE_FORMAT_2 หมายความว่าหลายเธรดที่เรียกใช้ฟังก์ชัน ISO8601DateFormatter จะใช้วัตถุ DateFormat เดียวกันร่วมกัน สิ่งนี้นำไปสู่ความเสียหายของข้อมูลและวันที่ไม่ถูกต้องถูกส่งคืนจากการโทร DateFormat ในการแก้ไขปัญหานี้คุณควรสร้างค่าคงที่ของสตริงรูปแบบและสร้างตัวแปร SimpleDateFormat เฉพาะที่ทุกเวลาที่ต้องการ สิ่งนี้จะช่วยให้มั่นใจว่าแต่ละวัตถุจะถูกใช้โดยหนึ่งเธรด
Theo

การแก้ไขที่ดีกว่าสำหรับความปลอดภัยของเธรดคือแทนที่จะใช้ไลบรารีวันที่ที่สร้างขึ้นเพื่อความปลอดภัยของเธรดแทน ใน Java นั้นโลกอาจเป็น Joda-Time หรือ java.time
Basil Bourque

-1

ดูเหมือนว่าจะทำงานได้ดีที่สุดสำหรับฉัน:

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 เป็นจาวา ฉันพบการทำงานด้านบนพร้อมกับข้อเสนอแนะ มีตัวอย่างบางส่วนที่ใช้ SimpleDateFormat ที่อยู่ใกล้ แต่ดูเหมือนจะไม่เป็นเซตย่อยตามที่แนะนำโดย:

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

และสนับสนุนโดย PLIST และ JavaScript Strings และนั่นคือสิ่งที่ฉันต้องการ

นี่น่าจะเป็นสตริง ISO8601 ที่พบได้บ่อยที่สุดและเซตย่อยที่ดี

ตัวอย่างที่พวกเขาให้คือ:

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.

ฉันยังมีรุ่นที่รวดเร็ว:

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

ฉันไม่ได้ทำการเปรียบเทียบ แต่ฉันเดาว่ามันจะเร็วมาก ดูเหมือนว่าจะทำงาน :)

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

-2

ฉันคิดว่าสิ่งที่ผู้คนจำนวนมากต้องการทำคือแจงสตริงวันที่ของ JSON มีโอกาสที่ดีหากคุณมาที่หน้านี้ซึ่งคุณอาจต้องการแปลงวันที่ JavaScript JSON เป็นวันที่ Java

วิธีแสดงสตริงวันที่ของ JSON มีลักษณะดังนี้:

    var d=new Date();
    var s = JSON.stringify(d);

    document.write(s);
    document.write("<br />"+d);


    "2013-12-14T01:55:33.412Z"
    Fri Dec 13 2013 17:55:33 GMT-0800 (PST)

สตริงวันที่ของ JSON คือ 2013-12-14T01: 55: 33.412Z

วันที่ไม่ได้อยู่ในข้อมูลจำเพาะของ JSON ต่อการพูด แต่ข้างต้นเป็นรูปแบบ ISO 8601 ที่เฉพาะเจาะจงมากในขณะที่ ISO_8601 นั้นมีขนาดใหญ่กว่ามากและนั่นเป็นเพียงส่วนย่อยเท่านั้น

ดูhttp://www.json.org ดูhttp://en.wikipedia.org/wiki/ISO_8601 ดูhttp://www.w3.org/TR/NOTE-datetime

ในขณะที่มันเกิดขึ้นฉันเขียน JSON parser และ PLIST parser ซึ่งทั้งสองใช้ ISO-8601 แต่ไม่ใช่บิตเดียวกัน

/*
    var d=new Date();
    var s = JSON.stringify(d);

    document.write(s);
    document.write("<br />"+d);


    "2013-12-14T01:55:33.412Z"
    Fri Dec 13 2013 17:55:33 GMT-0800 (PST)


 */
@Test
public void jsonJavaScriptDate() {
    String test =  "2013-12-14T01:55:33.412Z";

    Date date = Dates.fromJsonDate ( test );
    Date date2 = Dates.fromJsonDate_ ( test );

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

    puts (date);
}

ฉันเขียนสองวิธีในการทำสิ่งนี้สำหรับโครงการของฉัน หนึ่งมาตรฐานหนึ่งเร็ว

อีกครั้งสตริงวันที่ JSON เป็นการใช้งานที่เฉพาะเจาะจงมากของ ISO 8601 ....

(ฉันโพสต์ข้อความอื่นในคำตอบอื่นซึ่งควรใช้กับวันที่ PLIST ซึ่งเป็นรูปแบบ ISO 8601 ที่ต่างออกไป)

วันที่ JSON เป็นดังนี้:

public static Date fromJsonDate_( String string ) {

    try {

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


}

ไฟล์ PLIST (ASCII ไม่ใช่ GNUNext) ก็ใช้ ISO 8601 เช่นกัน แต่ไม่มีมิลลิวินาทีดังนั้น ... ไม่ใช่วันที่ ISO-8601 ทั้งหมดเหมือนกัน (อย่างน้อยฉันก็ไม่พบคนที่ใช้ milis เลยและ parser ที่ฉันได้เห็นข้ามเขตเวลาโดยสิ้นเชิง OMG)

ตอนนี้สำหรับรุ่นที่รวดเร็ว (คุณสามารถค้นหาได้ใน Boon)

public static Date fromJsonDate( String string ) {

    return fromJsonDate ( Reflection.toCharArray ( string ), 0, string.length () );

}

โปรดทราบว่า Reflection.toCharArray ใช้ไม่ปลอดภัยหากมี แต่ใช้ค่าเริ่มต้นเป็น string.toCharArray หากไม่ใช่

(คุณสามารถนำมันออกมาจากตัวอย่างโดยแทนที่ Reflection.toCharArray (string) ด้วย string.toCharArray ()

public static Date fromJsonDate( char[] charArray, int from, int to ) {

    if (isJsonDate ( charArray, from, to )) {
        int year = CharScanner.parseIntFromTo ( charArray, from + 0, from + 4 );
        int month = CharScanner.parseIntFromTo ( charArray,  from +5,  from +7 );
        int day = CharScanner.parseIntFromTo ( charArray,  from +8,  from +10 );
        int hour = CharScanner.parseIntFromTo ( charArray,  from +11,  from +13 );

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

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

        int miliseconds = CharScanner.parseIntFromTo ( charArray,  from +20,  from +23 );

        TimeZone tz = TimeZone.getTimeZone ( "GMT" );


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

    }   else {
        return null;
    }

}

isJsonDate มีการใช้งานดังนี้:

public static boolean isJsonDate( char[] charArray, int start, int to ) {
    boolean valid = true;
    final int length = to -start;

    if (length != JSON_TIME_LENGTH) {
        return false;
    }

    valid &=  (charArray [ start + 19 ]  == '.');

    if (!valid) {
        return false;
    }


    valid &=  (charArray[  start +4 ]  == '-') &&
            (charArray[  start +7 ]  == '-') &&
            (charArray[  start +10 ] == 'T') &&
            (charArray[  start +13 ] == ':') &&
            (charArray[  start +16 ] == ':');

    return valid;
}

อย่างไรก็ตาม ... ฉันเดาได้ว่ามีคนไม่กี่คนที่มาที่นี่ .. อาจกำลังมองหาสตริงวันที่ JSON และถึงแม้ว่ามันจะเป็นวันที่ ISO-8601 แต่ก็เป็นวันที่เจาะจงมากที่ต้องใช้การแจงเฉพาะ

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

ดูhttps://github.com/RichardHightower/boon Boon มีตัวแยกคำแบบ PLIST (ASCII) และตัวแยกวิเคราะห์ JSON

ตัวแยกวิเคราะห์ JSON เป็นตัวแยกวิเคราะห์ Java JSON ที่เร็วที่สุดที่ฉันรู้

ผ่านการตรวจสอบโดย dudes ประสิทธิภาพของ Gatling

https://github.com/gatling/json-parsers-benchmark

Benchmark                               Mode Thr     Count  Sec         Mean   Mean error        Units
BoonCharArrayBenchmark.roundRobin      thrpt  16        10    1   724815,875    54339,825    ops/s
JacksonObjectBenchmark.roundRobin      thrpt  16        10    1   580014,875   145097,700    ops/s
JsonSmartBytesBenchmark.roundRobin     thrpt  16        10    1   575548,435    64202,618    ops/s
JsonSmartStringBenchmark.roundRobin    thrpt  16        10    1   541212,220    45144,815    ops/s
GSONStringBenchmark.roundRobin         thrpt  16        10    1   522947,175    65572,427    ops/s
BoonDirectBytesBenchmark.roundRobin    thrpt  16        10    1   521528,912    41366,197    ops/s
JacksonASTBenchmark.roundRobin         thrpt  16        10    1   512564,205   300704,545    ops/s
GSONReaderBenchmark.roundRobin         thrpt  16        10    1   446322,220    41327,496    ops/s
JsonSmartStreamBenchmark.roundRobin    thrpt  16        10    1   276399,298   130055,340    ops/s
JsonSmartReaderBenchmark.roundRobin    thrpt  16        10    1    86789,825    17690,031    ops/s

มันมีตัวแยกวิเคราะห์ JSON ที่เร็วที่สุดสำหรับสตรีม, ผู้อ่าน, ไบต์ [], ถ่าน [], CharSequence (StringBuilder, CharacterBuffer) และสตริง

ดูมาตรฐานเพิ่มเติมได้ที่:

https://github.com/RichardHightower/json-parsers-benchmark


คำตอบเกี่ยวกับ JSON นี้อยู่นอกหัวข้อจากคำถาม นอกจากนี้คำถามนี้ไม่ถูกต้องเพราะไม่มีสิ่งดังกล่าวเป็น“วันที่ JSON” น้อยมากในหมู่ชนิดข้อมูล JSON และทุกวันนี้รหัสทั้งหมดนี้สามารถถูกแทนที่ด้วยการเรียกบรรทัดเดียวไปยังคุณสมบัติ Java ในตัว:Instant.parse( "2013-12-14T01:55:33.412Z" )
Basil Bourque
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.