#scala #lift
#scala #лифт
Вопрос:
У меня есть поле ввода даты в моем приложении lift, и я хочу проверить, что введенная пользователем дата находится в правильном формате: дд / мм / гггг.
Как я могу написать проверку регулярных выражений для этого в scala? Я просмотрел примеры сопоставления с шаблоном, но это кажется чрезмерно сложным.
PS: Мне не нужно использовать regex, любые другие альтернативы приветствуются!
Ответ №1:
SimpleDateFormat
является уродливым и (что более тревожно) не потокобезопасным. Если вы попытаетесь одновременно использовать один и тот же экземпляр в 2 или более потоках, то ожидайте, что все произойдет самым неприятным образом.
JodaTime намного приятнее:
import org.joda.time.format._
val fmt = DateTimeFormat forPattern "dd/MM/yyyy"
val input = "12/05/2009"
val output = fmt parseDateTime input
Если он выдает IllegalArgumentException
, значит, дата была недействительной.
Поскольку я подозреваю, что вы захотите узнать фактическую дату, если она была действительной, вы можете вернуть Option[DateTime]
, с None
если она была недействительной.
def parseDate(input: String) = try {
Some(fmt parseDateTime input)
} catch {
case e: IllegalArgumentException => None
}
В качестве альтернативы используйте Either
для захвата фактического исключения, если форматирование было невозможно:
def parseDate(input: String) = try {
Right(fmt parseDateTime input)
} catch {
case e: IllegalArgumentException => Left(e)
}
Обновить
Чтобы затем использовать Either
, у вас есть две основные тактики:
сопоставьте одну из двух сторон:
parseDate(input).left map (_.getMessage)
//will convert the Either[IllegalArgumentException, DateTime]
//to an Either[String, DateTime]
сверните ее:
parseDate(input) fold (
_ => S.error(
"birthdate",
"Invalid date. Please enter date in the form dd/mm/yyyy."),
dt => successFunc(dt)
)
Конечно, эти два параметра могут быть составлены:
parseDate(input).left map (_.getMessage) fold (
errMsg => S.error("birthdate", errMsg), //if failure (Left by convention)
dt => successFunc(dt) //if success (Right by convention)
)
Комментарии:
1. Просто обратите внимание, что если она будет использоваться в Lift, вам лучше выбрать Box, а не Option с точки зрения оболочки. Поле позволит вам сохранить ошибку.
2. @arcticpenguin — Лично мне очень не нравится
Box
конструкция Lift, и я при первой же возможности преобразую их вOption
s, когда API будет вынужден принять эти вещи. Если мне нужно также отслеживать исключения монадически, я буду использоватьEither
в качестве предпочтения, не в последнюю очередь потому, что я могу легко сопоставить исключение сString
или каким-либо другим типом для ведения журнала или представления конечному пользователю.3. Спасибо! Мне это нравится. Сейчас у меня это работает как «validateDate (дата рождения) соответствует { case Some (_) => // success case None =>S.ошибка («дата рождения», «Недопустимая дата. Пожалуйста, введите дату в форме дд / мм /гггг.») }»
4. Я все еще не знаю, для чего нужно «Либо» — хе-хе, но я знаю вариант — поэтому я выбрал это.
5. fmt = ГГГГ / ММ / ДД, input = 2015/02/34 Исключение не выдается
Ответ №2:
Как написал неизвестный пользователь, вам следует использовать какую-нибудь библиотеку, которая знает, как правильно обрабатывать даты, включая дни в определенном месяце и високосные годы.
SimpleDateFormat
не очень интуитивно понятен в отношении переноса полей и изначально принимает неправильные даты, просто перенося другие поля. Чтобы предотвратить это, вы должны вызвать setLenient(false)
для этого. Также имейте в виду, что SimpleDateFormat
это не потокобезопасно, поэтому вам нужно создавать новый экземпляр каждый раз, когда вы хотите его использовать:
def validate(date: String) = try {
val df = new SimpleDateFormat("dd/MM/yyyy")
df.setLenient(false)
df.parse(date)
true
} catch {
case e: ParseException => false
}
В качестве альтернативы вы также можете использовать Joda Time, который немного более интуитивно понятен, чем Java Date API, и предлагает потокобезопасные форматы даты:
val format = DateTimeFormat.forPattern("dd/MM/yyyy")
def validate(date: String) = try {
format.parseMillis(date)
true
}
catch {
case e: IllegalArgumentException => false
}
Ответ №3:
Хорошей практикой является определение DateTimeFormatter
экземпляра в объекте, поскольку он потокобезопасен и неизменяем.
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
}
Ответ №4:
Я бы использовал не регулярное выражение, а SimpleDateFormat (что не так просто, как мы увидим).
Регулярное выражение, которое разрешает использовать 28 и 30 в качестве дня, но не 38, разные длины месяцев и високосные годы, может быть интересной задачей, но не для кода реального мира.
val df = new java.text.SimpleDateFormat ("dd/MM/yyyy")
(Я предполагаю, что M как в большом месяце, а не m как в маленькой минуте).
Теперь давайте начнем с ошибки:
scala> df.parse ("09/13/2001")
res255: java.util.Date = Wed Jan 09 00:00:00 CET 2002
hoppla — он очень терпимый и переносит месяцы на следующий год. Но мы можем получить ее с помощью второго процесса форматирования:
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
Я надеюсь, что вы не привязаны к регулярному выражению и можете его использовать.
Комментарии:
1. О нет, я могу использовать все, что мне нравится. Я исправил вопрос. Это выглядит интересно, спасибо!
2. Итак, вы говорите, что для проверки, если это неправильно — я должен сопоставить это — и преобразовать это обратно в строку?
3. Также — проверяет ли это, что дата является «реальной» датой, или только то, что она соответствует общему синтаксису. Например, могу ли я получить 31 апреля 2011?
Ответ №5:
исходя из этого, мы можем подтвердить дату в строке, а также получить ожидаемый ответ о дате путем синтаксического анализа в формате, подобном «дд / ММ / гггг».
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
}