Chuyển đổi Chuỗi tuân thủ ISO 8601 sang java.util.Date


667

Tôi đang cố gắng chuyển đổi một chuỗi định dạng ISO 8601 thành a java.util.Date.

Tôi thấy mẫu này yyyy-MM-dd'T'HH:mm:ssZtuân thủ ISO8601 nếu được sử dụng với Bản địa (mẫu so sánh).

Tuy nhiên, bằng cách sử dụng java.text.SimpleDateFormat, tôi không thể chuyển đổi Chuỗi được định dạng chính xác 2010-01-01T12:00:00+01:00. Tôi phải chuyển đổi nó đầu tiên 2010-01-01T12:00:00+0100, không có dấu hai chấm.

Vì vậy, giải pháp hiện tại là

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

Điều đó rõ ràng là không tốt. Tôi đang thiếu một cái gì đó hoặc có một giải pháp tốt hơn?


Câu trả lời

Nhờ nhận xét của JuanZe, tôi đã tìm thấy phép thuật Joda-Time , nó cũng được mô tả ở đây .

Vì vậy, giải pháp là

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

Hoặc đơn giản hơn, sử dụng trình phân tích cú pháp mặc định thông qua hàm tạo:

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

Đối với tôi, điều này là tốt đẹp.


243
Hãy sẵn sàng nhận nhiều câu trả lời "Sử dụng JodaTime" ...
JuanZe

3
@ Ice09: Nếu tài liệu API cho DateTimeFormat là chính xác (tài liệu JoDa có thể gây hiểu nhầm, sai hoặc không đầy đủ), mẫu bạn đã sử dụng trong "câu trả lời" của riêng bạn không tương thích với ISO8601.
jarnbjo

21
Tôi không chắc chắn khi điều này được thêm vào, nhưng 'X' xuất hiện để giải quyết vấn đề này trong SimpleDateFormat. Mẫu "yyyy-MM-dd'T'HH: mm: ssX" phân tích thành công ví dụ trong câu hỏi.
mlohbihler

12
'X' khả dụng kể từ Java 7.
Lars Grammel

3
Java 8 làm cho nó dễ dàng! Có một viên ngọc ẩn của Adam trong các câu trả lời dưới đây: stackoverflow.com/a/27479533/1262901
Fabian Keller

Câu trả lời:


477

Thật không may, các định dạng múi giờ có sẵn cho SimpleDateFormat (Java 6 trở về trước) không tuân thủ ISO 8601 . SimpleDateFormat hiểu các chuỗi múi giờ như "GMT + 01: 00" hoặc "+0100", chuỗi sau theo RFC # 822 .

Ngay cả khi Java 7 đã thêm hỗ trợ cho các bộ mô tả múi giờ theo ISO 8601, SimpleDateFormat vẫn không thể phân tích chính xác chuỗi ngày hoàn chỉnh, vì nó không hỗ trợ cho các phần tùy chọn.

Định dạng lại chuỗi đầu vào của bạn bằng regrec chắc chắn là một khả năng, nhưng các quy tắc thay thế không đơn giản như trong câu hỏi của bạn:

  • Một số múi giờ không phải là hết giờ UTC , do đó, chuỗi không nhất thiết phải kết thúc bằng ": 00".
  • ISO8601 chỉ cho phép số giờ được đưa vào múi giờ, do đó "+01" tương đương với "+01: 00"
  • ISO8601 cho phép sử dụng "Z" để biểu thị UTC thay vì "EST: 00".

Giải pháp dễ dàng hơn là có thể sử dụng trình chuyển đổi kiểu dữ liệu trong JAXB, vì JAXB phải có khả năng phân tích chuỗi ngày ISO8601 theo đặc tả Lược đồ XML. javax.xml.bind.DatatypeConverter.parseDateTime("2010-01-01T12:00:00Z")sẽ cung cấp cho bạn một Calendarđối tượng và bạn chỉ cần sử dụng getTime () trên nó, nếu bạn cần một Dateđối tượng.

Bạn có thể cũng có thể sử dụng Joda-Time , nhưng tôi không biết tại sao bạn nên bận tâm với điều đó.


