[英]Converting ISO 8601-compliant String to java.util.Date
我正在嘗試將ISO 8601格式的 String 轉換為java.util.Date
。
如果與區域設置(比較示例)一起使用,我發現模式yyyy-MM-dd'T'HH:mm:ssZ
符合 ISO8601 標准。
但是,使用java.text.SimpleDateFormat
,我無法轉換格式正確的 String 2010-01-01T12:00:00+01:00
。 我必須先將其轉換為2010-01-01T12:00:00+0100
,不帶冒號。
所以,目前的解決方案是
SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMANY);
String date = "2010-01-01T12:00:00+01:00".replaceAll("\\+0([0-9]){1}\\:00", "+0$100");
System.out.println(ISO8601DATEFORMAT.parse(date));
這顯然不是那么好。 我錯過了什么還是有更好的解決方案?
回答
感謝 JuanZe 的評論,我找到了Joda-Time 的魔法, 這里也有描述。
所以,解決方案是
DateTimeFormatter parser2 = ISODateTimeFormat.dateTimeNoMillis();
String jtdate = "2010-01-01T12:00:00+01:00";
System.out.println(parser2.parseDateTime(jtdate));
或者更簡單地說,通過構造函數使用默認解析器:
DateTime dt = new DateTime( "2010-01-01T12:00:00+01:00" ) ;
對我來說,這很好。
不幸的是, SimpleDateFormat (Java 6 和更早版本)可用的時區格式不符合 ISO 8601 。 SimpleDateFormat 理解時區字符串,如“GMT+01:00”或“+0100”,后者根據RFC # 822 。
即使 Java 7 根據 ISO 8601 添加了對時區描述符的支持,SimpleDateFormat 仍然無法正確解析完整的日期字符串,因為它不支持可選部分。
使用正則表達式重新格式化您的輸入字符串當然是一種可能性,但替換規則並不像您的問題那么簡單:
更簡單的解決方案可能是使用 JAXB 中的數據類型轉換器,因為 JAXB 必須能夠根據 XML Schema 規范解析 ISO8601 日期字符串。 javax.xml.bind.DatatypeConverter.parseDateTime("2010-01-01T12:00:00Z")
會給你一個Calendar
對象,如果你需要一個Date
對象,你可以簡單地使用 getTime() 。
您也可以使用Joda-Time ,但我不知道您為什么要為此煩惱(2022 年更新;可能是因為 Android 的javax.xml
包中缺少整個javax.xml.bind
部分)。
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 javadoc的示例部分中找到更多示例。
UPD 2020 年 2 月 13 日: Java 8 中有一種全新的方法可以做到這一點
好的,這個問題已經回答了,但無論如何我都會放棄我的答案。 它可能會幫助某人。
我一直在尋找適用於 Android (API 7) 的解決方案。
javax.xml
的答案不適用於 Android API 7。最終實現了這個簡單的類。 它僅涵蓋最常見的 ISO 8601 字符串形式,但在某些情況下應該足夠了(當您非常確定輸入將采用這種格式時)。
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* Helper class for handling a most common subset of ISO 8601 strings
* (in the following format: "2008-03-01T13:00:00+01:00"). It supports
* parsing the "Z" timezone, but many other less-used features are
* missing.
*/
public final class ISO8601 {
/** Transform Calendar to ISO 8601 string. */
public static String fromCalendar(final Calendar calendar) {
Date date = calendar.getTime();
String formatted = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.format(date);
return formatted.substring(0, 22) + ":" + formatted.substring(22);
}
/** Get current date and time formatted as ISO 8601 string. */
public static String now() {
return fromCalendar(GregorianCalendar.getInstance());
}
/** Transform ISO 8601 string to Calendar. */
public static Calendar toCalendar(final String iso8601string)
throws ParseException {
Calendar calendar = GregorianCalendar.getInstance();
String s = iso8601string.replace("Z", "+00:00");
try {
s = s.substring(0, 22) + s.substring(23); // to get rid of the ":"
} catch (IndexOutOfBoundsException e) {
throw new ParseException("Invalid length", 0);
}
Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(s);
calendar.setTime(date);
return calendar;
}
}
性能說明:我每次都實例化新的 SimpleDateFormat 以避免 Android 2.1 中的錯誤。 如果你和我一樣驚訝,看看這個謎語。 對於其他 Java 引擎,您可以將實例緩存在私有靜態字段中(使用 ThreadLocal,以保證線程安全)。
java.time API (內置於 Java 8 及更高版本)使這更容易一些。
如果您知道輸入是UTC 格式,例如末尾的Z
(代表 Zulu),則Instant
類可以解析。
java.util.Date date = Date.from( Instant.parse( "2014-12-12T10:39:40Z" ));
如果您的輸入可能是另一個從 UTC 偏移的值,而不是最后由Z
(Zulu) 指示的UTC ,請使用OffsetDateTime
類進行解析。
OffsetDateTime odt = OffsetDateTime.parse( "2010-01-01T12:00:00+01:00" );
然后提取Instant
,並通過調用from
轉換為java.util.Date
。
Instant instant = odt.toInstant(); // Instant is always in UTC.
java.util.Date date = java.util.Date.from( instant );
OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" )
Java 8 及更高版本中新的java.time包的靈感來自 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:00
調整為00:00
。
Instant instant = odt.toInstant();
…或者…
OffsetDateTime odtUtc = odt.withOffsetSameInstant( ZoneOffset.UTC );
如果需要,調整到時區。 時區是一個區域與UTC 值的偏移歷史,具有一組用於處理異常的規則,例如夏令時 (DST)。 因此,盡可能應用時區而不是單純的偏移量。
ZonedDateTime zonedDateTimeMontréal = odt.atZoneSameInstant( ZoneId.of( "America/Montreal" ) );
對於僅日期值,請使用LocalDate
。
LocalDate ld = LocalDate.of( 2010 , Month.JANUARY , 1 ) ;
或者:
LocalDate ld = LocalDate.parse( "2010-01-01" ) ;
java.time框架內置於 Java 8 及更高版本中。 這些類取代了麻煩的舊日期時間類,例如java.util.Date
、 Calendar
和SimpleDateFormat
。
要了解更多信息,請參閱Oracle 教程。 並在 Stack Overflow 上搜索許多示例和解釋。 規范是JSR 310 。
現在處於維護模式的Joda-Time項目建議遷移到java.time類。
您可以直接與您的數據庫交換java.time對象。 使用符合JDBC 4.2或更高版本的JDBC 驅動程序。 不需要字符串,不需要java.sql.*
類。 Hibernate 5 & JPA 2.2 支持java.time 。
從哪里獲得 java.time 類?
Jackson-databind 庫還具有執行此操作的ISO8601DateFormat 類(在ISO8601Utils中的實際實現。
ISO8601DateFormat df = new ISO8601DateFormat();
Date d = df.parse("2010-07-28T22:25:51Z");
從 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);
對於 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);
DatatypeConverter 解決方案不適用於所有 VM。 以下對我有用:
javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar("2011-01-01Z").toGregorianCalendar().getTime()
我發現 joda 不能開箱即用(特別是對於我上面給出的示例,其中包含日期的時區,這應該是有效的)
我認為我們應該使用
DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
對於日期2010-01-01T12:00:00Z
解析 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());
}
}
Java 7+ 的解決方法是使用 SimpleDateFormat:
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);
此代碼可以解析 ISO8601 格式,如:
2017-05-17T06:01:43.785Z
2017-05-13T02:58:21.391+01:00
但是在 Java6 上, SimpleDateFormat
不理解X
字符並且會拋出IllegalArgumentException: Unknown pattern character 'X'
我們需要使用SimpleDateFormat
將 ISO8601 日期標准化為 Java 6 中可讀的格式。
public static Date iso8601Format(String formattedDate) throws ParseException {
try {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);
return df.parse(formattedDate);
} catch (IllegalArgumentException ex) {
// error happen in Java 6: Unknown pattern character 'X'
if (formattedDate.endsWith("Z")) formattedDate = formattedDate.replace("Z", "+0000");
else formattedDate = formattedDate.replaceAll("([+-]\\d\\d):(\\d\\d)\\s*$", "$1$2");
DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US);
return df1.parse(formattedDate);
}
}
當 Java 6 中發生錯誤時,上述方法將 [ Z
替換為+0000
] 或 [ +01:00
替換為+0100
](您可以檢測 Java 版本並將 try/catch 替換為 if 語句)。
在我搜索了很多以將 ISO8601 轉換為最新版本后,我突然發現了一個 java 類ISO8601Util.java ,這是com.google.gson.internal.bind.util的一部分。 所以你可以用它來轉換日期。
ISO8601Utils.parse("2010-01-01T12:00:00Z" , ParsePosition(0))
你可以簡單地使用這個 kotlin 擴展功能
fun String.getDateFromString() : Date? = ISO8601Utils.parse(this ,
ParsePosition(0))
Java 8+
我在答案中沒有找到的簡單的一個襯里:
Date date = Date.from(ZonedDateTime.parse("2010-01-01T12:00:00+01:00").toInstant());
Date 不包含時區,它將以 UTC 格式存儲,但即使在使用System.out.println(date)
進行簡單輸出時也會正確轉換為您的 JVM 時區。
請注意,在 Java 8 中,您可以使用java.time.ZonedDateTime類及其靜態parse(CharSequence text)
方法。
我遇到了同樣的問題並通過以下代碼解決了它。
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());
它對我來說很好。
正如這里的優秀答案所示,Java 有十幾種不同的方法來解析日期時間。 但有點令人驚訝的是,Java 的時間類都沒有完全實現 ISO 8601!
對於 Java 8,我建議:
ZonedDateTime zp = ZonedDateTime.parse(string);
Date date = Date.from(zp.toInstant());
這將處理 UTC 和偏移量的示例,例如“2017-09-13T10:36:40Z”或“2017-09-13T10:36:40+01:00”。 它適用於大多數用例。
但它不會處理像“2017-09-13T10:36:40+01”這樣的示例,這是一個有效的 ISO 8601 日期時間。
它也不會只處理日期,例如“2017-09-13”。
如果你必須處理這些,我建議先使用正則表達式來嗅探語法。
這里有一個很好的 ISO 8601 示例列表,其中包含許多極端案例: https ://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ 我是不知道任何可以處理所有這些的 Java 類。
您也可以使用以下課程 -
org.springframework.extensions.surf.util.ISO8601DateFormat
Date date = ISO8601DateFormat.parse("date in iso8601");
鏈接到 Java Doc - Hierarchy For Package org.springframework.extensions.surf.maven.plugin.util
我正在嘗試將ISO 8601格式的String轉換為java.util.Date
。
如果與區域設置(比較示例)一起使用,我發現模式yyyy-MM-dd'T'HH:mm:ssZ
符合ISO8601。
但是,使用java.text.SimpleDateFormat
不能轉換格式正確的String 2010-01-01T12:00:00+01:00
。 我必須先將其轉換為2010-01-01T12:00:00+0100
,而不能使用冒號。
所以,目前的解決方案是
SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMANY);
String date = "2010-01-01T12:00:00+01:00".replaceAll("\\+0([0-9]){1}\\:00", "+0$100");
System.out.println(ISO8601DATEFORMAT.parse(date));
顯然不是那么好。 我是否缺少某些東西或有更好的解決方案?
回答
感謝JuanZe的評論,我找到了Joda-Time魔術, 這里也對此進行了描述。
因此,解決方案是
DateTimeFormatter parser2 = ISODateTimeFormat.dateTimeNoMillis();
String jtdate = "2010-01-01T12:00:00+01:00";
System.out.println(parser2.parseDateTime(jtdate));
或更簡單地說,通過構造函數使用默認解析器:
DateTime dt = new DateTime( "2010-01-01T12:00:00+01:00" ) ;
對我來說,這很好。
正如其他人所提到的,Android 沒有很好的方法來支持使用 SDK 中包含的類解析/格式化 ISO 8601 日期。 我已經多次編寫此代碼,因此我最終創建了一個 Gist,其中包含一個支持格式化和解析 ISO 8601 和 RFC 1123 日期的 DateUtils 類。 Gist 還包括一個測試用例,顯示它支持的內容。
SimpleDateFormat for JAVA 1.7 有一個很酷的 ISO 8601 格式模式。
這是我所做的:
Date d = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
Locale.ENGLISH).format(System.currentTimeMillis());
使用像LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)
這樣的字符串
我很驚訝,甚至沒有一個 java 庫支持https://en.wikipedia.org/wiki/ISO_8601中的所有 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 }; }
像這樣做:
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);
}
這是輸出:
2016 年 10 月 19 日星期三 15:15:36 CST
一個小測試展示了如何解析 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());
}
當想要從 UTC 轉換為我們想要的格式時。 它會根據我們停留的區域/位置而改變
//utcDate = "2021-06-05T02:46:29Z"
fun converterUtcToReadableDateTime(utcDate: String): String {
val offsetDateTime = OffsetDateTime.ofInstant(Instant.parse(utcDate), ZoneId.systemDefault())
val patternDate = "dd MMM yyyy h:mm a"
return DateTimeFormatter.ofPattern(patternDate).format(offsetDateTime)
}
fun converterUtcToReadableDate(utcDate: String): String {
val offsetDateTime = OffsetDateTime.ofInstant(Instant.parse(utcDate), ZoneId.systemDefault())
val patternDate = "d MMM yyyy"
return DateTimeFormatter.ofPattern(patternDate).format(offsetDateTime)
}
fun converterUtcToReadableTime(utcDate: String): String {
val offsetDateTime = OffsetDateTime.ofInstant(Instant.parse(utcDate), ZoneId.systemDefault())
val patternDate = "h:mm a"
return DateTimeFormatter.ofPattern(patternDate).format(offsetDateTime)
}
我也有類似的需求:我需要能夠在事先不知道確切格式的情況下解析任何符合 ISO8601 的日期,並且我想要一個也可以在 Android 上運行的輕量級解決方案。
當我搜索我的需求時,我偶然發現了這個問題,並注意到 AFAIU,沒有答案完全符合我的需求。 所以我開發了jISO8601並將它推到了 maven central 上。
只需添加你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");
希望它有所幫助。
要像這樣格式化日期,以下在基於 Java 6 的應用程序中對我有用。 在 thymeleaf 項目中有一個DateFormat
類JacksonThymeleafISO8601DateFormat
插入缺少的冒號:
我將它用於 ECMAScript 日期格式兼容性。
這是使用Java 7的基於Kotlin的方法。您可以傳遞自定義模式,也可以僅使用默認的ISO8601模式。 但是,您可能需要處理解析錯誤。
object DateUtil {
private const val ISO_8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSX"
fun convertDateToTimestamp(date: Date, pattern: String = ISO_8601_PATTERN): String {
val df = SimpleDateFormat(pattern, Locale.getDefault()).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
return df.format(date)
}
fun convertTimestampToDate(timestamp: String, pattern: String = ISO_8601_PATTERN): Date {
val formatter = SimpleDateFormat(pattern, Locale.getDefault()).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
return formatter.parse(timestamp) ?: Date(0)
}
}
我不能使用 Java 8 的特性,所以只有java.util.Date
可用。 我已經依賴於 gson 庫,但不想直接使用ISO8601Utils
。 ISO8601Utils
是一個內部 API, gson 的作者警告不要使用它。
我使用 gson 的公共 API 解析了 ISO8601 日期:
fun parseISO8601DateToLocalTimeOrNull(date: String): Date? {
return try {
GsonBuilder()
.create()
.getAdapter(Date::class.java)
.fromJson("\"$date\"")
} catch (t: Throwable) {
null
}
}
在引擎蓋下,適配器仍然使用ISO8601Utils
。 但是,如果您使用的是適配器,您可以確定不同的兼容版本的 gson 不會破壞您的項目。
我擔心適配器的創建可能會很慢,所以我用debuggable=false
測量了 Pixel 3a 上的執行時間。 parseISO8601DateToLocalTimeOrNull
需要大約 0.5 毫秒來解析日期。
基本功能禮貌:@wrygiel。
該函數可以將 ISO8601 格式轉換為可以處理偏移值的 Java 日期。 根據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 字符串
/* "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;
}
}
我正在嘗試將ISO 8601格式的String轉換為java.util.Date
。
如果與區域設置(比較示例)一起使用,我發現模式yyyy-MM-dd'T'HH:mm:ssZ
符合ISO8601。
但是,使用java.text.SimpleDateFormat
不能轉換格式正確的String 2010-01-01T12:00:00+01:00
。 我必須先將其轉換為2010-01-01T12:00:00+0100
,而不能使用冒號。
所以,目前的解決方案是
SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMANY);
String date = "2010-01-01T12:00:00+01:00".replaceAll("\\+0([0-9]){1}\\:00", "+0$100");
System.out.println(ISO8601DATEFORMAT.parse(date));
顯然不是那么好。 我是否缺少某些東西或有更好的解決方案?
回答
感謝JuanZe的評論,我找到了Joda-Time魔術, 這里也對此進行了描述。
因此,解決方案是
DateTimeFormatter parser2 = ISODateTimeFormat.dateTimeNoMillis();
String jtdate = "2010-01-01T12:00:00+01:00";
System.out.println(parser2.parseDateTime(jtdate));
或更簡單地說,通過構造函數使用默認解析器:
DateTime dt = new DateTime( "2010-01-01T12:00:00+01:00" ) ;
對我來說,這很好。
我認為很多人想要做的是解析 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 解析器和一個 PLIST 解析器,它們都使用 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 的解析器,而且我看到的解析器完全跳過了時區 OMG)。
現在是快速版本(您可以在 Boon 中找到它)。
public static Date fromJsonDate( String string ) {
return fromJsonDate ( Reflection.toCharArray ( string ), 0, string.length () );
}
請注意, Reflection.toCharArray 如果可用則使用 unsafe,但如果不可用則默認為 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 解析器。
由 Gatling Performance 帥哥獨立驗證。
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 解析器,可用於流、讀取器、bytes[]、char[]、CharSequence(StringBuilder、CharacterBuffer)和 String。
在以下位置查看更多基准:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.