Java 日付け処理比較 Joda-Time / Java8 Date API / Apache Commons DateUtils

More than 1 year has passed since last update.

Java8でDate APIに新しい物が導入された。
既存のライブラリーもあるので、結局使い勝手はどれいいのかと思い、他のライブラリーと比べてみた。

日付けの加算、減算、一定期間の日付けをリストで取得、日付け形式文字列や、Date型からの変換、その逆などよく使いそうなものを比較。

Library Version
Java 1.8.0_25
joda-time 1.6.1
commons-lang3 3.3.2

現在時刻から、Date、文字列へ変換

Java8
ZonedDateTime now = ZonedDateTime.now();
String nowString = now.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
Date nowDate = Date.from(now.toInstant());
Joda
DateTime now = new DateTime();
String nowString = now.toString("yyyy/MM/dd HH:mm:ss");
Date nowDate = now.toDate();
DateUtils
Date now = new Date();
String nowString = DateFormatUtils.format(now, "yyyy/MM/dd");
//DateUtilsは元々Date型を扱うので、DateUtils用の特別な日付け型はない。

現在時刻から計算した日付けの取得

例えば、先週アカウント登録されたユーザーを探すとき、週次のレポートを作る時など、週の始まりの日付けを求めることが多い。

Java8
//昨日
ZonedDateTime yesterday = ZonedDateTime.now().minusDays(1);

//先週の月曜
ZonedDateTime lastWeekMonday = 
  ZonedDateTime.now().minusWeeks(1).with(DayOfWeek.MONDAY));

//今週の日曜
ZonedDateTime sunday = ZonedDateTime.now().with(DayOfWeek.SUNDAY);
Joda
//昨日
DateTime yesterday = new DateTime().minusDays(1);

//先週の月曜
DateTime lastWeekMonday = 
  new DateTime().minusWeeks(1).withDayOfWeek(DateTimeConstants.MONDAY);

//今週の日曜
DateTime sunday = new DateTime().withDayOfWeek(DateTimeConstants.SUNDAY);
DateUtils
//昨日
Date yesterday = DateUtils.addDays(new Date(), -1);

//月曜、日曜など曜日を指定して日付けを取得出来そうなメソッドが見つけられなかった。

一定期間の日付けの取得

こちらについてはDateUtils以外は、日付けリストを取得したい期間分自力でループする必要がある。意外とこういう機能はライブラリはサポートしてくれないようだ。

Java8
//先週の月曜日から土曜日までの1週間分をStringのリストで取得
DateTimeFormatter datePattern = DateTimeFormatter.ofPattern("yyyy/MM/dd");

ZonedDateTime monday = 
 ZonedDateTime.now().minusWeeks(1).with(DayOfWeek.MONDAY);