18
Giải pháp JAXB là một cách tiếp cận thực sự sáng tạo! Nó hoạt động tốt, tôi đã thử nghiệm nó với mẫu của tôi. Tuy nhiên, đối với bất cứ ai phải đối mặt với vấn đề và được phép sử dụng JodaTime, tôi sẽ khuyên bạn nên sử dụng nó, vì nó cảm thấy tự nhiên hơn. Nhưng giải pháp của bạn không yêu cầu các thư viện bổ sung (ít nhất là với Java 6).
Ice09

36
Đây là mặt trái: Lịch c = GregorianCalWiki.getInstance (); c.setTime (aDate); return javax.xml.bind.DatatypeConverter.printDateTime (c);
Alexander Ljungberg

4
Trên thực tế không đơn giản lắm vì bạn phải khởi tạo datatypeConverter jaxb. Cuối cùng tôi đã sử dụng DatatypeFactory như DataTypeConverterImpl đã làm trong nội bộ. Thật là đau đầu.
gtrak

3
@Simon: Không, múi giờ tất nhiên không được bỏ qua. Bạn phải làm gì đó sai. Nếu bạn nhập nhiều hơn một vài ký tự và cho chúng tôi biết bạn đang thực sự làm gì, ai đó có thể giải thích cho bạn những gì.
jarnbjo

4
@jarnbjo bạn là người đầu tiên và duy nhất tôi gặp phải là người thích các lớp ngày tháng trước, tiêu chuẩn java, trong thời gian joda. Tôi thấy joda-time là một niềm vui theo nghĩa đen để sử dụng, đặc biệt là khi so sánh với api tiêu chuẩn là một sự gớm ghiếc.
NimChimpsky

244

Cách được ban phước bởi tài liệu 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);

Bạn có thể tìm thấy nhiều ví dụ trong phần Ví dụ tại SimpleDateFormat javadoc .

CẬP NHẬT 13/12/2020: Có một cách hoàn toàn mới để làm điều này trong Java 8


7
Câu trả lời của bạn đã giúp tôi chuyển đổi ISODate của MongoDB thành ngày địa phương. Trân trọng.
Bầu trời xanh

9
@ b.long Java đã thêm nhiều hơn một hằng số cho các định dạng tuân thủ ISO 8601 như vậy. Java có một khung hoàn toàn mới cho công việc thời gian bao gồm hỗ trợ mặc định tích hợp cho các định dạng đó. Xem java.timekhung công tác mới trong Java 8, lấy cảm hứng từ Joda-Time , thay thế các lớp java.util.Date, .CalWiki và SimpleDateFormat rắc rối.
Basil Bourque

2
Điều này không có nghĩa là bạn cần biết trước định dạng ngày sao? Điều gì sẽ xảy ra nếu bạn phải chấp nhận string1string2không biết bạn sẽ nhận được gì.
Timmmm

16
'Z' cần được trích dẫn
kervin

7
@kervin Nếu Z nằm trong dấu ngoặc kép, thì bộ định dạng sẽ không được tìm riêng cho ký tự Z, không phải tất cả các chuỗi bù mà nó có thể biểu thị? Có vẻ như trích dẫn Z sẽ chỉ hoạt động ngẫu nhiên, nếu chuỗi ngày của bạn tình cờ ở UTC.
spaaarky21

201

Được rồi, câu hỏi này đã được trả lời, nhưng dù sao tôi cũng sẽ bỏ câu trả lời của mình. Nó có thể giúp ai đó.

Tôi đã tìm kiếm một giải pháp cho Android (API 7).

  • Joda đã ra khỏi câu hỏi - nó rất lớn và bị khởi tạo chậm. Nó cũng có vẻ là một quá mức cần thiết cho mục đích cụ thể đó.
  • Câu trả lời liên quan javax.xmlsẽ không hoạt động trên API Android 7.

Đã kết thúc thực hiện lớp học đơn giản này. Nó chỉ bao gồm dạng chuỗi ISO 8601 phổ biến nhất , nhưng điều này là đủ trong một số trường hợp (khi bạn khá chắc chắn rằng đầu vào sẽ ở định dạng này ).

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

