
Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Dealing with a string that contains a date is a common problem when writing real-life application code. In this tutorial, we showcase three ways to handle that problem from a Scala program: using simple parsing, regular expressions, and standard libraries.
If the date format is known up-front, we can split the string, then parse the components:
class DateParser {
// ...
def simpleParse(dateString: String): Option[GregorianCalendar] = {
dateString.split("/").toList match {
case yyyy :: mm :: dd :: Nil =>
Try(
new GregorianCalendar(
yyyy.toInt,
mm.toInt - 1,
dd.toInt - 1
)
).toOption
case _ => None
}
}
// ...
}
And we can test it:
class DateParserSpec extends AnyWordSpec with Matchers {
val parser = new DateParser
"a simple parser" should {
"retrieve elements of a string when it matches the predefined format" in {
val maybeDate = parser.simpleParse("2022/02/14")
// the format is "yyyy/mm/dd"
assert(maybeDate.isDefined)
assert(maybeDate.get.get(Calendar.YEAR) == 2022)
// in the case class, elements are 0-based:
assert(maybeDate.get.get(Calendar.MONTH) == 2 - 1)
assert(maybeDate.get.get(Calendar.DAY_OF_MONTH) == 14 - 1)
}
"fail to retrieve elements if date includes unexpected time" in {
val maybeDateTime = parser.simpleParse("2022/02/14T20:30:00")
assert(maybeDateTime.isEmpty)
}
}
// ...
}
This approach brings several problems that we need to keep in mind:
If the string format is a bit more complex, a variation of the above using regular expressions can do the trick:
class DateParser {
// ...
def regexParse(regex: Regex, dateString: String): Option[GregorianCalendar] = {
val groupsIteratorOption = Try(
regex.findAllIn(dateString).matchData
).toOption
groupsIteratorOption
.map(_.next())
.flatMap(iterator =>
if (iterator.groupCount < 3) None
else
Some(
new GregorianCalendar(
iterator.group(1).toInt,
iterator.group(2).toInt - 1,
iterator.group(3).toInt - 1
)
)
)
}
}
Let’s test it:
class DateParserSpec extends AnyWordSpec with Matchers {
val parser = new DateParser
// ...
"a regular expression parser" should {
// Note that this is a very naive regular expression,
// just to show a point. Don't use it for real.
val naiveDateRegExp = "^([0-9]{4})/([0-1]?[0-9])/([1-3]?[0-9]).*".r
"retrieve date elements when it matches the regular expression" in {
val maybeDate = parser.regexParse(naiveDateRegExp, "2022/02/14")
assert(maybeDate.isDefined)
assert(maybeDate.get.get(Calendar.YEAR) == 2022)
// in the case class, elements are 0-based:
assert(maybeDate.get.get(Calendar.MONTH) == 2 - 1)
assert(maybeDate.get.get(Calendar.DAY_OF_MONTH) == 14 - 1)
}
"retrieve date elements even if it includes unexpected elements (time)" in {
val maybeDate = parser.regexParse(naiveDateRegExp, "2022/02/14T20:30:00")
assert(maybeDate.isDefined)
assert(maybeDate.get.get(Calendar.YEAR) == 2022)
// in the case class, elements are 0-based:
assert(maybeDate.get.get(Calendar.MONTH) == 2 - 1)
assert(maybeDate.get.get(Calendar.DAY_OF_MONTH) == 14 - 1)
}
}
// ...
}
Sometimes, the date format isn’t completely under our control. It might contain timestamp or zone information, various time formats and locale-specific conventions, and so forth.
Luckily for us, there are two things we can do to improve the situation:
The language support for dates and times was limited before Java 8. Possibly, the best option for developers was using the fantastic Joda Time library.
Fortunately, with version 8, the Java language standard libraries assimilated the Joda Time library’s concepts first proposed. They appear under the package java.time.
Let’s look at how we can parse a string in ISO date format to LocalDate using java.time package:
val dateStr = "2024-09-19"
LocalDate.parse(dateStr) shouldBe LocalDate.of(2024, 9, 19)
We can use the parse() method on LocalDate to parse the string to the LocalDate instance. If the date string is in a different format, we can provide the format during parsing:
val dateStr = "19.09.2024"
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")
LocalDate.parse(dateStr, formatter) shouldBe LocalDate.of(2024, 9, 19)
Furthermore, if the string contains a time component, we can parse it into a LocalDateTime instance:
val dateStr = "19.09.2024 10:20:30"
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss")
LocalDateTime.parse(dateStr, formatter) shouldBe LocalDateTime.of(2024, 9, 19, 10, 20, 30)
More features of the Java standard Date/Time library are available in the official documentation.
Additionally, we can use the nscala-time library, which is a wrapper over java-time and joda-time APIs to make the date handling easier.
For the sake of example, let’s assume that we have a date, with time, in 24-hour format. We want to book a reservation to celebrate Valentine’s Day in a fancy restaurant at 8:30 pm. That includes time zone information for Paris, France: 2022-02-14T20:30:00Z[Europe/Paris].
Since the string represents a date and time for a specific place (Paris), it’s a zoned date/time. Therefore, we’re looking for the class ZonedDateTime.
On the other hand, if we weren’t interested in time zones, or differences between the time in different zones, we could limit ourselves to a wall clock date/time by using the local date/time (class LocalDateTime).
For this example, we don’t even need to write code. Let’s illustrate how to use the library:
{
"a library-based parser" should {
"retrieve date elements when a complex date/time string is passed" in {
val attemptedParse = Try(ZonedDateTime.parse("2022-02-14T20:30:00.00Z[Europe/Paris]"))
assert(attemptedParse.isSuccess)
val zdt = attemptedParse.get
assert(zdt.get(ChronoField.YEAR) == 2022)
assert(zdt.get(ChronoField.MONTH_OF_YEAR) == 2)
assert(zdt.get(ChronoField.DAY_OF_MONTH) == 14)
assert(zdt.get(ChronoField.HOUR_OF_DAY) == 21)
assert(zdt.get(ChronoField.MINUTE_OF_HOUR) == 30)
assert(zdt.getZone == ZoneId.of("Europe/Paris"))
}
"fail to retrieve date elements when an invalid date/time is passed" in {
val attemptedParse =
Try(ZonedDateTime.parse("2022-02-14"))
assert(attemptedParse.isFailure)
assert(
attemptedParse.failed.get.getMessage.contains("could not be parsed")
)
}
}
}
Although parsing dates is a complex problem, it becomes much more manageable by adopting standards and using the standard Java library for date and time.