JavaでのISO 8601形式の日時の処理

JavaでのISO 8601形式の日時の処理についての雑多なメモ

java.text.SimpleDateFormat

Java標準のSimpleDateFormatの場合、タイムゾーンを指定する大文字の"Z"が「+0900」となってしまい、「+09:00」のように区切りを入れてくれない。そのため、区切り文字を使用しないISO 8601の基本形式(Basic format)にするか、UTCにするかということになる。

ミリ秒のparseでエラー?

SimpleDateFormatでミリ秒を指定した文字列をparse()したところParseExceptionになってしまった。java versionは"1.6.0_22"。

Exception in thread "main" java.text.ParseException: Unparseable date: 
"20110214T164254.418+0900"
	at java.text.DateFormat.parse(Unknown Source)

いろいろやってみたらミリ秒を「S」だとエラー。3文字の「SSS」ならOKのようだ。

  • OK→"yyyyMMdd'T'HHmmss.SSSZ"
  • NG→"yyyyMMdd'T'HHmmss.SZ"、これはformat()はできてもparse()ができない。

org.apache.commons.lang.time

Apacheのcommons langの場合

org.apache.commons.lang.time.FastDateFormat

java.text.SimpleDateFormatとほぼ同機能だが、タイムゾーン出力では、大文字2文字の'ZZ'を使うと、「+09:00」のように出力される。

org.apache.commons.lang.time.DateUtils

FastDateFormatではparse()はサポートされていないようで、代わりにDateUtilsクラスにparseDate(String str, String[] parsePatterns)というのがある。フォーマット文字列を複数指定して解析できるみたいだ。内部的にSimpleDateFormatを使っているので、やはりミリ秒が「SSS」でないとエラーになるようだ。

Joda-Time

DateTimeクラスで普通にISO 8601の拡張形式で扱える。ISODateTimeFormatに基本形式に対応するフォーマッターがあるみたい。

コード

試しに使ってみたコード片

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;


public class ISO8601FormatTest {

	public static void main(String[] args) throws Exception {
		Date now = new Date();
		String str;
		Date date;
		
		// ISO 8601の基本形式
		SimpleDateFormat sdfIso8601BasicFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSSZ");
		str = sdfIso8601BasicFormat.format(now);
		System.out.println(str);
		date = sdfIso8601BasicFormat.parse(str);
		System.out.println(sdfIso8601BasicFormat.format(date));
		
		// ISO 8601の拡張形式(UTC)
		SimpleDateFormat sdfIso8601ExtendedFormatUtc = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
		sdfIso8601ExtendedFormatUtc.setTimeZone(TimeZone.getTimeZone("UTC"));
		str = sdfIso8601ExtendedFormatUtc.format(now);
		System.out.println(str);
		date = sdfIso8601ExtendedFormatUtc.parse(str);
		System.out.println(sdfIso8601ExtendedFormatUtc.format(date));
		
		// Apache commons langでformat()
		FastDateFormat fdfIso8601ExtendedFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");
		str = fdfIso8601ExtendedFormat.format(now);
		System.out.println(str);
		
		// Apache commons langでparse()
		String[] parsePatterns = {
				"yyyy-MM-dd'T'HH:mm:ss.SSSZZ",	
		};
		date = DateUtils.parseDate(str, parsePatterns);
		System.out.println(fdfIso8601ExtendedFormat.format(date));
		
		// Joda-timeで普通に文字列にするとISO 8601の拡張形式
		DateTime dtNow = new DateTime(now);
		str = dtNow.toString();
		System.out.println(str);
		
		DateTime fromString = new DateTime(str);
		System.out.println(fromString);
		
		// Joda-timeでISO 8601の基本形式
		DateTimeFormatter dftIsoBasic = ISODateTimeFormat.basicDateTime();
		str = dftIsoBasic.print(dtNow);
		System.out.println(str);
		fromString = dftIsoBasic.parseDateTime(str);
		System.out.println(dftIsoBasic.print(fromString));
	}

}

実行結果

20110219T155422.302+0900
20110219T155422.302+0900
2011-02-19T06:54:22.302Z
2011-02-19T06:54:22.302Z
2011-02-19T15:54:22.302+09:00
2011-02-19T15:54:22.302+09:00
2011-02-19T15:54:22.302+09:00
2011-02-19T15:54:22.302+09:00
20110219T155422.302+0900
20110219T155422.302+0900

log4j

log4jではISO8601形式でタイムスタンプを出力するという指定ができるが、厳密にはISO 8601になっていないようだ。「2010-12-15 16:16:59,140」というように空白を使ってしまっている。
以下のバグ報告を見ると修正されたような回答があるが、実際にはlog4j 1.2系では修正されていないような感じだ。

logbackでどうなっているかは見てない。

Axis2/Java

xsd:dateTimeとjava.util.Calendar間の変換処理。org.apache.axis2.databinding.utils.ConverterUtilあたりで自前でやっているようだ。

正規表現でのマッチング

XML関連の規約

XML関連の規約では、基本ISO 8601と同じなんだけど、もう少し限定して使いましょうみたいな感じらしい。
以下、ちゃんと読んでない。

ISO 8601の基本形式は使わないで、拡張形式だけで、表記の揺れをなくしましょうというスタンスのようだ。

付録のDにISO 8601 Date and Time Formatsというのがあるようだ。