Lưu ý về hiệu suất: Tôi khởi tạo SimpleDateFormat mới mỗi lần để tránh lỗi trong Android 2.1. Nếu bạn ngạc nhiên như tôi, hãy xem câu đố này . Đối với các công cụ Java khác, bạn có thể lưu trữ cá thể trong trường tĩnh riêng (sử dụng ThreadLocal, để an toàn cho luồng).


2
Có lẽ điều này nên được đặt thành một câu hỏi của riêng mình, với câu trả lời của riêng mình?
Thorbear

5
Đây là trang đầu tiên tôi đã sử dụng khi tôi đang tìm câu trả lời, vì vậy nó có vẻ phù hợp. Đối với hầu hết các nhà phát triển Java, Android không chính xác là Java. Tuy nhiên, trong hầu hết các trường hợp, một cái hoạt động giống như cái kia, vì vậy nhiều nhà phát triển Android sẽ tìm kiếm "java" khi tìm kiếm cái này.
wrygiel

1
Lưu ý rằng điều này không chiếm độ phân giải mili giây. Điều này là dễ dàng để thêm.
Sky Kelsey

6
tôi đã phải thêm .SSS trong vài giây nhưng hoạt động rất tốt. Tại sao bạn làm s = s.substring(0, 22) + s.substring(23);- tôi không thấy điểm này
Dori

1
input = input.replaceAll ("[Zz]", "+0000"); cũng sẽ làm việc và hoạt động chuỗi con có thể tránh được.
Javanator

115

java.time

Các API java.time (được xây dựng vào Java 8 và sau), làm cho điều này một chút dễ dàng hơn.

Nếu bạn biết đầu vào là trong UTC , chẳng hạn như Z(cho Zulu) ở cuối, Instantlớp có thể phân tích cú pháp.

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

Nếu đầu vào của bạn có thể là một giá trị offset-từ-UTC khác thay vì UTC được chỉ định bởi Z(Zulu) ở cuối, hãy sử dụng OffsetDateTimelớp để phân tích cú pháp.

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

Sau đó giải nén Instantvà chuyển đổi thành a java.util.Datebằng cách gọi from.

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

8
Câu trả lời này là làm việc quá sức. Một java.util.Date theo định nghĩa không có múi giờ. Vì vậy, không cần tất cả các mã liên quan đến múi giờ đó: LocalDateTimeZoneIdatZone. Điều này đơn giản sẽ làm:java.util.Date date = Date.from( ZonedDateTime.parse( "2014-12-12T10:39:40Z" ).toInstant() );
Basil Bourque

5
@BasilBourque Điều này phức tạp không cần thiết: Date.from(Instant.parse("2014-12-12T10:39:40Z" ));là đủ.
assylias

3
@assylias bạn đúng nhưng điều đó sẽ chỉ hoạt động khi chuỗi ngày là vùng UTC, ISO8601 cho phép mọi múi giờ ...
Adam

2
@Adam Xấu của tôi - Tôi đã không nhận ra câu hỏi chung chung hơn ví dụ của bạn. Như một bình luận phụ, OffsetDateTimesẽ đủ để phân tích ISO8601 (không chứa thông tin múi giờ mà chỉ là phần bù).
assylias

1
@assylias Cảm ơn bình luận của bạn về việc cho phép Instantphân tích cú pháp. Mặc dù không đủ cho Câu hỏi cụ thể này, nhưng đây là một điểm khác biệt quan trọng đáng được chỉ ra. Vì vậy, tôi đã thêm một ví dụ thứ hai của mã. Rất tiếc, chỉ nhận thấy đây không phải là Câu trả lời của tôi; Tôi hy vọng Adam chấp thuận.
Basil Bourque

67

Các thư viện Jackson-DataBind cũng có lớp ISO8601DateFormat nào đó (thực hiện thực tế trong ISO8601Utils .

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

Nó không phân tích được ngày này : 2015-08-11T13:10:00. Tôi nhận được String index out of range: 19. Nhìn vào mã, có vẻ như nó yêu cầu mili giây được chỉ định và múi giờ. Những người nên được tùy chọn.
Timmmm

2
Để trích dẫn tài liệu, định dạng phân tích cú pháp là : [yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh:mm]]. Nói cách khác, mili giây là tùy chọn nhưng múi giờ là bắt buộc.
david_p

