Move the DurationConverter to the common.options package

Also change it to java.time.Duration, rather than Jodatime. Now that we're on
Java 8, we no longer need Jodatime.

PiperOrigin-RevId: 162917526
GitOrigin-RevId: f9625f0bafb2f84524d152753b6e10c460abc82a
Change-Id: Ic0359c219059826c3a9a25d5b2d3280c491d7b5f
diff --git a/java/com/google/devtools/common/options/Converters.java b/java/com/google/devtools/common/options/Converters.java
index 4584f80..aacc64b 100644
--- a/java/com/google/devtools/common/options/Converters.java
+++ b/java/com/google/devtools/common/options/Converters.java
@@ -16,10 +16,12 @@
 import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
+import java.time.Duration;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.logging.Level;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
 
@@ -178,6 +180,47 @@
   }
 
   /**
+   * Standard converter for the {@link java.time.Duration} type.
+   */
+  public static class DurationConverter implements Converter<Duration> {
+    private final Pattern durationRegex = Pattern.compile("^([0-9]+)(d|h|m|s|ms)$");
+
+    @Override
+    public Duration convert(String input) throws OptionsParsingException {
+      // To be compatible with the previous parser, '0' doesn't need a unit.
+      if ("0".equals(input)) {
+        return Duration.ZERO;
+      }
+      Matcher m = durationRegex.matcher(input);
+      if (!m.matches()) {
+        throw new OptionsParsingException("Illegal duration '" + input + "'.");
+      }
+      long duration = Long.parseLong(m.group(1));
+      String unit = m.group(2);
+      switch(unit) {
+        case "d":
+          return Duration.ofDays(duration);
+        case "h":
+          return Duration.ofHours(duration);
+        case "m":
+          return Duration.ofMinutes(duration);
+        case "s":
+          return Duration.ofSeconds(duration);
+        case "ms":
+          return Duration.ofMillis(duration);
+        default:
+          throw new IllegalStateException("This must not happen. Did you update the regex without "
+              + "the switch case?");
+      }
+    }
+
+    @Override
+    public String getTypeDescription() {
+      return "An immutable length of time.";
+    }
+  }
+
+  /**
    * The converters that are available to the options parser by default. These are used if the
    * {@code @Option} annotation does not specify its own {@code converter}, and its type is one of
    * the following.
@@ -185,12 +228,14 @@
   static final Map<Class<?>, Converter<?>> DEFAULT_CONVERTERS = Maps.newHashMap();
 
   static {
+    // 1:1 correspondence with UsesOnlyCoreTypes.CORE_TYPES.
     DEFAULT_CONVERTERS.put(String.class, new Converters.StringConverter());
     DEFAULT_CONVERTERS.put(int.class, new Converters.IntegerConverter());
     DEFAULT_CONVERTERS.put(long.class, new Converters.LongConverter());
     DEFAULT_CONVERTERS.put(double.class, new Converters.DoubleConverter());
     DEFAULT_CONVERTERS.put(boolean.class, new Converters.BooleanConverter());
     DEFAULT_CONVERTERS.put(TriState.class, new Converters.TriStateConverter());
+    DEFAULT_CONVERTERS.put(Duration.class, new Converters.DurationConverter());
     DEFAULT_CONVERTERS.put(Void.class, new Converters.VoidConverter());
   }
 
diff --git a/java/com/google/devtools/common/options/UsesOnlyCoreTypes.java b/java/com/google/devtools/common/options/UsesOnlyCoreTypes.java
index 6f2714f..c40495d 100644
--- a/java/com/google/devtools/common/options/UsesOnlyCoreTypes.java
+++ b/java/com/google/devtools/common/options/UsesOnlyCoreTypes.java
@@ -20,6 +20,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.time.Duration;
 import java.util.List;
 
 /**
@@ -52,6 +53,7 @@
       double.class,
       boolean.class,
       TriState.class,
-      Void.class
+      Void.class,
+      Duration.class
   );
 }