ISO 8601 形式の文字列をJava.util.Date
に変換しようとしています。
Localeと共に使用した場合、パターンyyyy-MM-dd'T'HH:mm:ssZ
はISO8601に準拠していることがわかりました(サンプルの比較)。
しかし、Java.text.SimpleDateFormat
を使用して、正しくフォーマットされた文字列2010-01-01T12:00:00+01:00
を変換することはできません。コロンなしで、まず2010-01-01T12:00:00+0100
に変換する必要があります。
だから、現在の解決策は
SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMANY);
String date = "2010-01-01T12:00:00+01:00".replaceAll("\\+0([0-9]){1}\\:00", "+0$100");
System.out.println(ISO8601DATEFORMAT.parse(date));
これは明らかにそのニースではありません。私は何かが足りないのでしょうか、それとももっと良い解決策がありますか?
答え
JuanZeのコメントのおかげで、私は Joda-Time マジックを発見しました、それもここで 説明されています 。
だから、解決策は
DateTimeFormatter parser2 = ISODateTimeFormat.dateTimeNoMillis();
String jtdate = "2010-01-01T12:00:00+01:00";
System.out.println(parser2.parseDateTime(jtdate));
もっと簡単に言うと、コンストラクタを通してデフォルトのパーサを使う:
DateTime dt = new DateTime( "2010-01-01T12:00:00+01:00" ) ;
私にとって、これはいいですね。
残念ながら、 SimpleDateFormat (Java 6以前)で利用可能なタイムゾーンフォーマットは ISO 8601 に準拠していません。 SimpleDateFormatは "GMT + 01:00"や "+0100"のようなタイムゾーン文字列を理解します。後者は RFC#822 に従っています。
Java 7がISO 8601に従ってタイムゾーン記述子のサポートを追加したとしても、SimpleDateFormatはオプションの部分をサポートしていないため、まだ完全な日付文字列を正しく解析することはできません。
Regexpを使用して入力文字列を再フォーマットすることは確かに1つの可能性ですが、置換規則はあなたの質問ほど単純ではありません:
JAXBはXMLスキーマ仕様に従ってISO 8601日付文字列を解析できなければならないため、より簡単な解決策はおそらくJAXBでデータ型コンバーターを使用することです。 javax.xml.bind.DatatypeConverter.parseDateTime("2010-01-01T12:00:00Z")
はあなたにCalendar
オブジェクトを与えるでしょう、そしてもしあなたがDate
オブジェクトを必要とするならば、あなたはそれに対して単にgetTime()を使うことができます。
あなたはおそらく Joda-Time を使うこともできるでしょう、しかし私はなぜあなたがそれに悩まされるべきかわかりません。
さて、この質問はすでに答えられています、とにかく私は答えを落とします。それは誰かを助けるかもしれません。
私は Android向けのソリューションを探しています (API 7)。
javax.xml
に関する回答は、Android API 7では機能しません。この単純なクラスを実装することになった。これは ISO 8601文字列の最も一般的な形式 のみをカバーしていますが、場合によってはこれで十分なはずです(入力がthis形式になることが確実な場合)。
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を使用)にキャッシュすることができます。
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);
より多くの例がセクション 例 at SimpleDateFormat javadoc にあります。
Java.time API (Java 8以降に組み込まれています)は、これを少し簡単にします。
最後のZ
(Zulu用)のように、入力が _ utc _ にあることがわかっている場合は、 Instant
クラスで解析できます。
Java.util.Date date = Date.from( Instant.parse( "2014-12-12T10:39:40Z" ));
入力が最後のZ
(Zulu)で示される _ utc _ ではなく、別の offset-from-from-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 );
Jackson-databindライブラリ にはそれを行う ISO8601DateFormatクラス もあります( ISO8601Utils の実際の実装)。
ISO8601DateFormat df = new ISO8601DateFormat();
Date d = df.parse("2010-07-28T22:25:51Z");
OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" )
Java 8以降の新しい Java.time パッケージはJoda-Timeに触発されました。
OffsetDateTime
クラスは、 offset-from-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 );
必要に応じてタイムゾーンに調整してください。 time zone は、夏時間(DST)などの異常を処理するための一連の規則を使用した、地域の offset-from-UTC 値の履歴です。そのため、可能な限り、単なるオフセットではなくタイムゾーンを適用してください。
ZonedDateTime zonedDateTimeMontréal = odt.atZoneSameInstant( ZoneId.of( "America/Montreal" ) );
Java.time フレームワークは、Java 8以降に組み込まれています。これらのクラスは、 Java.util.Date
、 Calendar
、& SimpleDateFormat
などの厄介な古い legacy 日時クラスに代わるものです。
Joda-Time プロジェクトは、現在 maintenanceモード になっており、 Java.time クラスへの移行を推奨しています。
詳細については、 Oracleチュートリアル を参照してください。そして多くの例と説明についてはStack Overflowを検索してください。仕様は JSR 310 です。
データベースと直接Java.timeオブジェクトを交換できます。 JDBC 4.2 以降に準拠した JDBCドライバ を使用してください。文字列もJava.sql.*
クラスも必要ありません。
Java.timeクラスはどこで入手できますか?
ThreeTen-Extra プロジェクトはJava.timeを追加のクラスで拡張します。このプロジェクトは、Java.timeに将来追加される可能性があることを証明するものです。 Interval
、 YearWeek
、 YearQuarter
、および more などの便利なクラスがここにあります。
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
の
ISO 8601タイムスタンプを解析するためのもう1つの非常に簡単な方法は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);
このコードはISO 8601フォーマットを次のように解析できます。
2017-05-17T06:01:43.785Z
2017-05-13T02:58:21.391+01:00
しかしJava6では、SimpleDateFormat
はX
文字を理解しないのでスローされますIllegalArgumentException: Unknown pattern character 'X'
ISO 8601の日付をSimpleDateFormat
でJava 6で読める形式に正規化する必要があります。
public static Date iso8601Format(String formattedDate) throws ParseException {
try {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);
return df.parse(formattedDate);
} catch (IllegalArgumentException ex) {
// error happen in Java 6: Unknown pattern character 'X'
if (formattedDate.endsWith("Z")) formattedDate = formattedDate.replace("Z", "+0000");
else formattedDate = formattedDate.replaceAll("([+-]\\d\\d):(\\d\\d)\\s*$", "$1$2");
DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US);
return df1.parse(formattedDate);
}
}
Java 6でエラーが発生したときに[Z
を+0000
]または[+01:00
を+0100
]に置き換える方法(Javaのバージョンを検出し、try/catchをif文に置き換えることができます)。
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());
私にとってはうまくいった。
また、あなたは次のクラスを使用することができます -
org.springframework.extensions.surf.util.ISO8601DateFormat
Date date = ISO8601DateFormat.parse("date in iso8601");
Java Docへのリンク - パッケージの階層階層org.springframework.extensions.surf.maven.plugin.util
他の人が述べているように、AndroidにはSDKに含まれるクラスを使ってISO 8601の日付を解析/フォーマットすることをサポートする良い方法はありません。私はこのコードを何度も書いたので、ついにISO 8601とRFC 1123の日付のフォーマットと解析をサポートするDateUtilsクラスを含むGistを作成しました。要旨には、サポートする内容を示すテストケースも含まれています。
Apache Jackrabbit は永続的な日付のためにISO 8601フォーマットを使います、そしてそれらを解析するためのヘルパークラスがあります:
org.Apache.jackrabbit.util.ISO8601
jackrabbit-jcr-commons が付属しています。
ここでの優れた答えが示すように、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クラスを知りません。
Java 1.7用のSimpleDateFormatは、ISO 8601フォーマット用のクールなパターンを持っています。
これが私がしたことです:
Date d = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
Locale.ENGLISH).format(System.currentTimeMillis());
これを好きですか?
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秒
LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)
のような文字列を使う
1つの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
};
}
私は同様のニーズを持っていました:私は事前に正確なフォーマットを知らなくてもISO 8601に準拠した日付を解析できることが必要でした、そして私は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の日付フォーマットの互換性のためにそれを使いました。
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());
}