Replace joda-time with Java 8 API

joda-time is used to format dates to timestamp strings, but since change
I897188135 in core Gerrit it will no longer be exported in the plugin API
after all usage of joda-time was replaced with the Java 8 API.

Factor the timestamp formatting out to a new utility class, replacing the
usage of joda-time with Java 8's DateTimeFormatter.

Add a test which confirms that the new implementation produces the same
values as joda-time.

This change was originally done on the master branch. It is not strictly
necessary to backport to the stable-2.14 branch, since joda-time is still
included in the 2.14 API, but it will make it easier to implement changes
related to timestamps without causing reword when they're merged up to
master.

Also, note that when this change was originally reviewed on master, a
mistake related to handling of time zones was overlooked. That is fixed
in this version, so it's not a true cherry-pick.

Change-Id: I49bffffbd171065c6b3c0803a5a2bb377bd27ef1
diff --git a/BUILD b/BUILD
index 78ff732..bb1d8fe 100644
--- a/BUILD
+++ b/BUILD
@@ -33,3 +33,13 @@
         "@jgit_lfs//jar",
     ],
 )
+
+java_library(
+    name = "lfs__plugin_test_deps",
+    testonly = 1,
+    visibility = ["//visibility:public"],
+    exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
+        ":lfs__plugin",
+        "@joda_time//jar",
+    ],
+)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsAuthToken.java b/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsAuthToken.java
index 399dc21..278447c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsAuthToken.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsAuthToken.java
@@ -18,10 +18,6 @@
 import com.google.common.base.Splitter;
 import java.util.List;
 import java.util.Optional;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.joda.time.format.DateTimeFormatter;
-import org.joda.time.format.ISODateTimeFormat;
 
 public abstract class LfsAuthToken {
   public abstract static class Processor<T extends LfsAuthToken> {
@@ -65,12 +61,10 @@
     protected abstract boolean verifyTokenValues();
 
     static boolean onTime(String dateTime) {
-      String now = LfsAuthToken.ISO.print(now());
-      return now.compareTo(dateTime) <= 0;
+      return LfsDateTime.now().compareTo(dateTime) <= 0;
     }
   }
 
-  static final DateTimeFormatter ISO = ISODateTimeFormat.dateTime();
   public final String expiresAt;
 
   protected LfsAuthToken(int expirationSeconds) {
@@ -82,10 +76,6 @@
   }
 
   static String timeout(int expirationSeconds) {
-    return LfsAuthToken.ISO.print(now().plusSeconds(expirationSeconds));
-  }
-
-  static DateTime now() {
-    return DateTime.now().toDateTime(DateTimeZone.UTC);
+    return LfsDateTime.now(expirationSeconds);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsDateTime.java b/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsDateTime.java
new file mode 100644
index 0000000..ccedee5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/lfs/LfsDateTime.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.lfs;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+public class LfsDateTime {
+  private static final DateTimeFormatter FORMAT =
+      DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ")
+          .withZone(ZoneOffset.UTC)
+          .withLocale(Locale.getDefault());
+
+  public static String now() {
+    return FORMAT.format(Instant.now());
+  }
+
+  public static String now(int secondsToAdd) {
+    return FORMAT.format(Instant.now().plusSeconds(secondsToAdd));
+  }
+
+  public static String format(Instant instant) {
+    return FORMAT.format(instant);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/lfs/locks/LfsProjectLocks.java b/src/main/java/com/googlesource/gerrit/plugins/lfs/locks/LfsProjectLocks.java
index c9e0d28..a578736 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/lfs/locks/LfsProjectLocks.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/lfs/locks/LfsProjectLocks.java
@@ -23,6 +23,7 @@
 import com.google.gson.GsonBuilder;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
+import com.googlesource.gerrit.plugins.lfs.LfsDateTime;
 import com.googlesource.gerrit.plugins.lfs.locks.LfsLocksHandler.LfsLockExistsException;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
@@ -35,10 +36,6 @@
 import java.util.stream.Stream;
 import org.eclipse.jgit.internal.storage.file.LockFile;
 import org.eclipse.jgit.lfs.errors.LfsException;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-import org.joda.time.format.DateTimeFormatter;
-import org.joda.time.format.ISODateTimeFormat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -48,7 +45,6 @@
   }
 
   private static final Logger log = LoggerFactory.getLogger(LfsProjectLocks.class);
-  private static final DateTimeFormatter ISO = ISODateTimeFormat.dateTime();
   private static final Gson gson =
       new GsonBuilder()
           .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
@@ -107,7 +103,7 @@
       throw new LfsLockExistsException(lock);
     }
 
-    lock = new LfsLock(lockId, input.path, now(), new LfsLockOwner(user.getUserName()));
+    lock = new LfsLock(lockId, input.path, LfsDateTime.now(), new LfsLockOwner(user.getUserName()));
     LockFile fileLock = new LockFile(locksPath.resolve(lockId).toFile());
     try {
       if (!fileLock.lock()) {
@@ -189,8 +185,4 @@
   Collection<LfsLock> getLocks() {
     return locks.asMap().values();
   }
-
-  private String now() {
-    return ISO.print(DateTime.now().toDateTime(DateTimeZone.UTC));
-  }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/lfs/LfsAuthTokenTest.java b/src/test/java/com/googlesource/gerrit/plugins/lfs/LfsAuthTokenTest.java
index 185af35..01a4c72 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/lfs/LfsAuthTokenTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/lfs/LfsAuthTokenTest.java
@@ -15,14 +15,12 @@
 package com.googlesource.gerrit.plugins.lfs;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.googlesource.gerrit.plugins.lfs.LfsAuthToken.ISO;
 import static com.googlesource.gerrit.plugins.lfs.LfsAuthToken.Verifier.onTime;
 
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
 import org.junit.Test;
 
 public class LfsAuthTokenTest {
@@ -30,16 +28,14 @@
 
   @Test
   public void testExpiredTime() throws Exception {
-    DateTime now = now();
     // test that even 1ms expiration is enough
-    assertThat(onTime(ISO.print(now.minusMillis(1)))).isFalse();
+    assertThat(onTime(LfsDateTime.format(now().minusMillis(1)))).isFalse();
   }
 
   @Test
   public void testOnTime() throws Exception {
-    DateTime now = now();
     // if there is at least 1ms before there is no timeout
-    assertThat(onTime(ISO.print(now.plusMillis(1)))).isTrue();
+    assertThat(onTime(LfsDateTime.format(now().plusMillis(1)))).isTrue();
   }
 
   @Test
@@ -69,8 +65,8 @@
     assertThat(verifier.verify()).isFalse();
   }
 
-  private DateTime now() {
-    return DateTime.now().toDateTime(DateTimeZone.UTC);
+  private Instant now() {
+    return Instant.now();
   }
 
   private class TestToken extends LfsAuthToken {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/lfs/LfsDateTimeTest.java b/src/test/java/com/googlesource/gerrit/plugins/lfs/LfsDateTimeTest.java
new file mode 100644
index 0000000..e2852c5
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/lfs/LfsDateTimeTest.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.lfs;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.time.Instant;
+import org.joda.time.DateTime;
+import org.joda.time.format.ISODateTimeFormat;
+import org.junit.Test;
+
+public class LfsDateTimeTest {
+  @Test
+  public void format() throws Exception {
+    DateTime now = DateTime.now();
+    String jodaFormat = ISODateTimeFormat.dateTime().withZoneUTC().print(now);
+    String javaFormat = LfsDateTime.format(Instant.ofEpochMilli(now.getMillis()));
+    assertThat(javaFormat).isEqualTo(jodaFormat);
+  }
+}