相关文章推荐

開発者Gです。

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

今回はDate, LocalDate, LocalDateTimeの相互運用について考えてみます。

  • Date, LocalDate, LocalDateTimeの相互運用
    • DateInteropExtension.kt
    • LocalDateInteropExtension.kt
    • LocalDateTimeInteropExtension.kt
    • LocalDateTimeのnanoについて
      • LocalDateTimePropertyExtension.kt
      • 文字列をパースしてLocalDate, LolcalDateTimeを取得する
        • StringLocalDateExtension.kt
        • StringLocalDateTimeExtension.kt
        • ここまでのまとめ
        • オレオレDateライブラリの使用例

            Date, LocalDate, LocalDateTimeの相互運用

            前回まではKotlinの拡張関数と拡張プロパティを使用し、Date(java.util.Date)を大幅に機能強化しました。
            これによりDateの使い勝手に関する不満の多くは解消されたと思います。

            といっても、今後もDateの使用を継続することを勧めているわけではなく、LocalDateやLocalDateTimeへの移行期間中にDateを使用せざるを得ないシーンでの使用を想定しています。

            移行するにあたっては、DateからLocalDateやLocalDateTimeへの変換や、その逆の変換が必要になります。

            Date, LocalDate, LocalDateTimeの相互運用


            DateとLocalDateTimeはミリ秒精度までなら情報が欠落することなく相互に変換できます。LocalDateTimeはナノ秒まで使用できますが、ミリ秒よりも高い精度を使用した場合はDateTImeへの変換で情報が欠落するので注意が必要です。

            DateからLocalDateへの変換はLocalDateがdayまでの精度しか保持しないため、時分秒ミリ秒の情報は欠落します。もっとも、LocalDateは年月日を扱いやすくするのが目的ですので、これは問題ではありません。

            LocalDateからDateへの変換は情報の欠落はなく、特に問題ありません。

            LocalDateTimeにはすでにtoLocalDate()が用意されていますので、それ以外の相互変換用に以下の拡張関数を追加しましょう。

            DateInteropExtension.kt

            DateをLocalDate/LocalDateTimeへ変換します。

            package extensions.date.interop
            import extensions.date.property.*
            import java.time.LocalDate
            import java.time.LocalDateTime
            import java.util.*
             * DateをLocalDateに変換します。
            fun Date.toLocalDate(): LocalDate {
                return LocalDate.of(this.yearValue, this.monthValue, this.dayValue)
             * DateをLocalDateTimeに変換します。
            fun Date.toLocalDateTime(): LocalDateTime {
                return LocalDateTime.of(
                    this.yearValue,
                    this.monthValue,
                    this.dayValue,
                    this.hourValue,
                    this.minuteValue,
                    this.secondValue,
                    this.millisecondValue * 1000000     // nano
                
            package extensions.date.interop
            import extensions.date.format.format
            import extensions.string.date.toDate
            import org.junit.Test
            class DateInteropExtensionTest {
                @Test
                fun toLocalDate() {
                    val date = "2021/09/01".toDate()
                    val localDate = date.toLocalDate()
                    println("date=${date.format()}")
                    println("localDate=$localDate")
                    println("localDate.year=${localDate.year}")
                    println("localDate.monthValue=${localDate.monthValue}")
                    println("localDate.dayOfMonth=${localDate.dayOfMonth}")
                @Test
                fun toLocalDateTime() {
                    val date = "2021/09/01 12:34:56.789".toDate()
                    val localDateTime = date.toLocalDateTime()
                    println()
                    println("date=${date.format()}")
                    println("localDateTime=${localDateTime}")
                    println("localDateTime.year=${localDateTime.year}")
                    println("localDateTime.monthValue=${localDateTime.monthValue}")
                    println("localDateTime.dayOfMonth=${localDateTime.dayOfMonth}")
                    println("localDateTime.hour=${localDateTime.hour}")
                    println("localDateTime.minute=${localDateTime.minute}")
                    println("localDateTime.second=${localDateTime.second}")
                    println("localDateTime.nano=${localDateTime.nano}")
                
            date=2021/09/01
            localDate=2021-09-01
            localDate.year=2021
            localDate.monthValue=9
            localDate.dayOfMonth=1
            date=2021/09/01 12:34:56.789
            localDateTime=2021-09-01T12:34:56.789
            localDateTime.year=2021
            localDateTime.monthValue=9
            localDateTime.dayOfMonth=1
            localDateTime.hour=12
            localDateTime.minute=34
            localDateTime.second=56
            localDateTime.nano=789000000



            LocalDateInteropExtension.kt

            LocalDateをDate/LocalDateTimeへ変換します。

            package extensions.localdate.interop
            import extensions.string.date.toDate
            import java.time.LocalDate
            import java.time.LocalDateTime
            import java.time.format.DateTimeFormatter
            import java.util.*
             * LocalDateをDateに変換します。
            fun LocalDate.toDate(): Date {
                val pattern = "yyyy/MM/dd"
                val dateString = DateTimeFormatter.ofPattern(pattern).format(this)
                return dateString.toDate(pattern = pattern)
             * LocalDateをLocalDateTimeに変換します。
            fun LocalDate.toLocalDateTime(): LocalDateTime {
                return LocalDateTime.of(this.year, this.monthValue, this.dayOfMonth, 0, 0, 0)
                
            package extensions.localdate.interop
            import extensions.date.format.format
            import org.junit.Test
            import java.time.LocalDate
            class LocalDateExtensionTest {
                @Test
                fun toDate() {
                    val localDate = LocalDate.parse("2021-09-01")
                    val date = localDate.toDate()
                    println("localDate=$localDate")
                    println("date=${date.format()}")
                @Test
                fun toLocalDateTime() {
                    val localDate = LocalDate.parse("2021-09-01")
                    val localDateTime = localDate.toLocalDateTime()
                    println()
                    println("localDate=$localDate")
                    println("localDateTime.year=${localDateTime.year}")
                    println("localDateTime.monthValue=${localDateTime.monthValue}")
                    println("localDateTime.dayOfMonth=${localDateTime.dayOfMonth}")
                    println("localDateTime.hour=${localDateTime.hour}")
                    println("localDateTime.minute=${localDateTime.minute}")
                    println("localDateTime.second=${localDateTime.second}")
                    println("localDateTime.nano=${localDateTime.nano}")
                
            localDate=2021-09-01
            date=2021/09/01
            localDate=2021-09-01
            localDateTime.year=2021
            localDateTime.monthValue=9
            localDateTime.dayOfMonth=1
            localDateTime.hour=0
            localDateTime.minute=0
            localDateTime.second=0
            localDateTime.nano=0


            LocalDateTimeInteropExtension.kt

            LocalDateTimeをDateへ変換します。

            package extensions.localdatetime.interop
            import extensions.string.date.toDate
            import java.time.LocalDateTime
            import java.time.format.DateTimeFormatter
            import java.util.*
             * LocalDateTimeをDateに変換します。
            fun LocalDateTime.toDate(): Date {
                val pattern = "yyyy/MM/dd HH:mm:ss.SSS"
                val dateTimeString = DateTimeFormatter.ofPattern(pattern).format(this)
                return dateTimeString.toDate(pattern = pattern)
                
            package extensions.localdatetime.interop
            import extensions.date.format.format
            import extensions.date.property.*
            import org.junit.Test
            import java.time.LocalDateTime
            class LocalDateTimeExtensionTest {
                @Test
                fun toDate() {
                    val localDateTime = LocalDateTime.parse("2021-09-01T12:34:56.789")
                    val date = localDateTime.toDate()
                    println("lodalDateTime=$localDateTime")
                    println("date=${date.format()}")
                    println("date.year=${date.yearValue}")
                    println("date.monthValue=${date.monthValue}")
                    println("date.dayValue=${date.dayValue}")
                    println("date.hourValue=${date.hourValue}")
                    println("date.minuteValue=${date.minuteValue}")
                    println("date.secondValue=${date.secondValue}")
                    println("date.millisecondValue=${date.millisecondValue}")
                
            lodalDateTime=2021-09-01T12:34:56.789
            date=2021/09/01 12:34:56.789
            date.year=2021
            date.monthValue=9
            date.dayValue=1
            date.hourValue=12
            date.minuteValue=34
            date.secondValue=56
            date.millisecondValue=789

            LocalDateTimeのnanoについて

            LocalDateTimeでは秒未満の精度がナノ秒になっています。
            ミリ秒を直接取得する方法は用意されていません。

            ちなみにミリ秒の1/1000がマイクロ秒、マイクロ秒の1/1000がナノ秒なので
            ミリ秒の1/1000000がナノ秒となります。

            特定用途のアプリケーションならナノ秒の精度を必要とするのかもしれませんが、個人的にはオーバースペックですし、
            Dateの精度はミリですから、相互運用する上ではミリ秒に統一したいです。

            なので、LocalDateTimeにミリ秒を取得するプロパティを追加してみましょう。
            println("localDateTime=$localDateTime") println("localDateTime.nano=${localDateTime.nano}") println("localDateTime.millisecondValue=${localDateTime.millisecondValue}")

            localDateTime=2021-09-19T11:02:07.342918
            localDateTime.nano=342918000
            localDateTime.millisecondValue=342


            LocalDateTImeは型の定義上はナノ秒まで格納できるようになっていますが、手元のMacだと".342918"となり、マイクロ秒精度までしか取得できないようです。
            nanoプロパティを取得すると342918000になります。
            millisecondValueプロパティを取得すると342となります。単純に1000000で割っているので切り捨てになっていることが確認できます。
            import java.time.LocalDate import java.time.format.DateTimeFormatter import java.time.format.ResolverStyle * 文字列をLocalDateに変換します。変換できない場合は例外を発生させます。 * length -> pattern * 8 -> "yyyyMMdd" * 10 -> "yyyy/MM/dd" * else -> "yyyy/MM/dd" fun String.toLocalDate(pattern: String? = null): LocalDate { val p = pattern ?: when (this.length) { 8 -> "yyyyMMdd" 10 -> "yyyy/MM/dd" else -> "yyyy-MM-dd" }.replace("y", "u") try { val dtf = DateTimeFormatter.ofPattern(p).withResolverStyle(ResolverStyle.STRICT) return LocalDate.parse(this, dtf) } catch (t: Throwable) { throw IllegalArgumentException("LocalDateに変換できません。(this=$this, pattern=$pattern)", t) * 文字列をLocalDateに変換します。変換できない場合はnullを返します。 * length -> pattern * 8 -> "yyyyMMdd" * 10 -> "yyyy/MM/dd" * else -> "yyyy/MM/dd" fun String.toLocalDateOrNull(pattern: String? = null): LocalDate? { try { return this.toLocalDate(pattern = pattern) } catch (t: Throwable) { return null

            package extensions.string.localdate
            import org.junit.Test
            class StringLocalDateExtensionTest {
                @Test
                fun toLocalDate() {
                    println()
                    println("toLocalDate()")
                    run {
                        val text = "20210901"
                        val localDate = text.toLocalDate()
                        println("$text -> $localDate")
                    run {
                        val text = "2021/09/01"
                        val localDate = text.toLocalDate()
                        println("$text -> $localDate")
                    try {
                        "2021".toLocalDate()
                    } catch (t: Throwable) {
                        println(t)
                @Test
                fun toLocalDateOrNull() {
                    println()
                    println("toLocalDateOrNull()")
                    run {
                        val text = "20210901"
                        val localDate = text.toLocalDateOrNull()
                        println("$text -> $localDate")
                    run {
                        val text = "2021/09/01"
                        val localDate = text.toLocalDateOrNull()
                        println("$text -> $localDate")
                    run {
                        val text = "2021"
                        val localDate = text.toLocalDateOrNull()
                        println("$text -> $localDate")
            toLocalDate()
            20210901 -> 2021-09-01
            2021/09/01 -> 2021-09-01
            java.lang.IllegalArgumentException: LocalDateに変換できません。(this=2021, pattern=null)
            toLocalDateOrNull()
            20210901 -> 2021-09-01
            2021/09/01 -> 2021-09-01
            2021 -> null


            同様にLocalDateTimeについても文字列に対してtoDate()のように使用できる拡張関数を追加しましょう。
            import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.time.format.ResolverStyle * 文字列をLocalDateTimeに変換します。変換できない場合はnullを返します。 * length -> pattern * 14 -> "yyyyMMddHHmmss" * 17 -> "yyyyMMddHHmmssSSS" * 19 -> "yyyy/MM/dd HH:mm:ss" * 23 -> "yyyy/MM/dd HH:mm:ss.SSS" * else -> "yyyy/MM/dd" fun String.toLocalDateTime(pattern: String? = null): LocalDateTime { val p = pattern ?: when (this.length) { 14 -> "yyyyMMddHHmmss" 17 -> "yyyyMMddHHmmssSSS" 19 -> "yyyy/MM/dd HH:mm:ss" 23 -> "yyyy/MM/dd HH:mm:ss.SSS" else -> "yyyyMMddHHmmss" }.replace("y", "u") try { val dtf = DateTimeFormatter.ofPattern(p).withResolverStyle(ResolverStyle.STRICT) return LocalDateTime.parse(this, dtf) } catch (t: Throwable) { throw IllegalArgumentException("LocalDateTimeに変換できません。(this=$this, pattern=$pattern)", t) * 文字列をLocalDateTimeに変換します。変換できない場合はnullを返します。 * length -> pattern * 14 -> "yyyyMMddHHmmss" * 17 -> "yyyyMMddHHmmssSSS" * 19 -> "yyyy/MM/dd HH:mm:ss" * 23 -> "yyyy/MM/dd HH:mm:ss.SSS" * else -> "yyyy/MM/dd" fun String.toLocalDateTimeOrNull(pattern: String? = null): LocalDateTime? { try { return this.toLocalDateTime(pattern = pattern) } catch (t: Throwable) { return null

            package extensions.string.localdatetime
            import extensions.string.localdate.toLocalDate
            import org.junit.Test
            class StringLocalDateTimeExtensionTest {
                @Test
                fun toLocalDateTime() {
                    println()
                    println("toLocalDateTime()")
                    run {
                        val text = "20210901123456"
                        val localDateTime = text.toLocalDateTime()
                        println("$text -> $localDateTime")
                    run {
                        val text = "20210901123456789"
                        val localDateTime = text.toLocalDateTime()
                        println("$text -> $localDateTime")
                    run {
                        val text = "2021/09/01 12:34:56"
                        val localDateTime = text.toLocalDateTime()
                        println("$text -> $localDateTime")
                    run {
                        val text = "2021/09/01 12:34:56.789"
                        val localDateTime = text.toLocalDateTime()
                        println("$text -> $localDateTime")
                    try {
                        "2021".toLocalDate()
                    } catch (t: Throwable) {
                        println(t)
                @Test
                fun toLocalDateTimeOrNull() {
                    println()
                    println("toLocalDateTimeOrNull()")
                    run {
                        val text = "2021/09/01 12:34:56.789"
                        val localDateTime = text.toLocalDateTimeOrNull()
                        println("$text -> $localDateTime")
                    run {
                        val text = "2021"
                        val localDateTime = text.toLocalDateTimeOrNull()
                        println("$text -> $localDateTime")
            toLocalDateTime()
            20210901123456 -> 2021-09-01T12:34:56
            20210901123456789 -> 2021-09-01T12:34:56.789
            2021/09/01 12:34:56 -> 2021-09-01T12:34:56
            2021/09/01 12:34:56.789 -> 2021-09-01T12:34:56.789
            java.lang.IllegalArgumentException: LocalDateに変換できません。(this=2021, pattern=null)
            toLocalDateTimeOrNull()
            2021/09/01 12:34:56.789 -> 2021-09-01T12:34:56.789
            2021 -> null

            ここまでのまとめ

            これまでに作成した拡張プロパティや拡張関数をおさらいしましょう。

            これまでに作成した拡張プロパティと拡張関数


            こうして図にしてみると、結構たくさんつくりましたね。

            Dateを中心にString、LocalDate、LocalDateTimeとの相互変換が容易になるように拡張関数を実装しています。
            また、Dateの機能不足を補うために拡張プロパティや拡張関数を実装しています。

            Date単体だとこれでもまだまだ機能不足なのですが、そこはLocalDateやLocalDateTimeへ変換してからそれらの高度な機能を利用すればよいと思います。


            さいごに、これらの使用例をまとめたサンプルコードでしめたいと思います。
            import extensions.date.format.format import extensions.date.interop.toLocalDate import extensions.date.interop.toLocalDateTime import extensions.date.plusminus.* import extensions.date.property.* import extensions.localdate.interop.toDate import extensions.localdate.interop.toLocalDateTime import extensions.localdatetime.interop.toDate import extensions.string.date.toDate import extensions.string.localdate.toLocalDate import extensions.string.localdatetime.toLocalDateTime import org.junit.Test import java.time.LocalDate class OleOleDateLibrarySample1 { @Test fun string2date2string() { run { println("# 拡張関数(String <-> Date) ※日時パターン指定なし") val string = "20210901" val date = string.toDate() val format = date.format() println("string: $string") println("string.toDate(): $date") println("date.format(): $format") println() run { println("# 拡張関数(String <-> Date) ※日時パターン指定あり") val string = "2021.09.01" val pattern = "yyyy.MM.dd" val date = string.toDate(pattern = pattern) val format = date.format(pattern = pattern) println("string: $string") println("pattern: $pattern") println("string.toDate(): $date") println("date.format(): $format") println() @Test fun dateProperties() { println("# dateの拡張プロパティ") val string = "2021/09/01 12:34:56.789" val date = string.toDate() println("string: $string") println("date.yearValue: ${date.yearValue}") println("date.monthValue: ${date.monthValue}") println("date.dayValue: ${date.dayValue}") println("date.hourValue: ${date.hourValue}") println("date.minuteValue: ${date.minuteValue}") println("date.secondValue: ${date.secondValue}") println("date.millisecondValue: ${date.millisecondValue}") println() @Test fun datePlusMinusFunctions() { println("# dateの拡張メソッド(plus/minus)") val date = "2021/09/01 12:34:56.789".toDate() val datePlus = date .plusYears(1) .plusMonths(1) .plusDays(1) .plusHours(1) .plusMinutes(1) .plusSeconds(1) .plusMilliseconds(1) val dateMinus = datePlus .minusYears(1) .minusMonths(1) .minusDays(1) .minusHours(1) .minusMinutes(1) .minusSeconds(1) .minusMilliseconds(1) println("date: ${date.format()}") println("datePlus: ${datePlus.format()}") println("dateMinus: ${dateMinus.format()}") println() @Test fun date2LocalDate2date() { println("# 拡張関数(Date <-> LocalDate)") val date = "2021/09/01".toDate() println("date: $date") println("date.toLocalDate(): ${date.toLocalDate()}") println("localDate.toDate(): ${date.toLocalDate().toDate()}") println() @Test fun date2LocalDateTime2date() { println("# 拡張関数(Date <-> LocalDateTime)") val date = "2021/09/01 12:34:56.789".toDate() println("date: ${date.format()}") println("date.toLocalDateTime(): ${date.toLocalDateTime()}") println("localDateTime.toDate(): ${date.toLocalDateTime().toDate().format()}") println() @Test fun localDate2localDateTime2localDate() { println("# 拡張関数(LocalDate <-> LocalDateTime)") val localDate = LocalDate.of(2021, 9, 1) val localDateTime = localDate.toLocalDateTime() println("localDate: $localDate") println("localDate.toLocalDateTime(): ${localDate.toLocalDateTime()}") println("localDateTime.toLocalDate(): ${localDate.toLocalDateTime().toLocalDate()}") println() @Test fun string2localDate_localDateTime() { println("# 拡張関数(String -> LocalDate, String -> LocalDateTime)") val string1 = "2021/09/01" val string2 = "2021/09/01 12:34:56.789" println("\"2021/09/01\".toLocalDate(): ${string1.toLocalDate()}") println("\"2021/09/01 12:34:56.789\".toLocalDateTime(): ${string2.toLocalDateTime()}") println()

            # dateの拡張メソッド(plus/minus)
            date:      2021/09/01 12:34:56.789
            datePlus:  2022/10/02 13:35:57.790
            dateMinus: 2021/09/01 12:34:56.789
            # 拡張関数(LocalDate <-> LocalDateTime)
            localDate:                   2021-09-01
            localDate.toLocalDateTime(): 2021-09-01T00:00
            localDateTime.toLocalDate(): 2021-09-01
            # 拡張関数(String <-> Date) ※日時パターン指定なし
            string:          20210901
            string.toDate(): Wed Sep 01 00:00:00 JST 2021
            date.format():   2021/09/01
            # 拡張関数(String <-> Date) ※日時パターン指定あり
            string:          2021.09.01
            pattern:         yyyy.MM.dd
            string.toDate(): Wed Sep 01 00:00:00 JST 2021
            date.format():   2021.09.01
            # 拡張関数(Date <-> LocalDateTime)
            date:                   2021/09/01 12:34:56.789
            date.toLocalDateTime(): 2021-09-01T12:34:56.789
            localDateTime.toDate(): 2021/09/01 12:34:56.789
            # dateの拡張プロパティ
            string:                2021/09/01 12:34:56.789
            date.yearValue:        2021
            date.monthValue:       9
            date.dayValue:         1
            date.hourValue:        12
            date.minuteValue:      34
            date.secondValue:      56
            date.millisecondValue: 789
            # 拡張関数(Date <-> LocalDate)
            date:               Wed Sep 01 00:00:00 JST 2021
            date.toLocalDate(): 2021-09-01
            localDate.toDate(): Wed Sep 01 00:00:00 JST 2021
            # 拡張関数(String -> LocalDate, String -> LocalDateTime)
            "2021/09/01".toLocalDate():                  2021-09-01
            "2021/09/01 12:34:56.789".toLocalDateTime(): 2021-09-01T12:34:56.789

            7回シリーズでお届けしました「KotlinでDateの操作を簡単にするライブラリをつくってみる」の連載記事はいかがでしたでしょうか?

            ここまで読んでくれた方、オレオレライブラリ作りたくなったんじゃありませんか?

            作りましょう!オレオレライブラリ。
            やりましょう!車輪の再発明。

            自分でやってみないとわからないことがある。
            きっと無駄になることはないでしょう。

            ただし、業務ではなく趣味でじっくりやることをお勧めします。
            そのほうがクリエイティブにやれると思います。

            ではまた。

  •  
    推荐文章