Diary of a Perpetual Student

Perpetual Student: A person who remains at university far beyond the normal period

input[type="datetime-local"]の値をPlay Frameworkで受け取るには

結論

  • input[type="datetime-local"] をPlay Frameworkで受け取る時のValidationルールは localDateTime("yyyy-MM-dd'T'HH:mm")

本文

背景と問題

Play Frameworkのチュートリアルとして、スケジュール管理ツールのようなものを作ろうとしていました。予定のタイトルと開始日時・終了日時をDBに蓄積していくようなWebアプリケーションです。

日時の入力を自分で実装するのは大変なので、input[type="datetime-local"] 要素を利用しました。この要素を利用すると、ブラウザが良い感じに日時を選択するUIを表示してくれます。

developer.mozilla.org

この値を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"] を使うのは自然なムーブだと思うので、こちらのフォーマットをデフォルトにして欲しいなと思いました。