結論
input[type="datetime-local"]
をPlay Frameworkで受け取る時のValidationルールはlocalDateTime("yyyy-MM-dd'T'HH:mm")
本文
背景と問題
Play Frameworkのチュートリアルとして、スケジュール管理ツールのようなものを作ろうとしていました。予定のタイトルと開始日時・終了日時をDBに蓄積していくようなWebアプリケーションです。
日時の入力を自分で実装するのは大変なので、input[type="datetime-local"]
要素を利用しました。この要素を利用すると、ブラウザが良い感じに日時を選択するUIを表示してくれます。
この値をPlay Frameworkで受け取ろうとして、以下のようなControllerを作りました。
package controllers import java.time.LocalDateTime import import javax.inject._ import play.api.i18n._ import play.api.mvc._ import play.api.data.Form import play.api.data.Forms._ case class CreateScheduleRequest(title: String, startsAt: LocalDateTime, endsAt: LocalDateTime) @Singleton class CalendarController @Inject() (mcc: MessagesControllerComponents) extends MessagesAbstractController(mcc) { private val form = Form( mapping( "title" -> nonEmptyText(maxLength = 30), "startsAt" -> localDateTime, "endsAt" -> localDateTime )(CreateScheduleRequest.apply)(CreateScheduleRequest.unapply) ) def postAdd = Action { implicit request => form .bindFromRequest() .fold( error => { // エラー表示 }, postRequest => { // Scheduleの追加処理 } ) } }
ところが、input[type="datetime-local"]
で日時を選んで送信しても、localDateTimeじゃないよというバリデーションエラーが表示されてしまいます。
この要素を使ってフォームを送信すると、値としては 2018-06-12T19:30
のような形になります。これはJavaでいうと ISO_LOCAL_DATE_TIME
と呼ばれるフォーマットになります。
解決法の模索
まず最初に、内部的に LocalDateTime.parse()
を呼んでいて、これに失敗しているのではないかと考え、REPLで実験してみました。
scala> java.time.LocalDateTime.parse("2022-04-20T18:21") val res0: java.time.LocalDateTime = 2022-04-20T18:21
これはうまくいくようです。
次に、localDateTime
には nonEmptyText(maxLength = 30)
と同じように引数が渡せるのではないかと睨みました。実際、 def localDateTime(pattern)
という定義がありました。コメントには
@param pattern the date pattern, as defined in
java.time.format.DateTimeFormatter
と書かれています。ならばと以下のように書いてみましたがType Errorになります。
import java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME ... "startsAt" -> localDateTime(ISO_LOCAL_DATE_TIME),
なんと、patternはstringのみしか許容しないようです。定数として定義済みのフォーマッタも使えないのかあという気持ちになりました。
解決法
- "startsAt" -> localDateTime, + "startsAt" -> localDateTime("yyyy-MM-dd'T'HH:mm"), - "endsAt" -> localDateTime + "endsAt" -> localDateTime("yyyy-MM-dd'T'HH:mm")
上記のように文字列でフォーマットを教えてあげることで解決しました。Tという固定の文字列をpatternとしてどう表現するか分からず、地味に苦戦しました。(シングルクォートすれば良いようです。)
感想
WebのフォームからlocalDateTimeを送信する時に、input[type="datetime-local"]
を使うのは自然なムーブだと思うので、こちらのフォーマットをデフォルトにして欲しいなと思いました。