[tor-commits] [exonerator/master] Introduce a date class encapsulating all calculations related to dates.

karsten at torproject.org karsten at torproject.org
Fri Dec 15 16:19:50 UTC 2017


commit b040d1a28de7e65b7f16c3d529aa205d750d6d3c
Author: iwakeh <iwakeh at torproject.org>
Date:   Mon Dec 11 13:58:03 2017 +0000

    Introduce a date class encapsulating all calculations related to dates.
    
    Also use LocalDate for date parsing; turn tests into parametrized tests
    and add tests for checking validity of parameters.
---
 .../metrics/exonerator/ExoneraTorDate.java         |  47 +++++++++
 .../metrics/exonerator/ExoneraTorServlet.java      | 110 +++++++--------------
 .../metrics/exonerator/ExoneraTorDateTest.java     |  80 +++++++++++++++
 .../metrics/exonerator/ExoneraTorServletTest.java  |  29 ------
 4 files changed, 163 insertions(+), 103 deletions(-)

diff --git a/src/main/java/org/torproject/metrics/exonerator/ExoneraTorDate.java b/src/main/java/org/torproject/metrics/exonerator/ExoneraTorDate.java
new file mode 100644
index 0000000..723ffdd
--- /dev/null
+++ b/src/main/java/org/torproject/metrics/exonerator/ExoneraTorDate.java
@@ -0,0 +1,47 @@
+/* Copyright 2017 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.exonerator;
+
+import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
+
+import java.time.LocalDate;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeParseException;
+
+class ExoneraTorDate {
+
+  public static final ExoneraTorDate INVALID = new ExoneraTorDate("");
+  private static final int DATELENGTH = "yyyy-mm-dd".length();
+
+  final boolean empty;
+  final boolean valid;
+  final String asString;
+  final LocalDate date;
+  final String asRequested;
+  final boolean tooRecent;
+
+  ExoneraTorDate(String parameter) {
+    this.asRequested = parameter;
+    this.empty = null == parameter || parameter.trim().isEmpty();
+    this.date = empty ? null : parseDatestamp(parameter);
+    this.valid = null != date;
+    this.asString = valid ? date.format(ISO_LOCAL_DATE) : "";
+    this.tooRecent = valid
+        && date.isAfter(LocalDate.now(ZoneOffset.UTC).minusDays(2));
+  }
+
+  private static LocalDate parseDatestamp(String datestamp) {
+    String trimmedDatestamp = datestamp.replaceAll("\\s", "");
+    if (trimmedDatestamp.length() >= DATELENGTH) {
+      try {
+        return LocalDate
+            .parse(trimmedDatestamp.substring(0, DATELENGTH), ISO_LOCAL_DATE);
+      } catch (DateTimeParseException e) {
+        return null;
+      }
+    }
+    return null;
+  }
+
+}
diff --git a/src/main/java/org/torproject/metrics/exonerator/ExoneraTorServlet.java b/src/main/java/org/torproject/metrics/exonerator/ExoneraTorServlet.java
index 1ee857b..8464f50 100644
--- a/src/main/java/org/torproject/metrics/exonerator/ExoneraTorServlet.java
+++ b/src/main/java/org/torproject/metrics/exonerator/ExoneraTorServlet.java
@@ -3,8 +3,6 @@
 
 package org.torproject.metrics.exonerator;
 
-import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
-
 import org.apache.commons.lang3.StringEscapeUtils;
 
 import org.slf4j.Logger;
@@ -15,10 +13,6 @@ import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.net.URL;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.time.LocalDate;
-import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -26,7 +20,6 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.ResourceBundle;
 import java.util.SortedMap;
-import java.util.TimeZone;
 import java.util.TreeMap;
 import java.util.regex.Pattern;
 
@@ -77,11 +70,8 @@ public class ExoneraTorServlet extends HttpServlet {
       final boolean relayIpHasError = relayIp == null;
 
       /* Parse timestamp parameter. */
-      String timestampParameter = request.getParameter("timestamp");
-      String timestampStr = parseTimestampParameter(timestampParameter);
-      final boolean timestampHasError = timestampStr == null;
-      final boolean timestampTooRecent = !timestampHasError
-          && checkTimestampTooRecent(timestampStr);
+      ExoneraTorDate requestedDate
+          = new ExoneraTorDate(request.getParameter("timestamp"));
 
       /* Parse lang parameter. */
       String langParameter = request.getParameter("lang");
