Skip to content

Commit

Permalink
Removes joda-time and joda-convert (#6340)
Browse files Browse the repository at this point in the history
* removed joda-time and joda-convert

* migration notices
  • Loading branch information
schmitch authored and gmethvin committed Jul 22, 2016
1 parent 95920b7 commit 6dd1fb1
Show file tree
Hide file tree
Showing 28 changed files with 177 additions and 356 deletions.
@@ -0,0 +1,26 @@
<!--- Copyright (C) 2009-2016 Lightbend Inc. <https://www.lightbend.com> -->
# Joda Migration Guide

In order to make the default play distribution a bit smaller we removed all occurences of Joda Time from play.
This means that you should migrate to Java8 `java.time`, if you can.

## Use Joda-Time in Forms and Play-Json

If you still need to use Joda-Time in Play Forms and Play-Json you can just add the `play-joda` project:

```
libraryDependencies += "com.typesafe.play" % "play-joda" % "1.0.0"
```

And then import the corresponding Object for Forms:

```
import play.api.data.JodaForms._
```

or for Play-Json

```
import play.api.data.JodaWrites._
import play.api.data.JodaReads._
```
Expand Up @@ -80,7 +80,7 @@ You can use a `DynamicForm` if you need to retrieve data from an html form that

In case you want to define a mapping from a custom object to a form field string and vice versa you need to register a new Formatter for this object.
You can achieve this by registering a provider for `Formatters` which will do the proper initialization.
For an object like JodaTime's `LocalTime` it could look like this:
For an object like JavaTime's `LocalTime` it could look like this:

@[register-formatter](code/javaguide/forms/FormattersProvider.java)

Expand Down
Expand Up @@ -5,6 +5,7 @@

//#register-formatter
import java.text.ParseException;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -13,7 +14,7 @@
import javax.inject.Provider;
import javax.inject.Singleton;

import org.joda.time.LocalTime;
import java.time.LocalTime;

import play.data.format.Formatters;
import play.data.format.Formatters.SimpleFormatter;
Expand Down Expand Up @@ -46,12 +47,12 @@ public LocalTime parse(String input, Locale l) throws ParseException {
if (!m.find()) throw new ParseException("No valid Input", 0);
int hour = Integer.valueOf(m.group(1));
int min = m.group(2) == null ? 0 : Integer.valueOf(m.group(2));
return new LocalTime(hour, min);
return LocalTime.of(hour, min);
}

@Override
public String print(LocalTime localTime, Locale l) {
return localTime.toString("HH:mm");
return localTime.format(DateTimeFormatter.ofPattern("HH:mm"));
}

});
Expand Down
Expand Up @@ -4,7 +4,6 @@
package javaguide.forms;

import com.google.common.collect.ImmutableMap;
import org.joda.time.LocalTime;
import org.junit.Test;
import play.Application;
import play.data.DynamicForm;
Expand All @@ -21,6 +20,7 @@
import javaguide.forms.u1.User;

import java.text.ParseException;
import java.time.LocalTime;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -212,7 +212,7 @@ public void registerFormatter() {

Form<WithLocalTime> form = application.injector().instanceOf(FormFactory.class).form(WithLocalTime.class);
WithLocalTime obj = form.bind(ImmutableMap.of("time", "23:45")).get();
assertThat(obj.getTime(), equalTo(new LocalTime(23, 45)));
assertThat(obj.getTime(), equalTo(LocalTime.of(23, 45)));
assertThat(form.fill(obj).field("time").value(), equalTo("23:45"));
}

