Storing Joda DateTimes as MongoDB ISO dates using Play-reactivemongo plugin

2,067 views
Skip to first unread message

Meghana Viswanath

unread,
Feb 6, 2015, 8:07:59 AM2/6/15
to reacti...@googlegroups.com
Hi,

I am using the play-reactivemongo plugin since it is works quite neatly with Play's JSON library and I don't need to write relatively more complex BSON readers and writers for my entities.

The only issue and this is quite a big one for me, is that Joda DateTimes are stored as Long numbers in MongoDB. Since this is not human-readable, I thought about storing them as strings.

This though is human readable, would not work for me since I need to be able to perform date queries in the db.

Is there a way in which Joda DateTimes can be stored as MongoDB ISO dates using Play-reactivemongo plugin?

With BSON transformers, Joda DateTime values get saved down as MongoDB ISO dates. How can I achieve the same with the Play-reactivemongo plugin?

Thanks,
Meghana.

Aurelio Bignoli

unread,
Feb 6, 2015, 8:48:22 AM2/6/15
to reacti...@googlegroups.com
2015-02-06 14:07 GMT+01:00 Meghana Viswanath <meghana.v...@gmail.com>:
Hi,

I am using the play-reactivemongo plugin since it is works quite neatly with Play's JSON library and I don't need to write relatively more complex BSON readers and writers for my entities.

The only issue and this is quite a big one for me, is that Joda DateTimes are stored as Long numbers in MongoDB. Since this is not human-readable, I thought about storing them as strings.

This though is human readable, would not work for me since I need to be able to perform date queries in the db.

Is there a way in which Joda DateTimes can be stored as MongoDB ISO dates using Play-reactivemongo plugin?


I solved the problem this way:

package models

import java.util.UUID
import org.joda.time.DateTime
import play.api.libs.json.Json


case class Event(
  eventID: Option[UUID],
  userID: Option[UUID],
  title: String,
  description: String,
  startDate: DateTime,
  endDate: DateTime,
  allDay: Boolean
)

object Event {
  val dateTimePattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ"
  implicit val dateTimeReads = play.api.libs.json.Reads.jodaDateReads(dateTimePattern)
  implicit val dateTimeWrites = play.api.libs.json.Writes.jodaDateWrites(dateTimePattern)
  implicit val eventFormat = Json.format[Event]
}

(I think a code sample is always better than an explanation in my poor English, but feel free to ask).

Regards,
Aurelio

Meghana Viswanath

unread,
Feb 6, 2015, 9:03:56 AM2/6/15
to reacti...@googlegroups.com
Hi Aurelio,

In your example, the date values are being stored down as ISO formatted date strings.

What I really need is to store the dates as MongoDB ISODates. So, in the db, the dates look like - 
  "date" : ISODate("2015-02-05T20:09:10.485Z")

 instead of just a string like -
 "date" : "2015-02-05T20:09:10.485Z"

If the date is only stored as a string, I won't be able to perform date related queries on the field such as group by month, etc.

Aurelio Bignoli

unread,
Feb 7, 2015, 8:20:40 AM2/7/15
to reacti...@googlegroups.com
Meghana Viswanath writes:
 > If the date is only stored as a string, I won't be able to perform
 > date related queries on the field such as group by month, etc.

you are right, of course. I need only to sort my events in
chronological order, so the string representation is perhaps an
acceptable solution, but it would be much better to use ISODates.

Here is a modified version of my example which uses ISODates:

package models

import java.util.UUID
import org.joda.time.DateTime
import play.api.libs.functional.syntax._
import play.api.libs.json.{JsNumber, JsObject, JsPath, JsSuccess, JsValue, Json, Reads, Writes}
import play.modules.reactivemongo.json.BSONFormats._
import reactivemongo.bson.BSONDateTime

case class Event(
  eventID: Option[UUID],
  userID: Option[UUID],
  title: String,
  description: String,
  startDate: DateTime,
  endDate: DateTime,
  allDay: Boolean
)

