Let's implement a simple case class that represents a Time object that contains hours and minutes. For simplicity, we will use 24-hour clock.
case class Time(hour: Hour, minutes: Minute)
case class Hour(hour: Int)
case class Minute(minute: Int)
Now let's try to implement a code that parses a string into Time using custom extractor
object Time {
def unapply(stringTime: String): Option[Time] = {
stringTime.split(":", 2) match {
case Array(h, m) =>
Some(Time(h, m))
case _ => None
}
}
}
The problem with this code is that hours and minutes are not validated, therefore strings like "30:-1" will still be considered as valid time.
To solve this problem we can embed conditions into time extractor directly or better define separate Hour and Minute extractors with their own validation rules.
object Hour {
def unapply(stringHour: String): Option[Hour] = {
stringHour.toIntOption match {
case Some(h) if h >= 0 && h <= 23 => Some(Hour(h))
case _ => None
}
}
}
object Minute {
def unapply(stringMinute: String): Option[Minute] = {
stringMinute.toIntOption match {
case Some(m) if m >= 0 && m <= 59 => Some(Minute(m))
case _ => None
}
}
}
These extractors allow to extract minutes and hours from string only if they are valid. Now adjust the Time extractor to use extractors for hours and minutes
object Hour {
def unapply(stringHour: String): Option[Hour] = {
stringHour.toIntOption match {
case Some(h) if h >= 0 && h <= 23 => Some(Hour(h))
case _ => None
}
}
}
object Minute {
def unapply(stringMinute: String): Option[Minute] = {
stringMinute.toIntOption match {
case Some(m) if m >= 0 && m <= 59 => Some(Minute(m))
case _ => None
}
}
}
object Time {
def unapply(stringTime: String): Option[Time] = {
stringTime.split(":",2) match {
case Array(Hour(h), Minute(m)) =>
Some(Time(h, m))
case _ => None
}
}
}
Let's test the code on a few unit tests
object TimeExtractors extends App {
List("12:30", "-1:23", "a:b", "4:59", "1:2:3", "12:77").foreach {
case strTime @ Time(t) => println(s"${strTime} converted to ${t}")
case strTime => println(s"unknown time ${strTime}")
}
}
Output from the code above
12:30 converted to Time(Hour(12),Minute(30))
unknown time -1:23
unknown time a:b
4:59 converted to Time(Hour(4),Minute(59))
unknown time 1:2:3
unknown time 12:77
This article demonstrates how to implement a simple Time case class with validation for hours and minutes using extractors in Scala and how to combine multiple extractors to increase readability