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 reg
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
}
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)
)
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.
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
val fmt = DateTimeFormat forPattern "MM/dd/yyyy"
def validate(date: String) = Try(fmt.parseDateTime(date)).isSuccess
}
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
}