SimpleDateFormatやDateFormatUtilsを使用するとDateを指定したフォーマットで文字列に出力できますが、いつも同じフォーマットで良いのであれば毎回指定するのは面倒ですし、デフォルトの動作を決めた方が記述ミスも防げます。
以下のような仕様の拡張関数を追加しましょう。
ミリ秒が0以外の値であれば "yyyy/MM/dd HH:mm.ss.SSS" で出力する
ミリ秒が0 かつ 時分秒のいずれかが0以外の値であれば "yyyy/MM/dd HH:mm.ss" で出力する
上記以外ならば "yyyy/MM/dd" で出力する
オプションでタイムゾーンを出力する
DateFormatExtension.kt
package extensions.date.format
import extensions.date.property.hourValue
import extensions.date.property.millisecondValue
import extensions.date.property.minuteValue
import extensions.date.property.secondValue
import java.text.SimpleDateFormat
import java.util.*
@param [pattern]
@param [tz]
fun Date.format(pattern: String? = null, tz: String? = null): String {
var sdfFormat = (
if (pattern.isNullOrBlank().not())
pattern!!
else if (this.millisecondValue != 0)
"yyyy/MM/dd HH:mm:ss.SSS"
else if (this.hourValue != 0 || this.minuteValue != 0 || this.secondValue != 0)
"yyyy/MM/dd HH:mm:ss"
"yyyy/MM/dd"
if (tz != null) {
when (tz) {
"z" -> sdfFormat += " z"
else -> sdfFormat += tz
val sdf = SimpleDateFormat(sdfFormat)
return sdf.format(this)
package extensions.date.format
import org.apache.commons.lang3.time.DateUtils
import org.junit.Test
class DateFormatExtensionTest {
@Test
fun format() {
run {
val date = DateUtils.parseDate("2021/09/01 12:34:56.789", "yyyy/MM/dd HH:mm:ss.SSS")
val output = date.format()
println("date.format(): $output")
run {
val date = DateUtils.parseDate("2021/09/01 12:34:56", "yyyy/MM/dd HH:mm:ss")
val output = date.format()
println("date.format(): $output")
run {
val date = DateUtils.parseDate("2021/09/01", "yyyy/MM/dd")
val output = date.format()
println("date.format(): $output")
run {
val date = DateUtils.parseDate("2021/09/01 12:34:56.789", "yyyy/MM/dd HH:mm:ss.SSS")
val pattern = "yyyy/MM/dd HH:mm:ss.SSSz"
val output = date.format(pattern)
println("date.format(\"$pattern\"): $output")
run {
val date = DateUtils.parseDate("2021/09/01 12:34:56.789", "yyyy/MM/dd HH:mm:ss.SSS")
val pattern = "yyyy/MM/dd HH:mm:ss.SSSZ"
val output = date.format(pattern)
println("date.format(\"$pattern\"): $output")
run {
val date = DateUtils.parseDate("2021/09/01 12:34:56.789", "yyyy/MM/dd HH:mm:ss.SSS")
val pattern = "yyyy/MM/dd HH:mm:ss.SSSX"
val output = date.format(pattern)
println("date.format(\"$pattern\"): $output")
run {
val date = DateUtils.parseDate("2021/09/01 12:34:56.789", "yyyy/MM/dd HH:mm:ss.SSS")
val output = date.format(tz = "z")
println("date.format(tz=\"z\"): $output")
run {
val date = DateUtils.parseDate("2021/09/01 12:34:56.789", "yyyy/MM/dd HH:mm:ss.SSS")
val output = date.format(tz = "Z")
println("date.format(tz=\"Z\"): $output")
run {
val date = DateUtils.parseDate("2021/09/01 12:34:56.789", "yyyy/MM/dd HH:mm:ss.SSS")
val output = date.format(tz = "X")
println("date.format(tz=\"X\"): $output")
date.format(): 2021/09/01 12:34:56.789
date.format(): 2021/09/01 12:34:56
date.format(): 2021/09/01
date.format("yyyy/MM/dd HH:mm:ss.SSSz"): 2021/09/01 12:34:56.789JST
date.format("yyyy/MM/dd HH:mm:ss.SSSZ"): 2021/09/01 12:34:56.789+0900
date.format("yyyy/MM/dd HH:mm:ss.SSSX"): 2021/09/01 12:34:56.789+09
date.format(tz="z"): 2021/09/01 12:34:56.789 JST
date.format(tz="Z"): 2021/09/01 12:34:56.789+0900
date.format(tz="X"): 2021/09/01 12:34:56.789+09
なお、日時パターンの記述方法についてはSimpleDateFormatのヘルプを参照してください。
SimpleDateFormat (Java Platform SE 8)
文字列をパースしてDateを取得する
どのような文字列をパースして日付を得たいかはケースバイケースですが、やはりデフォルトの挙動を決め打ちにすることで、フォーマットを都度指定する手間を省き、ミスを防止することができます。
以下のような仕様の拡張関数を追加しましょう。
オプションでタイムゾーンを指定できる。指定しない場合はシステムデフォルトを適用する
タイムゾーンを除く部分について
文字列長が8ならば"yyyyMMdd"としてパースする
文字列長が10ならば"yyyy/MM/dd"としてパースする
文字列長が14ならば"yyyyMMddHHmmss"としてパースする
文字列長が17ならば"yyyyMMddHHmmssSSS"としてパースする
文字列長が19ならば"yyyy/MM/dd HH:mm.ss"としてパースする
文字列長が23ならば"yyyy/MM/dd HH:mm.ss.SSS"としてパースする
上記以外ならば"yyyyMMdd"としてパースする
StringDateExtension.kt
package extensions.string.date
import java.text.SimpleDateFormat
import java.time.ZoneId
import java.util.*
fun String.toDate(pattern: String? = null, tz: String? = null, strict: Boolean = true): Date {
val tokens = this.replace("-", "|-").replace("+", "|+").split("|")
val datePart = tokens[0]
val zonePart = if (tokens.count() == 2) tokens[1] else ""
val zonePattern =
if (tz != null) tz
else if (tokens.count() == 2) "X"
else ""
val sdfPattern = pattern
?: when (datePart.length) {
8 -> "yyyyMMdd"
10 -> "yyyy/MM/dd"
14 -> "yyyyMMddHHmmss"
17 -> "yyyyMMddHHmmssSSS"
19 -> "yyyy/MM/dd HH:mm:ss"
23 -> "yyyy/MM/dd HH:mm:ss.SSS"
else -> "yyyy/MM/dd"
} + zonePattern
try {
val sdf = SimpleDateFormat(sdfPattern)
sdf.isLenient = false
if (zonePart.isNotBlank()) {
sdf.timeZone = TimeZone.getTimeZone(ZoneId.of(zonePart))
val date = sdf.parse(this)
if (strict) {
val reformat =
sdf.format(date).replace("Z", "").replace(zonePart, "").replace(zonePart.replace("00", ""), "")
val thisWithoutZonePart = this.replace(zonePart, "")
if (thisWithoutZonePart != reformat) {
throw IllegalArgumentException("strict=$strict が指定されています。")
return date
} catch (t: Throwable) {
throw IllegalArgumentException("Dateに変換できません。(this=$this, pattern=$pattern)", t)
fun String.toDateOrNull(pattern: String? = null): Date? {
try {
return this.toDate(pattern = pattern)
} catch (t: Throwable) {
return null
package extensions.string.date
import extensions.date.format.format
import org.junit.Test
class StringDateExtensionTest {
@Test
fun toDate() {
println()
println("toDate()")
run {
val text = "20210901"
val date = text.toDate()
println("(length=8) $text -> ${date.format()}")
run {
val text = "2021/09/01"
val date = text.toDate()
println("(length=10) $text -> ${date.format()}")
run {
val text = "20210901123456"
val date = text.toDate()
println("(length=14) $text -> ${date.format()}")
run {
val text = "20210901123456789"
val date = text.toDate()
println("(length=17) $text -> ${date.format()}")
run {
val text = "2021/09/01 12:34:56"
val date = text.toDate()
println("(length=19) $text -> ${date.format()}")
run {
val text = "2021/09/01 12:34:56.789"
val date = text.toDate()
println("(length=23) $text -> ${date.format()}")
run {
val text = "2021/09/01 12:34:56.789+00"
val date = text.toDate()
println("(length=23+TZ) $text -> ${date.format()}")
run {
val text = "2021/09/01 12:34:56.789+09"
val date = text.toDate()
println("(length=23+TZ) $text -> ${date.format()}")
run {
val text = "2021/09/01 12:34:56.789+0900"
val date = text.toDate()
println("(length=23+TZ) $text -> ${date.format()}")
try {
"2021".toDate()
} catch (t: Throwable) {
println(t)
@Test
fun toDateOrNull() {
println()
println("toDateOrNull()")
run {
val text = "20210901"
val date = text.toDateOrNull()
println("(length=8) $text -> ${date?.format()}")
run {
val text = "2021/09/31"
val date = text.toDateOrNull()
println("$text -> $date")
toDate()
(length=8) 20210901 -> 2021/09/01
(length=10) 2021/09/01 -> 2021/09/01
(length=14) 20210901123456 -> 2021/09/01 12:34:56
(length=17) 20210901123456789 -> 2021/09/01 12:34:56.789
(length=19) 2021/09/01 12:34:56 -> 2021/09/01 12:34:56
(length=23) 2021/09/01 12:34:56.789 -> 2021/09/01 12:34:56.789
(length=23+TZ) 2021/09/01 12:34:56.789+00 -> 2021/09/01 21:34:56.789
(length=23+TZ) 2021/09/01 12:34:56.789+09 -> 2021/09/01 12:34:56.789
(length=23+TZ) 2021/09/01 12:34:56.789+0900 -> 2021/09/01 12:34:56.789
java.lang.IllegalArgumentException: Dateに変換できません。(this=2021, pattern=null)
toDateOrNull()
(length=8) 20210901 -> 2021/09/01
2021/09/31 -> null
上記のように"20210901".toDate()を実行すると、文字列長が8なので"yyyyMMdd"のフォーマットが自動的に決定され、パースが実行されます。
自動決定されるフォーマットがプロダクトの要件にマッチする場合は、上記のように生産性を向上することができます。