2
À đúng rồi, có vẻ như bạn đúng. Tuy nhiên, tôi khá chắc chắn rằng ISO8601 cho phép bạn bỏ qua múi giờ nên vẫn sai. JodaTime hoạt động mặc dù:new DateTime("2015-08-11T13:10:00").toDate()
Timmmm

3
Lớp đó hiện không được dùng nữa, lớp mới là StdDateFormat. Nếu không thì nó hoạt động như nhau.
JohnEye

51

tl; dr

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

Sử dụng java.time

Gói java.time mới trong Java 8 trở lên được lấy cảm hứng từ Joda-Time.

Các OffsetDateTimelớp đại diện cho một thời điểm trên timeline với một bù đắp-từ-UTC múi giờ nhưng không phải.

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

Gọi toStringsẽ tạo một chuỗi ở định dạng ISO 8601 tiêu chuẩn:

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

Để xem cùng một giá trị thông qua ống kính của UTC, hãy trích xuất Instanthoặc điều chỉnh phần bù từ +01:00đến 00:00.

Instant instant = odt.toInstant();  

…hoặc là…

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

Điều chỉnh thành múi giờ nếu muốn. Một múi giờ là một lịch sử của bù đắp-từ-UTC giá trị cho một khu vực, với một bộ quy tắc để xử lý bất thường như ánh sáng ban ngày tiết kiệm thời gian (DST). Vì vậy, áp dụng một múi giờ chứ không phải là một sự bù đắp bất cứ khi nào có thể.

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

Giới thiệu về java.time

Các java.time khung được xây dựng vào Java 8 và sau đó. Những lớp học thay thế cái cũ phiền hà di sản lớp học ngày thời gian như java.util.Date, Calendar, & SimpleDateFormat.

Các Joda thời gian dự án, bây giờ trong chế độ bảo trì , khuyên chuyển đổi sang các java.time lớp.

Để tìm hiểu thêm, xem Hướng dẫn Oracle . Và tìm kiếm Stack Overflow cho nhiều ví dụ và giải thích. Đặc điểm kỹ thuật là JSR 310 .

Bạn có thể trao đổi các đối tượng java.time trực tiếp với cơ sở dữ liệu của bạn. Sử dụng trình điều khiển JDBC tương thích với JDBC 4.2 trở lên. Không cần chuỗi, không cần java.sql.*lớp.

Nơi để có được các lớp java.time?

  • Java SE 8 , Java SE 9 , Java SE 10 và mới hơn
    • Được xây dựng trong.
    • Một phần của API Java tiêu chuẩn với việc triển khai đi kèm.
    • Java 9 thêm một số tính năng nhỏ và sửa lỗi.
  • Java SE 6 Java SE 7
    • Phần lớn chức năng java.time được chuyển ngược lại sang Java 6 & 7 trong ThreeTen-Backport .
  • Android
    • Các phiên bản sau của triển khai gói Android của các lớp java.time.
    • Đối với Android trước đó (<26), các ThreeTenABP dự án thích nghi ThreeTen-backport (nêu trên). Xem Cách sử dụng ThreeTenABP .

Các ThreeTen-Extra dự án mở rộng java.time với các lớp bổ sung. Dự án này là một nền tảng chứng minh cho các bổ sung có thể trong tương lai cho java.time. Bạn có thể tìm thấy một số các lớp học hữu ích ở đây chẳng hạn như Interval, YearWeek, YearQuarter, và nhiều hơn nữa .



27

Đối với phiên bản Java 7

Bạn có thể làm theo tài liệu của Oracle: http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

X - được sử dụng cho múi giờ 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);

Điều này có nghĩa là múi giờ là bắt buộc . Theo ISO 8601, nó là tùy chọn. Cũng như các giây, v.v. Vì vậy, điều này chỉ phân tích một tập hợp con cụ thể của ISO 8601.
Timmmm

1
Hoạt động tuyệt vời với Java 1.8
Thiago Pereira

