Scalaで年月単位の期間計算

勤続年数や有効期限など世の中には何かとある日からどれだけの日数が
経過したかの計算することがあります。
特に年月単位での計算が多いのでScalaでどのように記述できるか試してみました。
Joda Timeを使えばいいじゃない、で実用面では終わってしまう話なのですが
関数型の記述で日付計算が書けるものなのか興味があったので挑戦してみました。
仕事の都合でJavaで書いた内容がベースになっているのでJava臭さが抜けませんな。

import java.util.Calendar
import java.util.GregorianCalendar
import java.text._

object PeriodCalculator {

    val fields = List(Calendar.DATE, Calendar.MONTH, Calendar.YEAR)

    // 年月単位での差分結果を格納するオブジェクト
    class Period(years: Int, months: Int) {
        def getYears() = years
        def getMonths() = months
    }
    
    def main(args: Array[String]): Unit = {
        val startDate = getCalendar(2010, 1, 16)
        
        calPeriod(startDate, getCalendar(2010,3,31))
        calPeriod(startDate, getCalendar(2011,1,15))
        calPeriod(startDate, getCalendar(2011,1,16))
        calPeriod(startDate, getCalendar(2011,12,31))
        calPeriod(getCalendar(2008,2,29), getCalendar(2009,2,28))
        calPeriod(getCalendar(2008,2,29), getCalendar(2008,3,28))
    }
    
    def getCalendar(year: Int, month: Int, day: Int) = {
        new GregorianCalendar(year, month-1, day)
    }   
    
    def calPeriod(aFromDate: Calendar, toDate: Calendar): Unit = {
        // 閏日特別処理
        val fromDate = if (isLeapDay(aFromDate))
                            getPreviousDay(aFromDate)
                       else aFromDate
        
        val period = getPeriod(fromDate, toDate, 0, 0)

        println("%tY/%<tm/%<td".format(aFromDate)+
                " - "+
                "%tY/%<tm/%<td".format(toDate)+
                " >>> years = %d".format(period.getYears())+
                ", months = %d".format(period.getMonths()))
    }
    
    /*     
      Y年M月D日は次月以降のD日を迎えるたびに+1月経過したとみなします
      ex. 2010年10月1日を起算日とした場合、2010年11月1日で1ヶ月、
          2010年12月1日で2ヶ月経過になる
    */
    def getPeriod(fromDate: Calendar, toDate: Calendar,
                  fieldIndex: Int, previousDiff: Int): Period = {
        // 1つ前の要素でマイナスがある場合は切り崩し処理が必要
        val loan = if (previousDiff >= 0) 0 else 1
        
        val diff = toDate.get(fields(fieldIndex)) -
                   fromDate.get(fields(fieldIndex)) - loan
        if (fieldIndex == fields.size - 1) {
            // 年要素まで計算が終わったら終了する
            val months = if (previousDiff >= 0) previousDiff
                         else previousDiff + 12
            new Period(diff, months)
        } else {
            // 現在の要素の差分を渡して次の要素の差分計算を行う
            getPeriod(fromDate, toDate, fieldIndex + 1, diff)
        }
    }
    
    // 閏日判定
    def isLeapDay(date: Calendar) = {
        if (date.get(Calendar.MONTH) == Calendar.FEBRUARY &&
            date.get(Calendar.DATE) == 29) true
        else false
    }
    
    // 閏日処理のための特例メソッド
    def getPreviousDay(date: Calendar) = {
        getCalendar(date.get(Calendar.YEAR),
                    date.get(Calendar.MONTH)+1,
                    date.get(Calendar.DATE)-1)
    }
}

実行結果はこんな感じになります。

2010/01/16 - 2010/03/31 >>> years = 0, months = 2
2010/01/16 - 2011/01/15 >>> years = 0, months = 11
2010/01/16 - 2011/01/16 >>> years = 1, months = 0
2010/01/16 - 2011/12/31 >>> years = 1, months = 11
2008/02/29 - 2009/02/28 >>> years = 1, months = 0
2008/02/29 - 2008/03/28 >>> years = 0, months = 1