ローソンデジタルイノベーション テックブログ

ローソンデジタルイノベーション(LDI)の技術ブログです

KotlinでDateの操作を簡単にするライブラリをつくってみる(その6)

f:id:ldi-contributor:20210916070736p:plain


開発者Gです。

前回はDateを文字列にフォーマットする拡張関数と、文字列をパースしてDateを取得する拡張関数を追加しました。

今回はDateに年月日時分秒ミリ秒を加算、減算するのに便利な拡張関数を追加します。


なお、ソースコードは全部、または一部を抜粋して表示します。
また、前回までに作成した拡張関数と拡張プロパティを使用するので、本記事に掲載のサンプルコードを動作させるには必要に応じて前回までのサンプルコードを取り込んでください。

Calendarで加算、減算を行う方法

Javaの標準ライブラリで日付の計算をするにはCalendarクラスを使用します。
Calendarには加算用のメソッドのみ用意されています。
減算するにはマイナス値を加算します。

CalendarPlusMinus1.kt
package samples

import extensions.date.format.format
import extensions.date.property.calendar
import extensions.string.date.toDate
import org.junit.Test
import java.util.*

class CalendarPlusMinus1 {

    @Test
    fun plus() {

        val date = "2021/09/01".toDate()    // (その5)の記事で作成した拡張関数toDate()を使用します
        println("date:             ${date.format()}")

        val calendar = date.calendar    // (その4)の記事で作成した拡張プロパティcalendarを使用します

        // 年を加算します
        calendar.add(Calendar.YEAR, 1)
        val date2 = Date.from(calendar.toInstant())
        println("YEAR + 1:         ${date2.format()}")

        // 月を加算します
        calendar.add(Calendar.MONTH, 1)
        val date3 = Date.from(calendar.toInstant())
        println("MONTH + 1:        ${date3.format()}")

        // 日を加算します
        calendar.add(Calendar.DAY_OF_MONTH, 1)
        val date4 = Date.from(calendar.toInstant())
        println("DAY_OF_MONTH + 1: ${date4.format()}")

        // 時を加算します
        calendar.add(Calendar.HOUR_OF_DAY, 1)
        val date5 = Date.from(calendar.toInstant())
        println("HOUR_OF_DAY + 1:  ${date5.format()}")

        // 分を加算します
        calendar.add(Calendar.MINUTE, 1)
        val date6 = Date.from(calendar.toInstant())
        println("MINUTE + 1:       ${date6.format()}")

        // 秒を加算します
        calendar.add(Calendar.SECOND, 1)
        val date7 = Date.from(calendar.toInstant())
        println("SECOND + 1:       ${date7.format()}")

        // ミリ秒を加算します
        calendar.add(Calendar.MILLISECOND, 1)
        val date8 = Date.from(calendar.toInstant())
        println("MILLISECOND + 1:  ${date8.format()}")
    }

    @Test
    fun minus() {

        println()

        val date = "2021/09/01".toDate()    // (その5)の記事で作成した拡張関数toDate()を使用します
        println("date:             ${date.format()}")

        val calendar = date.calendar    // (その4)の記事で作成した拡張プロパティcalendarを使用します

        // 年を減算します
        calendar.add(Calendar.YEAR, -1)
        val date2 = Date.from(calendar.toInstant())
        println("YEAR - 1:         ${date2.format()}")

        // 月を減算します
        calendar.add(Calendar.MONTH, -1)
        val date3 = Date.from(calendar.toInstant())
        println("MONTH - 1:        ${date3.format()}")

        // 日を減算します
        calendar.add(Calendar.DAY_OF_MONTH, -1)
        val date4 = Date.from(calendar.toInstant())
        println("DAY_OF_MONTH - 1: ${date4.format()}")

        // 時を減算します
        calendar.add(Calendar.HOUR_OF_DAY, -1)
        val date5 = Date.from(calendar.toInstant())
        println("HOUR_OF_DAY - 1:  ${date5.format()}")

        // 分を減算します
        calendar.add(Calendar.MINUTE, -1)
        val date6 = Date.from(calendar.toInstant())
        println("MINUTE - 1:       ${date6.format()}")

        // 秒を減算します
        calendar.add(Calendar.SECOND, -1)
        val date7 = Date.from(calendar.toInstant())
        println("SECOND - 1:       ${date7.format()}")

        // ミリ秒を減算します
        calendar.add(Calendar.MILLISECOND, -1)
        val date8 = Date.from(calendar.toInstant())
        println("MILLISECOND - 1:  ${date8.format()}")
    }
}
実行結果
date:             2021/09/01
YEAR + 1:         2022/09/01
MONTH + 1:        2022/10/01
DAY_OF_MONTH + 1: 2022/10/02
HOUR_OF_DAY + 1:  2022/10/02 01:00:00
MINUTE + 1:       2022/10/02 01:01:00
SECOND + 1:       2022/10/02 01:01:01
MILLISECOND + 1:  2022/10/02 01:01:01.001

date:             2021/09/01
YEAR - 1:         2020/09/01
MONTH - 1:        2020/08/01
DAY_OF_MONTH - 1: 2020/07/31
HOUR_OF_DAY - 1:  2020/07/30 23:00:00
MINUTE - 1:       2020/07/30 22:59:00
SECOND - 1:       2020/07/30 22:58:59
MILLISECOND - 1:  2020/07/30 22:58:58.999


いちおうひと通りの加減算はできますが、

        calendar.add(Calendar.YEAR, -1)
        val date2 = Date.from(calendar.toInstant())

のようにインデックス指定したり、CalendarのインスタンスからDateのインスタンスに変換したりする必要があり、使い勝手がよくありません。

LocalDateやLocalDateTimeのplus系とminus系のメソッド