20

Giải pháp DatatypeConverter không hoạt động trong tất cả các máy ảo. Các công việc sau đây cho tôi:

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

Tôi đã thấy rằng joda không hoạt động ngoài hộp (cụ thể là ví dụ tôi đã đưa ra ở trên với múi giờ vào một ngày, nên hợp lệ)


15

Tôi nghĩ chúng ta nên sử dụng

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

Cho ngày 2010-01-01T12:00:00Z


5
Tại sao đây là một câu trả lời tốt hơn so với những người khác, bao gồm câu trả lời được chấp nhận với 76 upvote?
Erick Robertson

3
@ErickRobertson: Rất đơn giản, linh hoạt, không có chuyển đổi và hầu hết mọi người không quan tâm đến múi giờ.
TWiStErRob

7
không có nhiều điểm làm việc với thời gian nếu bạn không quan tâm đến múi giờ!
Dori

16
Điều này IGNORES hoàn toàn múi giờ. Đã sử dụng điều này cho đến khi tôi nhận ra điều này đang xảy ra, vì vậy tôi chuyển sang JodaTime.
Joshua Pinter

3
Vứt bỏ múi giờ sẽ chỉ dẫn đến lỗi tại một số điểm.
Bart van Kuik

10

Một cách rất đơn giản khác để phân tích dấu thời gian ISO8601 là sử dụng 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());
  }
}

10

Bắt đầu từ Java 8, có một cách hoàn toàn mới được hỗ trợ chính thức để làm điều này:

    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
Nếu chuỗi ở định dạng tức thì, với dấu Zlà offset, chúng ta không cần chỉ định rõ ràng điều này. Chỉ cần Instant i = Instant.parse(s);. Chuỗi trong câu hỏi đã có +01:00, trong trường hợp DateTimeFormatter.ISO_INSTANTnày không hoạt động (ít nhất là không có trên Java 11 của tôi).
Ole VV

2
@ OleV.V. Bạn có thể sử dụng ISO_OFFSET_DATE_TIMEđể định dạng ngày với offset, như +01:00( docs.oracle.com/javase/8/docs/api/java/time/format/ Lỗi )
Lucas Basquerotto

1
Đó là sự thật, @LucasBasquerotto. Mặc dù không đề cập rõ ràng về định dạng đó, nhưng câu trả lời của AdamBasil Bourque đã làm điều tương tự.
Ole VV

6

java.time

Lưu ý rằng trong Java 8, bạn có thể sử dụng lớp java.time.ZencedDateTimeparse(CharSequence text)phương thức tĩnh của nó .


Các chuỗi đầu vào trong Câu hỏi chỉ có phần bù từ UTC, không phải là vùng toàn thời gian. Vì vậy, InstantZonedDateTimethích hợp ở đây, không ZonedDateTime.
Basil Bourque

6

Cách giải quyết cho Java 7+ là sử dụng SimpleDateFormat:
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);

Mã này có thể phân tích định dạng ISO8601 như:

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

Nhưng trên Java6, SimpleDateFormatkhông hiểu Xký tự và sẽ ném
IllegalArgumentException: Unknown pattern character 'X'
Chúng ta cần chuẩn hóa ngày ISO8601 thành định dạng có thể đọc được trong 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);
    }
}

Phương pháp trên để thay thế [ Zbằng +0000] hoặc [ +01:00bằng +0100] khi xảy ra lỗi trong Java 6 (bạn có thể phát hiện phiên bản Java và thay thế lệnh try / Catch bằng câu lệnh if).


Không, các lớp thời gian cũ rắc rối như DateSimpleDateFormatđược thiết kế kém, khó hiểu và thiếu sót. Chúng bây giờ là di sản, được thay thế bởi các lớp java.time được tích hợp trong Java 8 trở lên. Đối với Java 6 và Java 7, phần lớn chức năng java.time được chuyển ngược lại trong dự án ThreeTen-Backport . Tốt hơn nhiều để thêm thư viện đó vào ứng dụng của bạn hơn là sử dụng các lớp kế thừa đó. Giải pháp một dòng trong java.time:OffsetDateTime.parse( "2010-01-01T12:00:00+01:00" )
Basil Bourque

