joesan
joesan

Reputation: 15435

Handling and throwing Exceptions in 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.

Upvotes: 4

Views: 1301

Answers (3)

joesan
joesan

Reputation: 15435

I made it sweet like this now! I like this a lot better! Use this if you want to collect all the successes and all the failures. Note that, this might be a bit in-efficient when you need to break out of the loop as soon as you find one success!

implicit def dateTimeCSVConverter: CsvFieldReader[DateTime] = (s: String) => Try {
    val (successes, failures) = dateFormats.map {
      case format => Try(DateTimeFormat.forPattern(format).parseDateTime(s))
    }.partition(_.isSuccess)
    if (successes.nonEmpty)
      successes.head.get
    else
      failures.head.get
  }

Upvotes: 3

Krzysztof Atłasik
Krzysztof Atłasik

Reputation: 22625

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.

Upvotes: 6

w4bo
w4bo

Reputation: 905

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")
    }
  })

Upvotes: 5

Related Questions