Search code examples
javakotlindatetimejava-time

Kotlin parsing date which is in format - Jan 1 2023 12:00AM to 2023-01-01 00:00:00.0


It appears that Kotlin is very specific regarding the formats of Date. I am receiving a string in this "Jan 1 2023 12:00AM" and wanted to convert it into this "2023-01-01 00:00:00.0" format. I looked around but couldn't find a solution.

fun FormatDateTime(dateTimeString: String): String{
    val DATE_FORMAT = DateTimeFormatter.ofPattern("MMM dd uuuu")
    var dat = dateTimeString.substring(0, dateTimeString.length-7).trim() //Removing ' 12:00AM' part
    val changeDate = LocalDateTime.parse(dat, DATE_FORMAT)
    //After getting the date, reformat it again to '2023-01-01 00:00:00.0'

   return "" //return formatted date string, currently returning empty string
}

But its failing. The error is as follows - java.time.format.DateTimeParseException: Text 'Jan 1 2023' could not be parsed at index 4. Is there any way to achieve it?

Made some changes to Slaw's answer and not sure if this going break in which scenarios except if the code runs in distinct geographical locations.

fun convert(input: String): String {
    var sanitizedInput = input.replace("\\s+".toRegex(), " ").trim()

    return LocalDateTime.parse(sanitizedInput, DateTimeFormatter.ofPattern("MMM d uuuu hh:mma"))
        .format(DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.S"))
}

Solution

  • You're using the MMM dd uuuu pattern to parse the input string. The dd means it expects single-digit days to be padded by a leading 0. But your example input, Jan 1 2023 12:00AM, does not have that padding. Hence the error at index 4. You should be using d instead of dd.

    You should also consider parsing the entire input string instead of a substring. Then use the truncatedTo API of the LocalDateTime object to set the time to zero. I assume this is what you want since you're currently trying to drop the time value from the input string (via the substring call) and your example output still has its time as midnight. If you don't want this then remove the truncatedTo call in the example below.

    Based on your question, the following example meets your needs:

    import java.time.LocalDateTime
    import java.time.format.DateTimeFormatter
    import java.time.temporal.ChronoUnit
    import java.util.Locale
    
    val INPUT_FORMAT = DateTimeFormatter.ofPattern("MMM d uuuu hh:mma", Locale.US)
    val OUTPUT_FORMAT = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.S", Locale.US)
    
    fun main() {
        val input = "Jan 1 2023 12:00AM"
        println("$input => ${convert(input)}")
    }
    
    fun convert(input: String): String {
        return LocalDateTime.parse(input, INPUT_FORMAT)
                .truncatedTo(ChronoUnit.DAYS)
                .format(OUTPUT_FORMAT)
    }
    

    Output:

    Jan 1 2023 12:00AM => 2023-01-01 00:00:00.0
    

    I explicitly specified the locale so that the localizable parts of the input string ("Jan", "AM"/"PM") are properly parsed. I chose Locale.US for the example, but you can use any locale that uses the right values for MMM and a. Or even let the locale be determined by configuration at run-time.

    You could rely on the system default locale, but then your code is likely to break on computers with different locales than your test computer.


    Note an alternative to truncatedTo(ChronoUnit.DAYS) is with(LocalTime.MIDNIGHT).