5

Tôi đã đối mặt với cùng một vấn đề và giải quyết nó bằng đoạn mã sau.

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

Trước đó tôi đã sử dụng SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.getDefault());

Nhưng sau đó tôi đã tìm thấy nguyên nhân chính của ngoại lệ là yyyy-MM-dd'T'HH:mm:ss.SSSZ,

Vì vậy, tôi đã sử dụng

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

Nó làm việc tốt cho tôi .


Chỉ cần những gì tôi cần mà không phải sử dụng joda-time, XML api hoặc bất cứ thứ gì khác. Chỉ cần đúng mẫu.
Philippe Gioseffi


4

Java có hàng tá cách khác nhau để phân tích thời gian, vì các câu trả lời xuất sắc ở đây đã chứng minh. Nhưng hơi ngạc nhiên, không có lớp thời gian nào của Java thực hiện đầy đủ ISO 8601!

Với Java 8, tôi khuyên bạn nên:

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

Điều đó sẽ xử lý các ví dụ cả trong UTC và với phần bù, như "2017-09-13T10: 36: 40Z" hoặc "2017-09-13T10: 36: 40 + 01: 00". Nó sẽ làm cho hầu hết các trường hợp sử dụng.

Nhưng nó sẽ không xử lý các ví dụ như "2017-09-13T10: 36: 40 + 01", đó thời gian ngày ISO 8601 hợp lệ.
Nó cũng sẽ không xử lý chỉ ngày, ví dụ: "2017-09-13".

Nếu bạn phải xử lý chúng, trước tiên tôi khuyên bạn nên sử dụng biểu thức chính quy để đánh hơi cú pháp.

Có một danh sách đẹp về các ví dụ ISO 8601 ở đây với rất nhiều trường hợp góc: https : //www.myinter đạn.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck / Tôi không biết về bất kỳ lớp Java nào có thể đối phó với tất cả chúng.


OffsetDateTimesẽ làm và về mặt khái niệm khớp với thời gian ngày với phần bù tốt hơn.
Ole VV

Này @ OleV.V. cám ơn vì sự gợi ý. Đáng buồn là không: OffsetDateTime.parse () sẽ đưa ra một ngoại lệ cho một số chuỗi ISO 8601 hợp lệ, ví dụ: "2017-09-13T10: 36: 40 + 01" hoặc "2017-09-13"
Daniel Winterstein

Tôi chỉ có ý nói rằng OffsetDateTimexử lý các ví dụ mà bạn xử lý ZonedDateTime. Tôi tin rằng nó không xử lý bất kỳ ví dụ ZonedDateTimenào không. Theo nghĩa đó, nó không cải thiện (nhưng cũng không tệ hơn). Xin lỗi, tôi không hoàn toàn rõ ràng.
Ole VV

1
Đây phải là câu trả lời được chấp nhận vào năm 2020, với tình trạng của sự vật.
slashCoder


3

Như những người khác đã đề cập, Android không có cách nào tốt để hỗ trợ phân tích / định dạng ngày ISO 8601 bằng cách sử dụng các lớp có trong SDK. Tôi đã viết mã này nhiều lần vì vậy cuối cùng tôi đã tạo ra một Gist bao gồm lớp DateUtils hỗ trợ định dạng và phân tích cú pháp ngày ISO 8601 và RFC 1123. Gist cũng bao gồm một trường hợp thử nghiệm cho thấy những gì nó hỗ trợ.

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


2

SimpleDateFormat cho JAVA 1.7 có một mẫu thú vị cho định dạng ISO 8601.

Lớp SimpleDateFormat

Đây là những gì tôi đã làm:

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

2
Ztrong định dạng chuỗi không phải là múi giờ ISO 8601, bạn nên sử dụng X( XXhoặc XXX) nếu bạn muốn múi giờ ISO 8601
Vojta

d thuộc loại String
Tim Child

1

Làm như thế này:

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

}

Đây là đầu ra:

Thứ Tư 19 tháng 10 15:15:36 CST 2016


1

Sử dụng chuỗi như LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)


