日付管理ならJoda-Time Androidが使いやすい


前置き

これまで日付の管理で普通にCalendar使っていたわけなのですが、

  • 日時の扱いがコード量が多くなって面倒
  • バグが起きやすい仕様(Monthだけ開始が0とか…)でビビりながら使わざるを得ない

ということで、なんかいいの無いかな〜と思っていた所、Joda-Time Androidというのがとても使いやすかったです。こちらのスライドで紹介されて見つけたのですが、良かったとか、ちょっと気をつけなきゃいけないことなどまとめました。

Joda-Time Androidとは & 使い方

ソースはこちらです。
元々JavaでJoda-Timeというライブラリがあったのですが、メモリ上にolson timezone database(世界中のタイムゾーン情報を集めたDB)と言うものを持ち、それをClassLoader.getResourceAsStream()というメソッドでメモリ上に展開する(約4MBだそうです)ため、初回起動時にキャッシュにめちゃくちゃ時間がかかるという問題が発生していました。
Joda-Time AndroidはResource経由でアクセスすることで、このキャッシュによるメモリ増加を解消しているとのことです。(で、コードみてみたのですが、多分このへんがその変換をやっているようです)

導入はAndroid Studioであれば楽勝です。

build.gradle
dependencies {
  compile 'net.danlew:android.joda:2.6.0'
}

をbuild.gradleに付け加えるだけです。
あとはApplicationで

MyApp.java
public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        JodaTimeAndroid.init(this);
    }
}

とすれば、利用できます。

Timezoneデータは最新(2014)となっているようですが、今後更新する場合は、IANAというところからtzdataをDLし、ビルドソースの/tzdata配下にあるデータを置き換え、

./gradlew updateTzData

とすれば、最新化できるようです。

よかったところ

時間の表現が直感的

日付に対して、plus,minusで時間が表現でき、より直感的です。もちろん時間も対応しています。
例えば、2015/1/19 0:00:00 ~ 2015/1/19 23:59:59の取得したい場合、こう書けます(流石にもうちょっと綺麗にかけるかもですがあくまで例です)

format.java
String DATE_FORMAT_CAL = "yyyy/MM/dd";
String DATE_FORMAT_APP = "yyyy/MM/dd HH:mm:ss";
Calendar calendar= Calendar.getInstance();
calendar.set(day,year,month);
Date date = calendar.getTime();
SimpleDateFormat sdf1 = new SimpleDateFormat(DATE_FORMAT_CAL);
SimpleDateFormat sdf2 = new SimpleDateFormat(DATE_FORMAT_APP);
String dateStr = sdf1.format(date);
try {
    Date dateFrom = sdf2.parse(dateStr + "00:00:00");
    Date dateTo = sdf2.parse(dateStr + "23:59:59");
}catch(ParseException e){
    e.getMessage();
}

一方、Joda-Timeを使うと、こんな具合で書けます。

format2.java
DateTime startDate = new DateTime().withDate(year,month,day).withTimeAtStartOfDay();
DateTime endDate = startDate.plusDays(1).minusSeconds(1);

DateTimeはDateやCalに変換してよきに利用することができます。
チェーンメソッドに対応しているので、ワンライナーで書けるところも気持ち良いです。

コードが(基本的には)少なくなる

例えば前月の月を取得したい場合、色んな書き方があるかもですが、Calendarだとこんな風に書くことができます。

prev1.java
public int getPrevMonth(Calendar cal){
    //現在が1月の場合は前年にする
    if(cal.get(Calendar.MONTH) == cal.getActualMinimum(Calendar.MONTH)){
        cal.set(Calendar.YEAR, cal.get(Calendar.YEAR-1));
    }
    //monthは0-11(0の時は今月が1月なので、11(12月)を返す)
    int month = cal.get(Calendar.MONTH);
    if(month == 0){
        return month = 11;
    }
    return month-1; //0-11で返るのでバグの温床に…
}

一方Joda-Timeでは

prev2.java
public int getPrevMonth(Calendar cal){
    return new DateTime(cal).minusMonths(1).getMonthOfYear(); //1-12で返る。わかりやすい
}

と、こんな具合に簡潔にかけます。

DateUtilsが豊富

Joda-TimeにはDateUtilsクラスがあって、それを使うことで旨味がまします。
例えば英語でX分前(10 minutes ago)とか表現する場合 pruralに気をつけて実装する必要がありますが、Joda-Timeでは

rel.java
DateUtils.getRelativeTimeSpanString(this, now.minusMinutes(10))); //10 minutes ago

と、よろしくやってくれます。日付の表示方法も多くのオプションがあり、サンプルコードで確認できます。

使ってみてできなかったこと

なかなか便利なJoda-Time Androidなのですが、できなかったこともあります。

listview等のAdapterとしては利用できない

当たり前といえば当たり前なのですが、adapterとしては利用できません。インスタンス化したDateTimeに設定された日付自体を変えることはできないからです。
例えばgridViewに以下のように設定した場合、

SampleActivity.java
DateAdapter mAdapter;
DateTime mDateTime;

protected void onCreate(Bundle savedInstanceState) {
//...
GridView calendar = new GridView(context);
mDateTime = new dateTime();
mAdapter = new DateAdapter(context, mDateTime);
calendar.setAdapter(mAdapter);
//...
}

private void nextMonth(){
//一ヶ月ずらして更新
mDateTime = mDateTime.plusMonths(1);  
mAdapter.notifyDataSetChanged();
}

dateTimeを更新すればgridViewも更新されるかと思ったのですが、参照が外れてしまうのでダメでした(当たり前か)。
ここは素直にCalendarを使い、adapterクラス内でCalendarをDateTimeに変換して利用する方が良さげです。

getDayOfWeekがCalendarと返ってくる値が異なる

jodaTime のgetDayOfWeekメソッドだとSUNDAY=7(http://joda-time.sourceforge.net/apidocs/org/joda/time/DateTimeConstants.html )ですが、javaのCalendarだとSUNDAY = 1(http://download.java.net/jdk7/archive/b123/docs/api/constant-values.html#java.util.Calendar.DAY_OF_WEEK )が返ってきます。注意が必要です。