Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling and throwing Exceptions in Scala

Tags:

scala

I have the following implementation:

  val dateFormats = Seq("dd/MM/yyyy", "dd.MM.yyyy")
  implicit def dateTimeCSVConverter: CsvFieldReader[DateTime] = (s: String) => Try {
    val elem = dateFormats.map {
      format =>
        try {
          Some(DateTimeFormat.forPattern(format).parseDateTime(s))
        } catch {
          case _: IllegalArgumentException =>
            None
        }
    }.collectFirst {
      case e if e.isDefined => e.get
    }
    if (elem.isDefined)
      elem.get
    else
      throw new IllegalArgumentException(s"Unable to parse DateTime $s")
  }

So basically what I'm doing is that, I'm running over my Seq and trying to parse the DateTime with different formats. I then collect the first one that succeeds and if not I throw the Exception back.

I'm not completely satisfied with the code. Is there a better way to make it simpler? I need the exception message passed on to the caller.

like image 278
joesan Avatar asked Dec 07 '22 12:12

joesan


2 Answers

The one problem with your code is it tries all patterns no matter if date was already parsed. You could use lazy collection, like Stream to solve this problem:

 def dateTimeCSVConverter(s: String) = Stream("dd/MM/yyyy", "dd.MM.yyyy")
       .map(f => Try(DateTimeFormat.forPattern(format).parseDateTime(s))
       .dropWhile(_.isFailure)
       .headOption

Even better is the solution proposed by jwvh with find (you don't have to call headOption):

 def dateTimeCSVConverter(s: String) = Stream("dd/MM/yyyy", "dd.MM.yyyy")
       .map(f => Try(DateTimeFormat.forPattern(format).parseDateTime(s))
       .find(_.isSuccess)

It returns None if none of patterns matched. If you want to throw exception on that case, you can uwrap option with getOrElse:

 ...
 .dropWhile(_.isFailure)
 .headOption
 .getOrElse(throw new IllegalArgumentException(s"Unable to parse DateTime $s"))

The important thing is, that when any validation succeedes, it won't go further but will return parsed date right away.

like image 103
Krzysztof Atłasik Avatar answered Dec 28 '22 08:12

Krzysztof Atłasik


This is a possible solution that iterates through all the options

  val dateFormats = Seq("dd/MM/yyyy", "dd.MM.yyyy")
  val dates = Vector("01/01/2019", "01.01.2019", "01-01-2019")
    dates.foreach(s => {
      val d: Option[Try[DateTime]] = dateFormats
        .map(format => Try(DateTimeFormat.forPattern(format).parseDateTime(s)))
        .filter(_.isSuccess)
        .headOption
      d match {
        case Some(d) => println(d.toString)
        case _ => throw new IllegalArgumentException("foo")
      }
    })

This is an alternative solution that returns the first successful conversion, if any

  val dateFormats = Seq("dd/MM/yyyy", "dd.MM.yyyy")
  val dates = Vector("01/01/2019", "01.01.2019", "01-01-2019")
  dates.foreach(s => {
    dateFormats.find(format => Try(DateTimeFormat.forPattern(format).parseDateTime(s)).isSuccess) match {
      case Some(format) => println(DateTimeFormat.forPattern(format).parseDateTime(s))
      case _ => throw new IllegalArgumentException("foo")
    }
  })
like image 27
w4bo Avatar answered Dec 28 '22 06:12

w4bo