@@ -94,20 +84,21 @@ public class ExoneraTorServlet extends HttpServlet {
       /* Step 2: Query the backend server. */
 
       boolean successfullyConnectedToBackend = false;
-      String firstDate = null;
-      String lastDate = null;
+      ExoneraTorDate firstDate = ExoneraTorDate.INVALID;
+      ExoneraTorDate lastDate = ExoneraTorDate.INVALID;
       boolean noRelevantConsensuses = true;
       List<String[]> statusEntries = new ArrayList<>();
       List<String> addressesInSameNetwork = null;
 
       /* Only query, if we received valid user input. */
-      if (null != relayIp && !relayIp.isEmpty() && null != timestampStr
-          && !timestampStr.isEmpty() && !timestampTooRecent) {
-        QueryResponse queryResponse = this.queryBackend(relayIp, timestampStr);
+      if (null != relayIp && !relayIp.isEmpty()
+          && requestedDate.valid && !requestedDate.tooRecent) {
+        QueryResponse queryResponse
+            = this.queryBackend(relayIp, requestedDate.asString);
         if (null != queryResponse) {
           successfullyConnectedToBackend = true;
-          firstDate = queryResponse.firstDateInDatabase;
-          lastDate = queryResponse.lastDateInDatabase;
+          firstDate = new ExoneraTorDate(queryResponse.firstDateInDatabase);
+          lastDate = new ExoneraTorDate(queryResponse.lastDateInDatabase);
           if (null != queryResponse.relevantStatuses
               && queryResponse.relevantStatuses) {
             noRelevantConsensuses = false;
@@ -148,19 +139,19 @@ public class ExoneraTorServlet extends HttpServlet {
       this.writeHeader(out, rb, langStr);
 
       /* Write form. */
-      boolean timestampOutOfRange = null != timestampStr
-          && (null != firstDate && timestampStr.compareTo(firstDate) < 0
-          || (null != lastDate && timestampStr.compareTo(lastDate) > 0));
+      boolean timestampOutOfRange = requestedDate.valid
+          && (firstDate.valid && requestedDate.date.isBefore(firstDate.date)
+          || (lastDate.valid && requestedDate.date.isAfter(lastDate.date)));
       this.writeForm(out, rb, relayIp, relayIpHasError
-          || ("".equals(relayIp) && !"".equals(timestampStr)), timestampStr,
-          !relayIpHasError
-          && !("".equals(relayIp) && !"".equals(timestampStr))
-          && (timestampHasError || timestampOutOfRange
-          || (!"".equals(relayIp) && "".equals(timestampStr))), langStr);
+          || ("".equals(relayIp) && !requestedDate.empty),
+          requestedDate.asString, !relayIpHasError
+          && !("".equals(relayIp) && !requestedDate.valid)
+          && (!requestedDate.valid || timestampOutOfRange
+          || (!"".equals(relayIp) && requestedDate.empty)), langStr);
 
       /* If both parameters are empty, don't print any summary and exit.
        * This is the start page. */
-      if ("".equals(relayIp) && "".equals(timestampStr)) {
+      if ("".equals(relayIp) && requestedDate.empty) {
         this.writeFooter(out, rb, null, null);
 
         /* If only one parameter is empty and the other is not, print summary
@@ -168,7 +159,7 @@ public class ExoneraTorServlet extends HttpServlet {
       } else if ("".equals(relayIp)) {
         this.writeSummaryNoIp(out, rb);
         this.writeFooter(out, rb, null, null);
-      } else if ("".equals(timestampStr)) {
+      } else if (requestedDate.empty) {
         this.writeSummaryNoTimestamp(out, rb);
         this.writeFooter(out, rb, null, null);
 
@@ -177,14 +168,14 @@ public class ExoneraTorServlet extends HttpServlet {
       } else if (relayIpHasError) {
         this.writeSummaryInvalidIp(out, rb, ipParameter);
         this.writeFooter(out, rb, null, null);
-      } else if (timestampHasError) {
-        this.writeSummaryInvalidTimestamp(out, rb, timestampParameter);
+      } else if (!requestedDate.valid) {
+        this.writeSummaryInvalidTimestamp(out, rb, requestedDate.asRequested);
         this.writeFooter(out, rb, null, null);
 
         /* If the timestamp is too recent, print summary with error message and
          * exit. */
-      } else if (timestampTooRecent) {
-        this.writeSummaryTimestampTooRecent(out, rb, timestampStr);
+      } else if (requestedDate.tooRecent) {
+        this.writeSummaryTimestampTooRecent(out, rb, requestedDate.asString);
         this.writeFooter(out, rb, null, null);
 
         /* If we were unable to connect to the database,
@@ -195,35 +186,36 @@ public class ExoneraTorServlet extends HttpServlet {
 
         /* Similarly, if we found the database to be empty,
          * write an error message, too. */
-      } else if (null == firstDate || null == lastDate) {
+      } else if (firstDate.empty || lastDate.empty) {
         this.writeSummaryNoData(out, rb);
         this.writeFooter(out, rb, null, null);
 
         /* If the requested date is out of range, tell the user. */
       } else if (timestampOutOfRange) {
-        this.writeSummaryTimestampOutsideRange(out, rb, timestampStr,
-            firstDate, lastDate);
-        this.writeFooter(out, rb, relayIp, timestampStr);
+        this.writeSummaryTimestampOutsideRange(out, rb, requestedDate.asString,
+            firstDate.asString, lastDate.asString);
+        this.writeFooter(out, rb, relayIp, requestedDate.asString);
 
       } else if (noRelevantConsensuses) {
         this.writeSummaryNoDataForThisInterval(out, rb);
-        this.writeFooter(out, rb, relayIp, timestampStr);
+        this.writeFooter(out, rb, relayIp, requestedDate.asString);
 
         /* Print out result. */
       } else {
         if (!statusEntries.isEmpty()) {
-          this.writeSummaryPositive(out, rb, relayIp, timestampStr);
-          this.writeTechnicalDetails(out, rb, relayIp, timestampStr,
+          this.writeSummaryPositive(out, rb, relayIp, requestedDate.asString);
+          this.writeTechnicalDetails(out, rb, relayIp, requestedDate.asString,
               statusEntries);
         } else if (addressesInSameNetwork != null
             && !addressesInSameNetwork.isEmpty()) {
           this.writeSummaryAddressesInSameNetwork(out, rb, relayIp,
-              timestampStr, langStr, addressesInSameNetwork);
+              requestedDate.asString, langStr, addressesInSameNetwork);
         } else {
-          this.writeSummaryNegative(out, rb, relayIp, timestampStr);
+          this.writeSummaryNegative(out, rb, relayIp, requestedDate.asString);
         }
-        this.writePermanentLink(out, rb, relayIp, timestampStr, langStr);
-        this.writeFooter(out, rb, relayIp, timestampStr);
+        this.writePermanentLink(out, rb, relayIp, requestedDate.asString,
+            langStr);
+        this.writeFooter(out, rb, relayIp, requestedDate.asString);
       }
 
       /* Forward to the JSP that adds header and footer. */
@@ -298,35 +290,6 @@ public class ExoneraTorServlet extends HttpServlet {
     return relayIp;
   }
 
-  /** Parse a timestamp parameter and return either a non-<code>null</code>
-   * value in case the parameter was valid or empty, or <code>null</code> if it
-   * was non-empty and invalid. */
-  static String parseTimestampParameter(String passedTimestampParameter) {
-    String timestampStr = "";
-    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
-    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-    dateFormat.setLenient(false);
-    if (passedTimestampParameter != null
-        && passedTimestampParameter.length() > 0) {
-      String timestampParameter = passedTimestampParameter.trim();
-      try {
-        long timestamp = dateFormat.parse(timestampParameter).getTime();
-        timestampStr = dateFormat.format(timestamp);
-      } catch (ParseException e) {
-        timestampStr = null;
-      }
-    }
-    return timestampStr;
-  }
-
-  /** Return whether the timestamp parameter is too recent, which is the case if
-   * it matches the day before the current system date (in UTC) or is even
-   * younger. */
-  static boolean checkTimestampTooRecent(String timestampParameter) {
-    return LocalDate.parse(timestampParameter, ISO_LOCAL_DATE)
-        .isAfter(LocalDate.now(ZoneOffset.UTC).minusDays(2));
-  }
-
   /* Helper method for fetching a query response via URL. */
 
   private QueryResponse queryBackend(String relayIp, String timestampStr) {
@@ -698,4 +661,3 @@ public class ExoneraTorServlet extends HttpServlet {
     out.close();
   }
 }
-
diff --git a/src/test/java/org/torproject/metrics/exonerator/ExoneraTorDateTest.java b/src/test/java/org/torproject/metrics/exonerator/ExoneraTorDateTest.java
new file mode 100644
index 0000000..79690e7
--- /dev/null
+++ b/src/test/java/org/torproject/metrics/exonerator/ExoneraTorDateTest.java
@@ -0,0 +1,80 @@
+/* Copyright 2017 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.exonerator;
+
+import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.Collection;
+
+ at RunWith(Parameterized.class)
+public class ExoneraTorDateTest {
+
+  /** All test data. */
+  @Parameters
+  public static Collection<Object[]> data() {
+    return Arrays.asList(new Object[][] {   // input, output
+        {"2000-   10-10", LocalDate.parse("2000-10-10", ISO_LOCAL_DATE),
+         false, true},
+        {"2010-12-16 +0001", LocalDate.parse("2010-12-16", ISO_LOCAL_DATE),
+         false, true},
+        {"2010-12-16 CEST", LocalDate.parse("2010-12-16", ISO_LOCAL_DATE),
+         false, true},
+        {"2010-12-16abcd", LocalDate.parse("2010-12-16", ISO_LOCAL_DATE),
+         false, true},
+        {"2010-12-16", LocalDate.parse("2010-12-16", ISO_LOCAL_DATE),
+         false, true},
+        {"2000-10-10 12:10:00", LocalDate.parse("2000-10-10", ISO_LOCAL_DATE),
+         false, true},
+        {"2000-10-10 1210-04-05",
+         LocalDate.parse("2000-10-10", ISO_LOCAL_DATE), false, true},
+        {"20.10.16", null, false, false},
+        {null, null, true, false},
+        {"", null, true, false},
+        {"2010-12 16", null, false, false},
+        {"2010-\t12-\t16", LocalDate.parse("2010-12-16", ISO_LOCAL_DATE),
+         false, true},
+        {"2010- 12- \t16", LocalDate.parse("2010-12-16", ISO_LOCAL_DATE),
+         false, true},
+        {"2003-12-\t16", LocalDate.parse("2003-12-16", ISO_LOCAL_DATE),
+         false, true},
+        {"2004-10-14\t", LocalDate.parse("2004-10-14", ISO_LOCAL_DATE),
+         false, true},
+        {"\n2005-10-12\t\t", LocalDate.parse("2005-10-12", ISO_LOCAL_DATE),
+         false, true},
+        {"    2001-10-13   ", LocalDate.parse("2001-10-13", ISO_LOCAL_DATE),
+         false, true}
+    });
+  }
+
+  @Parameter(0)
+  public String dateParameter;
+
+  @Parameter(1)
+  public LocalDate expectedDate;
+
+  @Parameter(2)
+  public boolean empty;
+
+  @Parameter(3)
+  public boolean valid;
+
+  @Test
+  public void testTimestampParsing() {
+    ExoneraTorDate date = new ExoneraTorDate(dateParameter);
+    assertEquals("Input data: " + dateParameter, expectedDate, date.date);
+    assertEquals("Input data: " + dateParameter, empty, date.empty);
+    assertEquals("Input data: " + dateParameter, valid, date.valid);
+  }
+
+}
+
diff --git a/src/test/java/org/torproject/metrics/exonerator/ExoneraTorServletTest.java b/src/test/java/org/torproject/metrics/exonerator/ExoneraTorServletTest.java
index 16592c3..fc7e8e3 100644
--- a/src/test/java/org/torproject/metrics/exonerator/ExoneraTorServletTest.java
+++ b/src/test/java/org/torproject/metrics/exonerator/ExoneraTorServletTest.java
@@ -32,34 +32,5 @@ public class ExoneraTorServletTest {
       assertEquals(data[1], ExoneraTorServlet.parseIpParameter(data[0]));
     }
   }
-
-  private static final String[][] timestampTestData
-      = {   // input, output
-        {"2000-   10-10", "2000-10-10"},
-        {"2010-12-16 +0001", "2010-12-16"},
-        {"2010-12-16 CEST", "2010-12-16"},
-        {"2010-12-16abcd", "2010-12-16"},
-        {"2010-12-16", "2010-12-16"},
-        {"2000-10-10 12:10:00", "2000-10-10"},
-        {"2000-10-10 1210-04-05", "2000-10-10"},
-        {"20.10.16", null},
-        {null, ""},
-        {"", ""},
-        {"2010-12 16", null},
-        {"2010-\t12-\t16", "2010-12-16"},
-        {"2010- 12- \t16", "2010-12-16"},
-        {"2003-12-\t16", "2003-12-16"},
-        {"2004-10-10\t", "2004-10-10"},
-        {"\n2005-10-10\t\t", "2005-10-10"},
-        {"    2001-10-10   ", "2001-10-10"}
-      };
-
-  @Test
-  public void testTimestampParsing() {
-    for (String[] data : timestampTestData) {
-      assertEquals(data[1], ExoneraTorServlet.parseTimestampParameter(data[0]));
-    }
-  }
-
 }
 





More information about the tor-commits mailing list