Sler系SEの技術ブログ

#Java でナノ秒を扱う最善の方法を探る(Timestamp型とString型の相互変換)

kght6123

kght6123

#Java でナノ秒を扱う最善の方法を探る(Timestamp型とString型の相互変換)

MessageFormatを利用して、秒まではDate、ミリ秒以降はNumberで扱うことを考えました。

よく利用する日付フォーマットのパターンはenum化しています。

ついでにJavaとDBの現在のシステム日時をナノ秒まで取得する関数も追加しました。

// TimestampUtil.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.Date;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

public class TimestampUtil {
	
	/**
	 * 数字のみフォーマット
	 */
	private static final ThreadLocal<MessageFormat> NUMBER_ONLY_FORMAT = new ThreadLocal<MessageFormat>() {
		@Override protected MessageFormat initialValue() {
			// 間に何か記号がないとparseでエラーに、URLの予約文字を避けて-か_にする。
			return new MessageFormat("{0,date,yyyyMMddHHmmss}-{1,number,000000000}");
		}
	};
	
	/**
	 * 標準フォーマット
	 */
	private static final ThreadLocal<MessageFormat> TIMESTAMP_FORMAT = new ThreadLocal<MessageFormat>() {
		@Override protected MessageFormat initialValue() {
			return new MessageFormat("{0,date,yyyy/MM/dd HH:mm.ss}.{1,number,000000000}");
		}
	};
	
	/**
	 * 対応する日付フォーマット一覧
	 */
	public enum TimestampFormat {
		NUMBER_ONLY(NUMBER_ONLY_FORMAT),
		NORMAL(TIMESTAMP_FORMAT),
		;
		private final ThreadLocal<MessageFormat> mftl;
		private TimestampFormat(final ThreadLocal<MessageFormat> mftl) {
			this.mftl = mftl;
		}
		public MessageFormat getMessageFormat() {
			return mftl.get();
		}
	}
	
	/**
	 * Timestampを文字列に変換する(null, ナノ秒考慮)
	 * 
	 * @param ts
	 * @param tf
	 * @return
	 */
	public static String toString(final Timestamp ts, final TimestampFormat tf) {
		if(ts == null)
			return "";
		
		return tf.getMessageFormat().format(new Object[] { ts, ts.getNanos() });
	}
	
	/**
	 * 文字列をTimestampに変換する(null, ナノ秒考慮)
	 * 
	 * @param str
	 * @param tf
	 * @return
	 * @throws ParseException
	 */
	public static Timestamp toTimestamp(final String str, final TimestampFormat tf) throws ParseException {
		if(StringUtils.isEmpty(str))
			return new Timestamp(0L);
		
		final Object[] objects = tf.getMessageFormat().parse(str);
		final Timestamp ts = new Timestamp(((Date)objects[0]).getTime());
		
		if(objects[1] instanceof Long)
			ts.setNanos(((Long)objects[1]).intValue());
		else if(objects[1] instanceof Integer)
			ts.setNanos((Integer)objects[1]);
		
		return ts;
	}
	
	/**
	 * 文字列を別のTimestampFormatに変換する(ナノ秒考慮)
	 * 
	 * @param str
	 * @param fromTf
	 * @param toTf
	 * @return
	 * @throws ParseException
	 */
	public static String toTimestampFormat(final String str, final TimestampFormat fromTf, final TimestampFormat toTf) throws ParseException {
		
		return toString(toTimestamp(str, fromTf), toTf);
	}
	
	/**
	 * Databaseの現在時刻をTimestamp型で取得する
	 * 
	 * @param conn
	 * @param logger
	 * @return
	 * @throws SQLException
	 */
	public static Timestamp getNowTimestampForDatabase(final Connection conn, final Logger logger) throws SQLException
	{
		try (final PreparedStatement stmt = conn.prepareStatement("select systimestamp as ts from dual");
				final ResultSet rs = stmt.executeQuery();)
		{
			if(rs.next())
				return rs.getTimestamp(1);
			else
				throw new SQLException("SYSTIMESTAMPが取得できません");
		}
	}
	
	/**
	 * Javaの現在時刻をTimestamp型で取得する(どこまでのナノ秒をサポートしているかはOSによる)
	 * 
	 * @return
	 */
	public static Timestamp getNowTimestampForJava()
	{
		final long timeInMillis = System.currentTimeMillis();
		final long timeInNanos = System.nanoTime();
		final Timestamp timestamp = new Timestamp(timeInMillis);
		timestamp.setNanos((int)(timeInNanos % 1000000000));
		return timestamp;
	}
}