Expand Down
Expand Up @@ -76,8 +76,7 @@ The out of the box constraints are defined on the [Forms object](api/scala/play/
* [`number`](api/scala/play/api/data/Forms$.html#number%3AMapping%5BInt%5D): maps to `scala.Int`, optionally takes `min`, `max`, and `strict`.
* [`longNumber`](api/scala/play/api/data/Forms$.html#longNumber%3AMapping%5BLong%5D): maps to `scala.Long`, optionally takes `min`, `max`, and `strict`.
* [`bigDecimal`](api/scala/play/api/data/Forms$.html#bigDecimal%3AMapping%5BBigDecimal%5D): takes `precision` and `scale`.
* [`date`](api/scala/play/api/data/Forms$.html#date%3AMapping%5BDate%5D), [`sqlDate`](api/scala/play/api/data/Forms$.html#sqlDate%3AMapping%5BDate%5D), [`jodaDate`](api/scala/play/api/data/Forms$.html#jodaDate%3AMapping%5BDateTime%5D): maps to `java.util.Date`, `java.sql.Date` and `org.joda.time.DateTime`, optionally takes `pattern` and `timeZone`.
* [`jodaLocalDate`](api/scala/play/api/data/Forms$.html#jodaLocalDate%3AMapping%5BLocalDate%5D): maps to `org.joda.time.LocalDate`, optionally takes `pattern`.
* [`date`](api/scala/play/api/data/Forms$.html#date%3AMapping%5BDate%5D), [`sqlDate`](api/scala/play/api/data/Forms$.html#sqlDate%3AMapping%5BDate%5D): maps to `java.util.Date`, `java.sql.Date`, optionally takes `pattern` and `timeZone`.
* [`email`](api/scala/play/api/data/Forms$.html#email%3AMapping%5BString%5D): maps to `scala.String`, using an email regular expression.
* [`boolean`](api/scala/play/api/data/Forms$.html#boolean%3AMapping%5BBoolean%5D): maps to `scala.Boolean`.
* [`checked`](api/scala/play/api/data/Forms$.html#checked%3AMapping%5BBoolean%5D): maps to `scala.Boolean`.
Expand Down
7 changes: 0 additions & 7 deletions framework/project/Dependencies.scala
Expand Up @@ -106,9 +106,6 @@ object Dependencies {
mockitoAll
).map(_ % Test)

val jodatime = "joda-time" % "joda-time" % "2.9.3"
val jodaConvert = "org.joda" % "joda-convert" % "1.8.1"

val guiceDeps = Seq(
"com.google.inject" % "guice" % "4.0",
"com.google.inject.extensions" % "guice-assistedinject" % "4.0"
Expand All @@ -123,8 +120,6 @@ object Dependencies {
"commons-codec" % "commons-codec" % "1.10",

guava,
jodatime,
jodaConvert,

"org.apache.commons" % "commons-lang3" % "3.4",

Expand Down Expand Up @@ -250,8 +245,6 @@ object Dependencies {
) ++ specsBuild.map(_ % "test") ++ javaTestDeps

def jsonDependencies(scalaVersion: String) = Seq(
jodatime,
jodaConvert,
"org.scala-lang" % "scala-reflect" % scalaVersion,
logback % Test) ++
jacksons ++
Expand Down
Expand Up @@ -3,6 +3,7 @@
*/
package play.api.cache

import java.time.Instant
import javax.inject.Inject

import akka.stream.Materializer
Expand Down Expand Up @@ -169,7 +170,7 @@ final class CachedBuilder(
private def handleResult(result: Result, etagKey: String, resultKey: String): Result = {
cachingWithEternity.andThen { duration =>
// Format expiration date according to http standard
val expirationDate = http.dateFormat.print(System.currentTimeMillis() + duration.toMillis)
val expirationDate = http.dateFormat.format(Instant.ofEpochMilli(System.currentTimeMillis() + duration.toMillis))
// Generate a fresh ETAG for it
// Use quoted sha1 hash of expiration date as ETAG
val etag = s""""${Codecs.sha1(expirationDate)}""""
Expand Down
Expand Up @@ -3,10 +3,10 @@
*/
package play.api.cache

import java.time.Instant
import java.util.concurrent.atomic.AtomicInteger
import javax.inject._

import org.joda.time.DateTime
import play.api.cache.ehcache.EhCacheApi
import play.api.{ Application, http }
import play.api.mvc.{ Action, Results }
Expand Down Expand Up @@ -207,8 +207,8 @@ class CachedSpec extends PlaySpecification {
val res1 = header(EXPIRES, actionNotFound(FakeRequest("GET", "/b")).run())

def toDuration(header: String) = {
val now = DateTime.now().getMillis
val target = http.dateFormat.parseDateTime(header).getMillis
val now = Instant.now().toEpochMilli
val target = Instant.from(http.dateFormat.parse(header)).toEpochMilli
Duration(target - now, MILLISECONDS)
}

Expand Down
Expand Up @@ -639,97 +639,6 @@ trait DefaultReads extends LowPriorityDefaultReads {
}
}

/**
* Reads for the `org.joda.time.DateTime` type.
*
* @param pattern a date pattern, as specified in `java.text.SimpleDateFormat`.
* @param corrector a simple string transformation function that can be used to transform input String before parsing. Useful when standards are not exactly respected and require a few tweaks
*/
def jodaDateReads(pattern: String, corrector: String => String = identity): Reads[org.joda.time.DateTime] = new Reads[org.joda.time.DateTime] {
import org.joda.time.DateTime

val df = org.joda.time.format.DateTimeFormat.forPattern(pattern)

def reads(json: JsValue): JsResult[DateTime] = json match {
case JsNumber(d) => JsSuccess(new DateTime(d.toLong))
case JsString(s) => parseDate(corrector(s)) match {
case Some(d) => JsSuccess(d)
case None => JsError(Seq(JsPath() -> Seq(ValidationError("error.expected.jodadate.format", pattern))))
}
case _ => JsError(Seq(JsPath() -> Seq(ValidationError("error.expected.date"))))
}

private def parseDate(input: String): Option[DateTime] =
scala.util.control.Exception.allCatch[DateTime] opt (DateTime.parse(input, df))

}

/**
* the default implicit JodaDate reads
*/
implicit val DefaultJodaDateReads = jodaDateReads("yyyy-MM-dd")

/**
* Reads for the `org.joda.time.LocalDate` type.
*
* @param pattern a date pattern, as specified in `org.joda.time.format.DateTimeFormat`.
* @param corrector string transformation function (See jodaDateReads)
*/
def jodaLocalDateReads(pattern: String, corrector: String => String = identity): Reads[org.joda.time.LocalDate] = new Reads[org.joda.time.LocalDate] {

import org.joda.time.LocalDate
import org.joda.time.format.{ DateTimeFormat, ISODateTimeFormat }

val df = if (pattern == "") ISODateTimeFormat.localDateParser else DateTimeFormat.forPattern(pattern)

def reads(json: JsValue): JsResult[LocalDate] = json match {
case JsString(s) => parseDate(corrector(s)) match {
case Some(d) => JsSuccess(d)
case None => JsError(Seq(JsPath() -> Seq(ValidationError("error.expected.jodadate.format", pattern))))
}
case _ => JsError(Seq(JsPath() -> Seq(ValidationError("error.expected.date"))))
}

private def parseDate(input: String): Option[LocalDate] =
scala.util.control.Exception.allCatch[LocalDate] opt (LocalDate.parse(input, df))
}

/**
* the default implicit joda.time.LocalDate reads
*/
implicit val DefaultJodaLocalDateReads = jodaLocalDateReads("")

/**
* Reads for the `org.joda.time.LocalTime` type.
*
* @param pattern a date pattern, as specified in `org.joda.time.format.DateTimeFormat`.
* @param corrector string transformation function (See jodaTimeReads)
*/
def jodaLocalTimeReads(pattern: String, corrector: String => String = identity): Reads[org.joda.time.LocalTime] = new Reads[org.joda.time.LocalTime] {

import org.joda.time.LocalTime
import org.joda.time.format.{ DateTimeFormat, ISODateTimeFormat }

val df = if (pattern == "") ISODateTimeFormat.localTimeParser else DateTimeFormat.forPattern(pattern)

def reads(json: JsValue): JsResult[LocalTime] = json match {
case JsNumber(n) => JsSuccess(new LocalTime(n.toLong))
case JsString(s) => parseTime(corrector(s)) match {
case Some(d) => JsSuccess(d)
case None => JsError(Seq(JsPath() -> Seq(ValidationError("error.expected.jodatime.format", pattern))))
}
case _ => JsError(Seq(JsPath() -> Seq(ValidationError("error.expected.time"))))
}

private def parseTime(input: String): Option[LocalTime] =
scala.util.control.Exception.allCatch[LocalTime] opt (LocalTime.parse(input, df))
}

/**
* the default implicit joda.time.LocalTime reads
*/
implicit val DefaultJodaLocalTimeReads = jodaLocalTimeReads("")

/**
* Reads for the `java.sql.Date` type.
*
Expand All @@ -740,7 +649,7 @@ trait DefaultReads extends LowPriorityDefaultReads {
dateReads(pattern, corrector).map(d => new java.sql.Date(d.getTime))

/**
* the default implicit JodaDate reads
* the default implicit SqlDate reads
*/
implicit val DefaultSqlDateReads = sqlDateReads("yyyy-MM-dd")

Expand Down
Expand Up @@ -395,53 +395,6 @@ trait DefaultWrites {
JsNumber(BigDecimal valueOf t.toEpochMilli)
}

/**
* Serializer for org.joda.time.DateTime
* @param pattern the pattern used by SimpleDateFormat
*/
def jodaDateWrites(pattern: String): Writes[org.joda.time.DateTime] = new Writes[org.joda.time.DateTime] {
val df = org.joda.time.format.DateTimeFormat.forPattern(pattern)
def writes(d: org.joda.time.DateTime): JsValue = JsString(d.toString(df))
}

/**
* Default Serializer org.joda.time.DateTime -> JsNumber(d.getMillis (nb of ms))
*/
implicit object DefaultJodaDateWrites extends Writes[org.joda.time.DateTime] {
def writes(d: org.joda.time.DateTime): JsValue = JsNumber(d.getMillis)
}

/**
* Serializer for org.joda.time.LocalDate
* @param pattern the pattern used by org.joda.time.format.DateTimeFormat
*/
def jodaLocalDateWrites(pattern: String): Writes[org.joda.time.LocalDate] = new Writes[org.joda.time.LocalDate] {
val df = org.joda.time.format.DateTimeFormat.forPattern(pattern)
def writes(d: org.joda.time.LocalDate): JsValue = JsString(d.toString(df))
}

/**
* Default Serializer org.joda.time.LocalDate -> JsString(ISO8601 format (yyyy-MM-dd))
*/
implicit object DefaultJodaLocalDateWrites extends Writes[org.joda.time.LocalDate] {
def writes(d: org.joda.time.LocalDate): JsValue = JsString(d.toString)
}

/**
* Serializer for org.joda.time.LocalTime
* @param pattern the pattern used by org.joda.time.format.DateTimeFormat
*/
def jodaLocalTimeWrites(pattern: String): Writes[org.joda.time.LocalTime] = new Writes[org.joda.time.LocalTime] {
def writes(d: org.joda.time.LocalTime): JsValue = JsString(d.toString(pattern))
}

/**
* Default Serializer org.joda.time.LocalDate -> JsString(ISO8601 format (HH:mm:ss.SSS))
*/
implicit object DefaultJodaLocalTimeWrites extends Writes[org.joda.time.LocalTime] {
def writes(d: org.joda.time.LocalTime): JsValue = JsString(d.toString)
}

/**
* Serializer for java.sql.Date
* @param pattern the pattern used by SimpleDateFormat
Expand Down
Expand Up @@ -132,25 +132,10 @@ object JsonValidSpec extends Specification {
Json.toJson[java.util.Date](dd).validate[java.util.Date] must beEqualTo(JsSuccess(dd))
JsNumber(dd.getTime).validate[java.util.Date] must beEqualTo(JsSuccess(dd))

val dj = new org.joda.time.DateTime()
val dfj = org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd")
val ddj = org.joda.time.DateTime.parse(dfj.print(dj), dfj)

Json.toJson[org.joda.time.DateTime](ddj).validate[org.joda.time.DateTime] must beEqualTo(JsSuccess(ddj))
JsNumber(ddj.getMillis).validate[org.joda.time.DateTime] must beEqualTo(JsSuccess(ddj))

val ldj = org.joda.time.LocalDate.parse(dfj.print(dj), dfj)
Json.toJson[org.joda.time.LocalDate](ldj).validate[org.joda.time.LocalDate] must beEqualTo(JsSuccess(ldj))

val ds = new java.sql.Date(dd.getTime())

val dtfj = org.joda.time.format.DateTimeFormat.forPattern("HH:mm:ss.SSS")
Json.toJson[java.sql.Date](ds).validate[java.sql.Date] must beEqualTo(JsSuccess(dd))
JsNumber(dd.getTime).validate[java.sql.Date] must beEqualTo(JsSuccess(dd))

val ltj = org.joda.time.LocalTime.parse(dtfj.print(dj), dtfj)
Json.toJson[org.joda.time.LocalTime](ltj).validate[org.joda.time.LocalTime] must beEqualTo(JsSuccess(ltj))

// very poor test to do really crappy java date APIs
// TODO ISO8601 test doesn't work on CI platform...
val c = java.util.Calendar.getInstance()
Expand Down
Expand Up @@ -3,8 +3,9 @@
*/
package play.core.server.netty

import java.net.{ URI, InetSocketAddress }
import java.net.{ InetSocketAddress, URI }
import java.security.cert.X509Certificate
import java.time.Instant
import javax.net.ssl.SSLEngine
import javax.net.ssl.SSLPeerUnverifiedException

Expand All @@ -20,7 +21,7 @@ import play.api.Logger
import play.api.http._
import play.api.http.HeaderNames._
import play.api.mvc._
import play.core.server.common.{ ConnectionInfo, ServerResultUtils, ForwardedHeaderHandler }
import play.core.server.common.{ ConnectionInfo, ForwardedHeaderHandler, ServerResultUtils }

import scala.collection.JavaConverters._
import scala.concurrent.Future
Expand Down Expand Up @@ -299,7 +300,7 @@ private[server] class NettyModelConversion(forwardedHeaderHandler: ForwardedHead
case (cachedSeconds, dateHeaderString) if cachedSeconds == currentTimeSeconds =>
dateHeaderString
case _ =>
val dateHeaderString = ResponseHeader.httpDateFormat.print(currentTimeMillis)
val dateHeaderString = ResponseHeader.httpDateFormat.format(Instant.ofEpochMilli(currentTimeMillis))
cachedDateHeader = currentTimeSeconds -> dateHeaderString
dateHeaderString
}
Expand Down

0 comments on commit 6dd1fb1

Please sign in to comment.