object Event {

  implicit val dateTimeReads = new Reads[DateTime] {
    def reads(jv: JsValue) = {
      jv match {
        case JsObject(Seq(("$date", JsNumber(millis)))) => JsSuccess(new DateTime(millis.toLong))
        case _ => throw new Exception(s"Unknown JsValue for DateTime: $jv")
      }
    }
  }

  implicit val dateTimeWrites = new Writes[DateTime] {
    def writes(dt: DateTime): JsValue = {
      Json.toJson(BSONDateTime(dt.getMillis)) // {"$date": millis}
    }
  }

  implicit val eventFormat = Json.format[Event]

  val dateTimePattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ"
  val externalDateTimeReads = play.api.libs.json.Reads.jodaDateReads(dateTimePattern)
  val externalDateTimeWrites = play.api.libs.json.Writes.jodaDateWrites(dateTimePattern)

  val externalEventWrites: Writes[Event] = (
    (JsPath \ "eventID").writeNullable[UUID] and
    (JsPath \ "userID").writeNullable[UUID] and
    (JsPath \ "title").write[String] and
    (JsPath \ "description").write[String] and
    (JsPath \ "startDate").write[DateTime](externalDateTimeWrites) and
    (JsPath \ "endDate").write[DateTime](externalDateTimeWrites) and
    (JsPath \ "allDay").write[Boolean]
  )(unlift(Event.unapply))

  val externalEventRead: Reads[Event] = (
    (JsPath \ "eventID").readNullable[UUID] Ande
      (JsPath \ "userID").readNullable[UUID] and
      (JsPath \ "title").read[String] and
      (JsPath \ "description").read[String] and
      (JsPath \ "startDate").read[DateTime](externalDateTimeReads) and
      (JsPath \ "endDate").read[DateTime](externalDateTimeReads) and
      (JsPath \ "allDay").read[Boolean]
  )(Event.apply _)
}

The implicit Writes and Reads convert DateTime objects to/from
{"$date": millis}, which is stored as ISODate() in
Mongo. externalDateTimeWrites and externalDateTimeReads can be
esplicitly used when a more human-readable Json representation of an
Event is needed (e.g. to exchange Json data with clients).

Meghana Viswanath

unread,
Feb 7, 2015, 8:47:42 AM2/7/15
to reacti...@googlegroups.com
Thanks for that Aurelio.

I did manage to get the dates stored down as ISO dates last night by overriding the default joda datetime reads and writes just as you have shown. 

But since I was trying to avoid deserialising of the json to objects and serialising it again to json before sending it to the client, I only ever bring back JsValues from the db. This means I there is no opportunity for an external writer to transform the objects into the required json format. So, I've decided to use some simple json manipulation to replace {"$date" : 149893844} with the ISO formatted string.

Thanks again for all your help.

Rob Nichols

unread,
Feb 13, 2016, 3:21:24 AM2/13/16
to ReactiveMongo - http://reactivemongo.org
On Saturday, February 7, 2015 at 7:20:40 AM UTC-6, Aurelio Bignoli wrote:
    def reads(jv: JsValue) = {
      jv match {
        case JsObject(Seq(("$date", JsNumber(millis)))) => JsSuccess(new DateTime(millis.toLong))
        case _ => throw new Exception(s"Unknown JsValue for DateTime: $jv")
      }
    }

I apologize for reviving an old thread.  How does this pattern matching work?  I can not get the Seq part to work.

Thanks!

/rob

Cédric Chantepie

unread,
Feb 13, 2016, 8:49:49 PM2/13/16
to ReactiveMongo - http://reactivemongo.org
hi, this JSON concern is not specific to ReactiveMongo. I suggest you have a look at the Play JSON doc (which have been updated since), or ask on the Play Mailing list ( https://groups.google.com/forum/m/#!forum/play-framework ). Best regards.
Reply all
Reply to author
Forward
0 new messages