web-dev-qa-db-ja.com

ISO 8601準拠の文字列をJava.util.Dateに変換する

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

私にとって、これはいいですね。

599
Ice09

残念ながら、 SimpleDateFormat (Java 6以前)で利用可能なタイムゾーンフォーマットは ISO 8601 に準拠していません。 SimpleDateFormatは "GMT + 01:00"や "+0100"のようなタイムゾーン文字列を理解します。後者は RFC#822 に従っています。

Java 7がISO 8601に従ってタイムゾーン記述子のサポートを追加したとしても、SimpleDateFormatはオプションの部分をサポートしていないため、まだ完全な日付文字列を正しく解析することはできません。

Regexpを使用して入力文字列を再フォーマットすることは確かに1つの可能性ですが、置換規則はあなたの質問ほど単純ではありません:

  • 一部のタイムゾーンは、 _ utc _ で1時間休みではないため、文字列が必ずしも ":00"で終わるとは限りません。
  • ISO 8601では、時間帯に含めることができるのは時間数だけなので、 "+ 01"は "+ 01:00"と同じです。
  • ISO 8601では、 "+ 00:00"の代わりに "Z"を使用してUTCを示すことができます。

JAXBはXMLスキーマ仕様に従ってISO 8601日付文字列を解析できなければならないため、より簡単な解決策はおそらくJAXBでデータ型コンバーターを使用することです。 javax.xml.bind.DatatypeConverter.parseDateTime("2010-01-01T12:00:00Z")はあなたにCalendarオブジェクトを与えるでしょう、そしてもしあなたがDateオブジェクトを必要とするならば、あなたはそれに対して単にgetTime()を使うことができます。

あなたはおそらく Joda-Time を使うこともできるでしょう、しかし私はなぜあなたがそれに悩まされるべきかわかりません。

444
jarnbjo

さて、この質問はすでに答えられています、とにかく私は答えを落とします。それは誰かを助けるかもしれません。

私は Android向けのソリューションを探しています (API 7)。

  • Jodaは疑問の余地がありませんでした - それは巨大で初期化が遅いという問題があります。それはまたその特定の目的のために大きなやり過ぎであるように思われました。
  • 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を使用)にキャッシュすることができます。

198
wrygiel

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

より多くの例がセクションat SimpleDateFormat javadoc にあります。

195
Antonio

Java.time

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

Jackson-databindライブラリ にはそれを行う ISO8601DateFormatクラス もあります( ISO8601Utils の実際の実装)。

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

tl; dr

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

Java.timeを使う

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.time フレームワークは、Java 8以降に組み込まれています。これらのクラスは、 Java.util.DateCalendar 、& SimpleDateFormat などの厄介な古い legacy 日時クラスに代わるものです。

Joda-Time プロジェクトは、現在 maintenanceモード になっており、 Java.time クラスへの移行を推奨しています。

詳細については、 Oracleチュートリアル を参照してください。そして多くの例と説明についてはStack Overflowを検索してください。仕様は JSR 310 です。

データベースと直接Java.timeオブジェクトを交換できます。 JDBC 4.2 以降に準拠した JDBCドライバ を使用してください。文字列もJava.sql.*クラスも必要ありません。

Java.timeクラスはどこで入手できますか?

  • Java SE 8 Java SE 9 Java SE 10 、およびそれ以降
    • ビルトイン。
    • バンドル実装の標準Java APIの一部。
    • Java 9では、いくつかのマイナーな機能と修正が追加されています。
  • Java SE 6 および Java SE 7
    • Java.time機能の多くは ThreeTen-Backport でJava 6と7にバックポートされています。
  • Android
    • Java.timeクラスのAndroidバンドル実装の最新バージョン。
    • 初期のAndroid(<26)では、 ThreeTenABP プロジェクトはThreeTen-Backport(前述)を採用しています。 ThreeTenABPの使い方… を参照してください。

ThreeTen-Extra プロジェクトはJava.timeを追加のクラスで拡張します。このプロジェクトは、Java.timeに将来追加される可能性があることを証明するものです。 IntervalYearWeekYearQuarter 、および more などの便利なクラスがここにあります。


38
Basil Bourque

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);
27
d.danailov

DatatypeConverterソリューションはすべてのVMで機能するわけではありません。以下は私のために働きます:

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

私は、jodaがそのままでは機能しないことを発見しました(特に、日付のタイムゾーンを使って上記で与えた例のために、それは有効であるべきです)

20
James Scriven

私たちは使うべきだと思う

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

日付2010-01-01T12:00:00Z

14
Toby

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());
  }
}
9
tmandry

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では、SimpleDateFormatX文字を理解しないのでスローされます
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文に置き換えることができます)。

6
Khang .NT

Java.time

Java 8では、 Java.time.ZonedDateTime クラスとその静的parse(CharSequence text)メソッドを使用できます。

6
Martin Rust

私は 同じ 問題に直面し、それを次のコードで解決しました。

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

私にとってはうまくいった。

4
Abhay Kumar

また、あなたは次のクラスを使用することができます -

org.springframework.extensions.surf.util.ISO8601DateFormat


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

Java Docへのリンク - パッケージの階層階層org.springframework.extensions.surf.maven.plugin.util

4
Sergey Palyukh

他の人が述べているように、AndroidにはSDKに含まれるクラスを使ってISO 8601の日付を解析/フォーマットすることをサポートする良い方法はありません。私はこのコードを何度も書いたので、ついにISO 8601とRFC 1123の日付のフォーマットと解析をサポートするDateUtilsクラスを含むGistを作成しました。要旨には、サポートする内容を示すテストケースも含まれています。

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

3
Matt Accola

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クラスを知りません。

2

Java 1.7用のSimpleDateFormatは、ISO 8601フォーマット用のクールなパターンを持っています。

クラスSimpleDateFormat

これが私がしたことです:

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

これを好きですか?

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秒

1
yinhaomin

LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)のような文字列を使う

1
Stepan

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

私は同様のニーズを持っていました:私は事前に正確なフォーマットを知らなくても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");

それが助けを願っています。

0
gturri

このように日付をフォーマットするために、Java 6ベースのアプリケーションでは次のようにしました。欠けているコロンを挿入するthymeleafプロジェクトにはDateFormatクラスJacksonThymeleafISO8601DateFormatがあります。

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

私はECMAScriptの日付フォーマットの互換性のためにそれを使いました。

0
ekip

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

    }
0
ddtxra