Date and Time APIのLocalDateやLocalDateTimeでは上記のような不便さが解消されています。

LocalDateTimeには加減算するためのメソッドとして以下のものが用意されています。

  • plusYears, ​minusYears
  • plusMonths, minusMonths
  • plusDays, minusDays

LocalDateTimeにはさらに以下のものが用意されています。

  • plusHours, minusHours
  • plusMinutes, minusMinutes
  • plusSeconds, minusSeconds
  • plusNano, minusNano


Dateについてもこれらと同等の機能を利用できるように拡張関数を追加しましょう。

ただし、LocalDateTimeにはmillisecond(ミリ秒)がなく、利用できるのはnano(ナノ秒)ですが、Dateは精度がミリ秒までなので、plusMillisecond, minusMillisecondを実装します。

DatePlusMinusExtension.kt
package extensions.date.plusminus

import extensions.date.property.calendar
import java.util.*

/**
 * Dateに年を加えます。
 */
fun Date.plusYears(years: Int): Date {

    val calendar = this.calendar
    calendar.add(Calendar.YEAR, years)
    return Date.from(calendar.toInstant())
}

/**
 * Dateから年を引きます。
 */
fun Date.minusYears(years: Int): Date {

    return this.plusYears(-years)
}

/**
 * Dateに月を加えます。
 */
fun Date.plusMonths(months: Int): Date {

    val calendar = this.calendar
    calendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH) + months)
    return Date.from(calendar.toInstant())
}

/**
 * Dateから月を引きます。
 */
fun Date.minusMonths(months: Int): Date {

    return this.plusMonths(-months)
}

/**
 * Dateに日を加えます。
 */
fun Date.plusDays(days: Int): Date {

    val calendar = this.calendar
    calendar.add(Calendar.DAY_OF_MONTH, days)
    return Date.from(calendar.toInstant())
}

/**
 * Dateから日を引きます。
 */
fun Date.minusDays(days: Int): Date {

    return this.plusDays(-days)
}

/**
 * Dateに時を加えます。
 */
fun Date.plusHours(hours: Int): Date {

    val calendar = this.calendar
    calendar.add(Calendar.HOUR_OF_DAY, hours)
    return Date.from(calendar.toInstant())
}

/**
 * Dateから時を引きます。
 */
fun Date.minusHours(hours: Int): Date {

    return this.plusHours(-hours)
}

/**
 * Dateに分を加えます。
 */
fun Date.plusMinutes(minutes: Int): Date {

    val calendar = this.calendar
    calendar.add(Calendar.MINUTE, minutes)
    return Date.from(calendar.toInstant())
}

/**
 * Dateから分を引きます。
 */
fun Date.minusMinutes(minutes: Int): Date {

    return this.plusMinutes(-minutes)
}

/**
 * Dateに秒を加えます。
 */
fun Date.plusSeconds(seconds: Int): Date {

    val calendar = this.calendar
    calendar.add(Calendar.SECOND, seconds)
    return Date.from(calendar.toInstant())
}

/**
 * Dateから秒を引きます。
 */
fun Date.minusSeconds(seconds: Int): Date {

    return this.plusSeconds(-seconds)
}

/**
 * Dateにミリ秒を加えます。
 */
fun Date.plusMilliseconds(milliseconds: Int): Date {

    val calendar = this.calendar
    calendar.add(Calendar.MILLISECOND, milliseconds)
    return Date.from(calendar.toInstant())
}

/**
 * Dateからミリ秒を引きます。
 */
fun Date.minusMilliseconds(milliseconds: Int): Date {

    return this.plusMilliseconds(-milliseconds)
}
使用例
package extensions.date.plusminus

import extensions.date.format.format
import extensions.string.date.toDate
import org.junit.Test

class DatePlusMinusExtensionTest {

    @Test
    fun plusAndMinus() {

        val date = "2021/09/01".toDate()
        val date2 = date.plusYears(1)
        val date3 = date2.plusMonths(1)
        val date4 = date3.plusDays(1)
        val date5 = date4.plusHours(1)
        val date6 = date5.plusMinutes(1)
        val date7 = date6.plusSeconds(1)
        val date8 = date7.plusMilliseconds(1)

        println("date:  ${date.format()}")
        println("plusYears(1): ${date2.format()}")
        println("plusMonths(1): ${date3.format()}")
        println("plusDays(1): ${date4.format()}")
        println("plusHours(1): ${date5.format()}")
        println("plusMinutes(1): ${date6.format()}")
        println("plusSeconds(1): ${date7.format()}")
        println("plusMilliseconds(1): ${date8.format()}")

        // メソッドチェーンで連続的に呼び出すことができます。
        // 作業用の変数を使用しなくてよいのでコードがコンパクトになります。
        val date9 =
            date.plusYears(1)
                .plusMonths(1)
                .plusDays(1)
                .plusHours(1)
                .plusMinutes(1)
                .plusSeconds(1)
                .plusMilliseconds(1)
        println("Method chain result: ${date9.format()}")
    }
}
実行結果
date:  2021/09/01
plusYears(1): 2022/09/01
plusMonths(1): 2022/10/01
plusDays(1): 2022/10/02
plusHours(1): 2022/10/02 01:00:00
plusMinutes(1): 2022/10/02 01:01:00
plusSeconds(1): 2022/10/02 01:01:01
plusMilliseconds(1): 2022/10/02 01:01:01.001
Method chain result: 2022/10/02 01:01:01.001


追加した関数では、plusやminusの計算後の新しいDateのインタンスを返すので、メソッドチェーンが利用できます。
これにより、作業用の変数を使用しなくてよいのでコードがコンパクトになります。

次回予告

Date、LocalDate、LocalDateTimeの相互運用(仮)

乞うご期待。