Part of Github Copilot with Kotlin

Kotlin: Convert string to date

Sandy LaneSandy Lane

Video: Kotlin: Convert string to date by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Kotlin: Convert a String to a Date

SimpleDateFormat for legacy code; LocalDate.parse for everything new. Both work, but the modern API is type-safe, immutable, and thread-safe — pick the second when starting fresh.

A common task: turn "2024-06-15" into a date object you can compare, format, and arithmetic on. Copilot suggests the legacy approach first (because it has the most training data); the modern path is a cleaner fit.

The Copilot prompt

fun main() {
  val dateString = "2024-06-15"
  // Parse the string into a Date object using the format "yyyy-MM-dd"

Copilot generates:

import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

fun main() {
  val dateString = "2024-06-15"
  val format = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())

  try {
    val date: Date = format.parse(dateString)!!
    println("Parsed date: $date")
  } catch (e: Exception) {
    println("Failed to parse date: ${e.message}")
  }
}

Walkthrough

Three pieces.

1. Format string. "yyyy-MM-dd" describes the input layout: 4-digit year, dash, 2-digit month, dash, 2-digit day. The case matters — MM is month, mm is minute. Mix them up and you parse silently wrong.

2. Parse call. format.parse(dateString) returns a java.util.Date or null (if the locale doesn't reach a successful parse). The !! says "I'm confident this isn't null" — fine here for hardcoded valid input, dangerous for user input.

3. Try/catch. SimpleDateFormat.parse throws ParseException on malformed input. Always wrap in try/catch when the input could be bad.

Why this is the legacy path

SimpleDateFormat and java.util.Date are pre-Java-8. They have several footguns:

  • Mutable. Date objects can be modified — date.setTime(...). Sharing them across threads is unsafe.
  • Not thread-safe. SimpleDateFormat itself is not thread-safe. Two threads using the same SimpleDateFormat instance produce wrong results or crash.
  • Magic numbers in calendar APIs. Calendar.MONTH is 0-indexed (January = 0, December = 11). Off-by-one bugs.
  • No type for "just a date" or "just a time." Date is a timestamp; you can't say "I want June 15, no time component."

For new code, prefer java.time (Java 8+).

The modern way: java.time

import java.time.LocalDate

fun main() {
  val dateString = "2024-06-15"
  val date = LocalDate.parse(dateString)   // ISO format by default
  println("Parsed date: $date")
}

LocalDate.parse accepts ISO 8601 (yyyy-MM-dd) by default. No format string needed for the common case.

For non-ISO formats, supply a DateTimeFormatter:

import java.time.LocalDate
import java.time.format.DateTimeFormatter

val formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")
val date = LocalDate.parse("15/06/2024", formatter)

LocalDate is immutable, thread-safe, and self-describing (it's a date, not a timestamp). Same for LocalTime (just time), LocalDateTime (date + time, no zone), ZonedDateTime (date + time + zone), Instant (epoch).

Steering Copilot toward java.time

Copilot leans on SimpleDateFormat because that's what most legacy training data uses. To get the modern version, mention it explicitly:

// Parse the string into a LocalDate using java.time

or

// Parse "2024-06-15" into a LocalDate

Naming LocalDate in the comment usually triggers the right import and code path.

Error handling

The modern equivalent of "parse and handle errors":

fun parseDate(s: String): LocalDate? {
  return try {
    LocalDate.parse(s)
  } catch (e: DateTimeParseException) {
    null
  }
}

val date = parseDate("2024-06-15")
val bad = parseDate("not-a-date")
println(date)   // 2024-06-15
println(bad)    // null

Returning null for invalid input is the Kotlin way. The caller decides what to do.

Date arithmetic

val today = LocalDate.now()
val birthday = LocalDate.of(1990, 5, 15)
val age = java.time.Period.between(birthday, today).years

val tomorrow = today.plusDays(1)
val nextMonth = today.plusMonths(1)
val yearFromNow = today.plusYears(1)

val daysSinceBirthday = java.time.temporal.ChronoUnit.DAYS.between(birthday, today)

LocalDate supports natural arithmetic — add days/months/years, calculate periods between two dates. Same on LocalTime, LocalDateTime, etc.

Formatting back to string

val date = LocalDate.of(2024, 6, 15)
println(date.toString())    // "2024-06-15" (ISO default)
println(date.format(DateTimeFormatter.ofPattern("MMM dd, yyyy")))   // "Jun 15, 2024"
println(date.format(DateTimeFormatter.ofPattern("EEEE")))           // "Saturday"

toString() produces ISO 8601 by default. Custom formats via DateTimeFormatter.ofPattern.

Common mistakes

Mixing mm (minutes) and MM (months). SimpleDateFormat("yyyy-mm-dd") is silently wrong — minutes will fill the month slot.

Not handling ParseException. User input is always potentially malformed.

Using SimpleDateFormat from multiple threads. Use ThreadLocal<SimpleDateFormat> or — better — switch to DateTimeFormatter, which is thread-safe.

Force-unwrapping format.parse(null). parse returns null for unparseable input on some locales; !! crashes.

Locale confusion. "Jan 15, 2024" parses with one locale, fails with another. Always specify Locale.ENGLISH (or Locale.ROOT) for machine-readable formats.

Common Copilot habits

When asked to "parse a date string":

  • Default suggestion: SimpleDateFormat.
  • Mentioning LocalDate in the comment: java.time.LocalDate.parse.
  • Mentioning ISO 8601: LocalDate.parse (no format string).
  • Mentioning thread safety or immutability: DateTimeFormatter.

Naming the API in the comment is the lever. Without explicit mention, you get whatever Copilot considers the "most common" code, which often reflects training data from before Java 8.

What's next

Episode 4: Create a random date in Kotlin. Generate a random LocalDate between two bounds — useful for test data and generating plausible-looking dummy records.

Recap

SimpleDateFormat is the legacy parser — works, but mutable, not thread-safe, format-string typo-prone. LocalDate.parse(s) is the modern way — immutable, thread-safe, ISO 8601 default. For non-ISO formats, supply a DateTimeFormatter. Handle parse failures with try/catch returning null. Steer Copilot to the modern API by mentioning LocalDate or DateTimeFormatter in the comment.

Next episode: random date generation.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.