1

Tôi ngạc nhiên rằng thậm chí không có một thư viện java nào hỗ trợ tất cả các định dạng ngày ISO 8601 theo https://en.wikipedia.org/wiki/ISO_8601 . Joda DateTime đã hỗ trợ hầu hết trong số họ, nhưng không phải tất cả và do đó tôi đã thêm logic tùy chỉnh để xử lý tất cả chúng. Đây là thực hiện của tôi.

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

Một thử nghiệm nhỏ cho thấy cách phân tích một ngày trong ISO8601 và LocalDateTime không xử lý các 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, các lớp học ngày thời gian khủng khiếp phiền hà như java.util.Date, java.util.Calendarjava.text.SimpleDateFormatbây giờ là di sản , thay thế bởi sự java.time class built vào Java 8 và sau đó. Xem Hướng dẫn của Oracle .
Basil Bourque

Bạn đúng là LocalDateTimekhông xử lý thời gian mùa hè (DST) vì nó hoàn toàn không xử lý múi giờ. Cho rằng chúng ta cần ZonedDateTime. Đề xuất DateSimpleDateFormatlà - IMHO xấu.
Ole VV

1
Thật vậy, ZencedDateTime hoạt động. Và java.time.Instant cũng là một lựa chọn tốt để xử lý DST. Tôi biết rằng java.util.Date không được dùng nữa và không nên sử dụng, nhưng tôi chỉ trả lời câu hỏi ban đầu: Cách chuyển đổi Chuỗi trong 8601 thành java.util.date ....
ddtxra

0

Tôi có một nhu cầu tương tự: Tôi cần có khả năng phân tích bất kỳ ngày nào tuân thủ ISO8601 mà không cần biết trước định dạng chính xác và tôi muốn một giải pháp nhẹ cũng có thể hoạt động trên Android.

Khi tôi giải quyết nhu cầu của mình, tôi vấp phải câu hỏi này và nhận thấy rằng AFAIU, không có câu trả lời nào hoàn toàn phù hợp với nhu cầu của tôi. Vì vậy, tôi đã phát triển jISO8601 và đẩy nó vào trung tâm maven.

Chỉ cần thêm vào bạn pom.xml:

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

và sau đó bạn tốt để đi:

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

Hy vọng nó sẽ giúp.



-1

Chức năng cơ bản Lịch sự: @wrygiel.

Hàm này có thể chuyển đổi định dạng ISO8601 thành Ngày Java có thể xử lý các giá trị bù. Theo định nghĩa của ISO 8601, phần bù có thể được đề cập ở các định dạng khác nhau.

±[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

Lớp này có các phương thức tĩnh để chuyển đổi

  • Đối tượng chuỗi ISO8601 đến Ngày (Local TimeZone)
  • Ngày đến chuỗi ISO8601
  • Tiết kiệm ánh sáng ban ngày được tự động calc

Mẫu 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
Xem ra - DateFormat và các lớp dẫn xuất không tương thích đa luồng! Sử dụng các đối tượng SimpleDateFormat tĩnh như DATE_FORMAT_1 và DATE_FORMAT_2 có nghĩa là nhiều luồng gọi các hàm ISO8601DateFormatter sẽ chia sẻ cùng một đối tượng DateFormat. Điều này dẫn đến hỏng dữ liệu và ngày không chính xác được trả về từ các cuộc gọi DateFormat. Để khắc phục điều này, bạn chỉ cần tạo các chuỗi mẫu hằng và tạo các biến SimpleDateFormat cục bộ bất cứ khi nào cần. Điều này sẽ đảm bảo rằng mỗi đối tượng chỉ được sử dụng bởi một luồng.
Theo

Một sửa chữa tốt hơn cho an toàn luồng là thay vào đó sử dụng một thư viện thời gian được xây dựng cho an toàn luồng. Trong Java thế giới đó là Joda-Time hoặc java.time.
Basil Bourque

-1

Điều này dường như làm việc tốt nhất cho tôi:

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


}

