Scala 3 dates: How to parse strings into dates (LocalDate, DateTimeFormatter)

This is an excerpt from the Scala Cookbook, 2nd Edition. This is Recipe 3.12, How to Parse Scala Strings Into Dates.

Problem

While using Scala (Scala 2 or 3), you need to parse a Scala String into one of the date/time types introduced in Java 8, and still used in Java 11, 14, 17, etc.

Scala Solution

If your String is already in the expected format, pass it to the parse method of the desired class. If the String is not in the expected (default) format, create a formatter to define the format you want to accept. The following examples demonstrate the expected formats, and other solutions.

Scala and LocalDate

This example shows the default format for java.time.LocalDate:

import java.time.LocalDate
val d = LocalDate.parse("2020-12-10")   // LocalDate = 2020-12-10

If you try to pass a string into parse with the wrong format, you’ll get an exception:

val d = LocalDate.parse("2020/12/10")   // java.time.format.DateTimeParseException

To accept a string in a different format, create a formatter for the desired pattern:

import java.time.format.DateTimeFormatter
val df = DateTimeFormatter.ofPattern("yyyy/MM/dd")
val d = LocalDate.parse("2020/12/10", df)   // LocalDate = 2020-12-10

Scala and LocalTime

These examples demonstrate the default format for java.time.LocalTime:

import java.time.LocalTime
val t = LocalTime.parse("01:02")     //01:02
val t = LocalTime.parse("13:02:03")  //13:02:03

Notice that each field requires a leading 0:

val t = LocalTime.parse("1:02")      //java.time.format.DateTimeParseException
val t = LocalTime.parse("1:02:03")   //java.time.format.DateTimeParseException

These examples demonstrate several ways of using formatters:

import java.time.format.DateTimeFormatter
LocalTime.parse("00:00", DateTimeFormatter.ISO_TIME)
    // 00:00
LocalTime.parse("23:59", DateTimeFormatter.ISO_LOCAL_TIME)
    // 23:59
LocalTime.parse("23 59 59", DateTimeFormatter.ofPattern("HH mm ss"))
    // 23:59:59
LocalTime.parse("11 59 59 PM", DateTimeFormatter.ofPattern("hh mm ss a"))
    // 23:59:59

Scala/LocalDateTime

This example demonstrates the default format for java.time.LocalDateTime:

import java.time.LocalDateTime
val s = "2021-01-01T12:13:14"
val ldt = LocalDateTime.parse(s)   // LocalDateTime = 2021-01-01T12:13:14

These examples demonstrate several ways of using formatters:

import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

val s = "1999-12-31 23:59"
val f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
val ldt = LocalDateTime.parse(s, f)
    // 1999-12-31T23:59

val s = "1999-12-31 11:59:59 PM"
val f = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss a")
val ldt = LocalDateTime.parse(s, f)
    // 1999-12-31T23:59:59

Scala/Instant

java.time.Instant only has one parse method that requires a date/time stamp in the proper format:

import java.time.Instant
Instant.parse("1970-01-01T00:01:02.00Z")   // 1970-01-01T00:01:02Z
Instant.parse("2021-01-22T23:59:59.00Z")   // 2021-01-22T23:59:59Z

Scala/ZonedDateTime

These examples demonstrate the default formats for java.time.ZonedDateTime:

import java.time.ZonedDateTime

ZonedDateTime.parse("2020-12-31T23:59:59-06:00")
    // ZonedDateTime = 2020-12-31T23:59:59-06:00

ZonedDateTime.parse("2020-12-31T23:59:59-00:00[US/Mountain]")
    // ZonedDateTime = 2020-12-31T16:59:59-07:00[US/Mountain]

These examples demonstrate several ways of using formatters with ZonedDateTime:

import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter._

val zdt = ZonedDateTime.parse("2021-01-01T01:02:03Z", ISO_ZONED_DATE_TIME)
    // ZonedDateTime = 2021-01-01T01:02:03Z

ZonedDateTime.parse("2020-12-31T23:59:59+01:00", ISO_DATE_TIME)
    // ZonedDateTime = 2020-12-31T23:59:59+01:00

ZonedDateTime.parse("2020-02-29T00:00:00-05:00", ISO_OFFSET_DATE_TIME)
    // ZonedDateTime = 2020-02-29T00:00-05:00

ZonedDateTime.parse("Sat, 29 Feb 2020 00:01:02 GMT", RFC_1123_DATE_TIME)
    // ZonedDateTime = 2020-02-29T00:01:02Z

Be aware that an improper date (or improperly formatted date) will throw an exception:

ZonedDateTime.parse("2021-02-29T00:00:00-05:00", ISO_OFFSET_DATE_TIME)
  // java.time.format.DateTimeParseException: Text '2021-02-29T00:00:00-05:00'
  // could not be parsed: Invalid date 'February 29' as '2021' is not a leap year

ZonedDateTime.parse("Fri, 29 Feb 2020 00:01:02 GMT", RFC_1123_DATE_TIME)
  // java.time.format.DateTimeParseException: Text
  // 'Fri, 29 Feb 2020 00:01:02 GMT' could not be parsed: Conflict found:
  // Field DayOfWeek 6 differs from DayOfWeek 5 derived from 2020-02-29

In summary, if you’re using Scala 2 or Scala3, and you need to parse a string into one of the date/time types introduced in Java 8, I hope this is helpful.