List<String> dates = 
  IntStream.range(0,7)
    .mapToObj(i -> monday.plusDays(i).format(datePattern)
    .collect(Collectors.toList());

//指定した月の1日から月末までの日付けをStringリストで取得//ZonedDateはない
LocalDate dec = LocalDate.parse("2014/12/01",datePattern);
List<String> dates = 
  IntStream.range(0, dec.with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth())
    .mapToObj(i -> dec.plusDays(i).format(datePattern))
    .collect(Collectors.toList());//2015/12/01 〜 2015/12/31
Joda
//先週の月曜日から土曜日までの1週間分をStringのリストで取得
String datePattern = "yyyy/MM/dd";

DateTime monday = 
 new DateTime().minusWeeks(1).withDayOfWeek(DateTimeConstants.MONDAY);
List<String> dates = 
  IntStream.range(0,7)
    .mapToObj(i -> monday.plusDays(i).toString(datePattern)
    .collect(Collectors.toList());

//指定した月の1日から月末までの日付けをStringリストで取得
DateTime dec = DateTimeFormat.forPattern(datePattern).parseDateTime("2015/12/01");
List<String> dates = 
  IntStream.range(0, dec.dayOfMonth().withMaximumValue().getDayOfMonth())
    .mapToObj(i -> december.plusDays(i).toString(datePattern))
    .collect(Collectors.toList());//2015/12/01 〜 2015/12/31
DateUtils
//指定した日の週の月曜から日曜までのDate型のリストを取得
Iterator<GregorianCalendar> days = 
  DateUtils.iterator(new Date(), DateUtils.RANGE_WEEK_MONDAY);

//DateUtilsにはひと月分の日付けを取得できそうなメソッドはなさそう。。?

文字列をパースして、日付け計算し、文字列へ戻す

Webアプリの画面の入力値や、バッチの起動引数を日付けとして使う場合によく使う処理。

Java8
String tomollow = 
  LocalDate.parse("2015/01/01", DateTimeFormatter.ofPattern("yyyy/MM/dd"))
           .plusDays(1).format(DateTimeFormatter.BASIC_ISO_DATE);//yyyyMMdd
Joda
String tomollow = 
  DateTimeFormat.forPattern("yyyy/MM/dd").parseDateTime("2015/01/01")
                .plusDays(1).toString("yyyyMMdd");
DateUtils
try {
  Date d = 
    DateUtils.addDays(
      DateUtils.parseDate("2015/01/01",new String[] { "yyyy/MM/dd" }), 1);
  String tomollow = DateFormatUtils.format(d, "yyyy/MM/dd");
} catch (ParseException e) {
  e.printStackTrace();
}

Date型から日付け計算、Date型へ戻す

既存のライブラリーがZoneDateTime、LocalDateTime等のインターフェースに対応するまでは、当分レガシーのDate型との変換が必要になるので、ここがやりやすいかも見ておきたい。

Java8
Date date = new Date();
ZonedDateTime dateTime = 
   ZonedDateTime.ofInstant(date.toInstant(),ZoneId.systemDefault()).plusYears(1);
Date oneYearAfter = Date.from(dateTime.toInstant());
Joda
Date date = new Date();
Date oneYearAfter = new DateTime(date).plusYears(1).toDate();
DateUtils
Date date = new Date();
Date oneYearAfter = DateUtils.addYears(date, 1);

タイムゾーン変換

Java8
ZonedDateTime jst = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
ZonedDateTime utc = ZonedDateTime.ofInstant(jst.toInstant(), ZoneId.of("UTC"));
Joda
DateTime jst = new DateTime(DateTimeZone.forID("Asia/Tokyo"));
DateTime utc = jst.toDateTime(DateTimeZone.UTC);
DateUtils
//Date型からDate型への変換メソッドはなく、文字列化では、タイムゾーン変換をサポートしているみたい
Date jst = Calendar.getInstance(TimeZone.getTimeZone("Asia/Tokyo")).getTime();
String utc =  DateFormatUtils.format(jst, "yyyyMMdd HH:mm:ss", TimeZone.getTimeZone("UTC"));

まとめ

現在のプロジェクトで利用しているフレームワークやライブラリーが本格的にJava8のDate APIに対応するまでは、Jodaでいいんじゃないかと思う。恐らく当分レガシーのDate型とつきあってく必要があるため。

Jodaは文字列、Date型との相互の変換などメソッドチェーンで一番シンプルにできそう。
Java8のAPIはすごく高機能らしいが、今回比較した内容ではJodaの方が使い勝手が良さそうだと思った。

ZonedDateTimeとDate間の変換がメソッドひとつでできなかったり、フォーマットのために変換用のインスタンスを作ったりでJodaの方が大分楽に感じた。

一定期間の日付けリストを取得する機能はどれも十分なものがなく、自力で頑張らなきゃならなさそう

Java8のDateApiを使う場合は、頻繁に使うstaticなメソッド、変数はstatic importしたほうがよさそう。
あと、Date型とZonedDateの相互変換用のメソッドを要した方がやりやすそうですね。

Java8
import static java.time.format.DateTimeFormatter.*; //ofPattern
import static java.time.temporal.TemporalAdjusters.*; //previous, next
import static java.time.DayOfWeek.*; //MONDAY, SUNDAY

//TemporalAdjusters.next -> next, DayOfWeek.MONDAY -> MONDAY

ZonedDateTime lastWeekMonday = 
  ZonedDateTime.now().minusWeeks(1).with(MONDAY);

ZonedDateTime nextMonday = ZonedDateTime.now().with(next(MONDAY));

String now = ZonedDateTime.now().format(ofPattern("yyyy/MM/dd"));