Tôi cần chuyển đổi thành / fro chuỗi ngày JavaScript sang Java. Tôi tìm thấy các công trình trên với các khuyến nghị. Có một số ví dụ sử dụng SimpleDateFormat gần nhưng chúng dường như không phải là tập hợp con như được đề xuất bởi:

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

và được hỗ trợ bởi Chuỗi PLIST và JavaScript và đó là những gì tôi cần.

Đây có vẻ là dạng phổ biến nhất của chuỗi ISO8601 ngoài kia và là một tập hợp con tốt.

Các ví dụ họ đưa ra là:

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.

Tôi cũng có một phiên bản nhanh:

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

Tôi chưa đánh giá nó, nhưng tôi đoán nó sẽ khá nhanh. Nó dường như làm việc. :)

@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

Tôi nghĩ điều mà nhiều người muốn làm là phân tích các chuỗi ngày JSON. Có một cơ hội tốt nếu bạn đến trang này mà bạn có thể muốn chuyển đổi ngày JavaScript JSON thành ngày Java.

Để hiển thị chuỗi ngày JSON trông như thế nào:

    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)

Chuỗi ngày JSON là 2013-12-14T01: 55: 33.412Z.

Các ngày không được bao phủ bởi thông số JSON mỗi lần nói, nhưng ở trên là định dạng ISO 8601 rất cụ thể, trong khi ISO_8601 lớn hơn nhiều và đó chỉ là một tập hợp con mặc dù là một tập hợp rất quan trọng.

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

Khi nó xảy ra, tôi đã viết một trình phân tích cú pháp JSON và trình phân tích cú pháp PLIST cả hai đều sử dụng ISO-8601 nhưng không phải là các bit giống nhau.

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

Tôi đã viết hai cách để làm điều này cho dự án của tôi. Một tiêu chuẩn, một nhanh chóng.

Một lần nữa, chuỗi ngày JSON là một triển khai rất cụ thể của ISO 8601 ....

(Tôi đã đăng một câu hỏi khác trong câu trả lời khác nên hoạt động cho các ngày PLIST, định dạng ISO 8601 khác).

Ngày JSON như sau:

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


}

Các tệp PLIST (ASCII non GNUNext) cũng sử dụng ISO 8601 nhưng không có milis giây nên ... không phải tất cả các ngày ISO-8601 đều giống nhau. (Ít nhất là tôi chưa tìm thấy cái nào sử dụng milis và trình phân tích cú pháp mà tôi đã thấy bỏ qua múi giờ hoàn toàn OMG).

Bây giờ cho phiên bản nhanh (bạn có thể tìm thấy nó trong Boon).

public static Date fromJsonDate( String string ) {

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

}

Lưu ý rằng Reflection.toCharArray sử dụng không an toàn nếu có nhưng mặc định là string.toCharArray nếu không.

(Bạn có thể lấy nó ra khỏi ví dụ bằng cách thay Reflection.toCharArray (chuỗi) bằng chuỗi.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 được triển khai như sau:

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

Dù sao ... tôi đoán là có khá nhiều người đến đây .. có thể đang tìm kiếm Chuỗi ngày JSON và mặc dù đó là ngày ISO-8601, đây là một ngày rất cụ thể cần một phân tích rất cụ thể.

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

Xem https://github.com/RichardHightower/boon Boon có trình phân tích cú pháp PLIST (ASCII) và trình phân tích cú pháp JSON.

Trình phân tích cú pháp JSON là trình phân tích cú pháp Java nhanh nhất mà tôi biết.

Xác nhận độc lập bởi các anh chàng hiệu suất 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

Nó có trình phân tích cú pháp JSON nhanh nhất cho các luồng, trình đọc, byte [], char [], CharSequence (StringBuilder, CharacterBuffer) và String.

Xem thêm điểm chuẩn tại:

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


Câu trả lời này về JSON không có chủ đề trong Câu hỏi. Hơn nữa, Câu hỏi này không chính xác vì không có thứ gọi là ngày JSON JSON trong số rất ít các loại dữ liệu JSON . Và ngày nay, tất cả mã này có thể được thay thế bằng một cuộc gọi một dòng đến tính năng Java tích hợp:Instant.parse( "2013-12-14T01:55:33.412Z" )
Basil Bourque
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.