9

I have a date input box in my lift application, and I want to check that a user-entered date is in correct format: dd/mm/yyyy.

How can I write a regex check for this in scala? I've looked at pattern matching examples - but that seems over-complicated.

PS: I don't have to use regex, any other alternatives are welcome!

Andriy Drozdyuk
  • 58,435
  • 50
  • 171
  • 272

5 Answers5

15

SimpleDateFormat is ugly and (more disturbingly) non-thread-safe. If you try to simultaneously use the same instance in 2 or more threads then expect things to blow up in a most unpleasant fashion.

JodaTime is far nicer:

import org.joda.time.format._
val fmt = DateTimeFormat forPattern "dd/MM/yyyy"
val input = "12/05/2009"
val output = fmt parseDateTime input

If it throws an IllegalArgumentException, then the date wasn't valid.

As I suspect you'll want to know the actual date if it was valid, you may want to return an Option[DateTime], with None if it was invalid.

def parseDate(input: String) = try {
  Some(fmt parseDateTime input)
} catch {
  case e: IllegalArgumentException => None
}

Alternatively, use an Either to capture the actual exception if formatting wasn't possible:

def parseDate(input: String) = try {
  Right(fmt parseDateTime input)
} catch {
  case e: IllegalArgumentException => Left(e)
}

UPDATE

To then use the Either, you have two main tactics:

map one of the two sides:

parseDate(input).left map (_.getMessage)
//will convert the Either[IllegalArgumentException, DateTime]
//to an Either[String, DateTime]

fold it:

parseDate(input) fold (
  _ => S.error(
    "birthdate",
    "Invalid date. Please enter date in the form dd/mm/yyyy."),
  dt => successFunc(dt)
)

Of course, the two can be composed:

parseDate(input).left map (_.getMessage) fold (
  errMsg => S.error("birthdate", errMsg), //if failure (Left by convention)
  dt => successFunc(dt) //if success (Right by convention)
)
Kevin Wright
  • 49,540
  • 9
  • 105
  • 155
  • Just a note that if its to be used in Lift, you're better off with Box versus Option in terms of a wrapper. Box will allow you to store the error. – andyczerwonka May 17 '11 at 16:25
  • 1
    @arcticpenguin - I personally have a stong dislike of Lift's `Box` construct, and will convert them to `Option`s at the earliest opportunity when forced to accept the things by the API. If I need to also track exceptions monadically then I'll use an `Either` in preference, not least because I can easily map the exception to a `String` or some other type for logging or presentation to the end user. – Kevin Wright May 17 '11 at 16:35
  • Thanks! I like it. I have it working now as "validateDate(birthdate) match { case Some(_) => // success case None =>S.error("birthdate", "Invalid date. Please enter date in the form dd/mm/yyyy.") }" – Andriy Drozdyuk May 17 '11 at 20:29
  • I still don't know what the Either is for - hehe, but I know the Option - so I went with that. – Andriy Drozdyuk May 17 '11 at 20:30
  • fmt = YYYY/MM/DD, input = 2015/02/34 No exception is thrown – Hao Ren Sep 29 '16 at 09:42
2

It's a good practice to define the DateTimeFormatter instance in an object since it's thread-safe and immutable.

  object DateOfBirth{
    import org.joda.time.format.DateTimeFormat
    import scala.util.Try
    private val fmt = DateTimeFormat.forPattern("MM/dd/yyyy")
    def validate(date: String) = Try(fmt.parseDateTime(date)).isSuccess
  }
Ben Aiad
  • 78
  • 1
  • 6
2

As user unknown has written you should use some library that knows how to handle dates correctly including days per specific month and leap years.

SimpleDateFormat is not very intuitive regarding rollovers of fields and initially accepts wrong dates by just rolling over the other fields. To prevent it from doing so you have to invoke setLenient(false) on it. Also keep in mind that SimpleDateFormat is not thread-safe so you need to create a new instance every time you want to use it:

def validate(date: String) = try {
    val df = new SimpleDateFormat("dd/MM/yyyy")
    df.setLenient(false)
    df.parse(date)
    true
  } catch {
    case e: ParseException => false
  }

Alternatively you may also use Joda Time which is a bit more intuitive than the Java Date APIs and offers thread-safe date formats:

val format = DateTimeFormat.forPattern("dd/MM/yyyy")

def validate(date: String) = try {
    format.parseMillis(date)
    true
  }
  catch {
    case e: IllegalArgumentException => false
  }
Moritz
  • 14,144
  • 2
  • 56
  • 55
1

from this we can validate date in string as well as we get the expected date response by parsing in the format like "dd/MM/yyyy".

  try {  val format = DateTimeFormat.forPattern("dd/MM/yyyy")
  format.parseMillis(dateInString)
  val df = new SimpleDateFormat("dd/MM/yyyy")
  val newDate = df.parse(dateInString)
  true
    } catch {
      case e: ParseException => false

      case e: IllegalArgumentException => false
    }
Nilesh
  • 2,054
  • 3
  • 23
  • 43
1

I wouldn't use a regex, but SimpleDateFormat (which isn't that simple, as we will see).

A regular expression which handles to allow 28 and 30 as day, but not 38, different month-lengths and leap years, might be an interesting challenge, but not for real world code.

val df = new java.text.SimpleDateFormat ("dd/MM/yyyy")

(I assume M as in big Month, not m as in small minute).

Now, let's start with an error:

scala> df.parse ("09/13/2001")                                
res255: java.util.Date = Wed Jan 09 00:00:00 CET 2002

hoppla - it is very tolerant, and wraps months around to the next year. But we can get it with a second formatting process:

scala> val sInput = "13/09/2001"
sInput: java.lang.String = 13/09/2001

scala> sInput.equals (df.format (df.parse (sInput))) 
res259: Boolean = true

scala> val sInput = "09/13/2001"                     
sInput: java.lang.String = 09/13/2001

scala> sInput.equals (df.format (df.parse (sInput))) 
res260: Boolean = false

I hope you aren't bound to regex, and can use it.

user unknown
  • 35,537
  • 11
  • 75
  • 121