Repository: gchq/sleeper
Issue: Compaction Job status reports use local timezone in range query

When using the Compaction Job status reporter, entered ranges use the local timezone, whereas dates and times are stored in UTC in the database, leading to incorrect query results. Converting range input to UTC will prevent this.

Changed files:
java/clients/src/main/java/sleeper/status/report/CompactionJobStatusReport.java
java/clients/src/test/java/sleeper/status/report/compactionjob/StatusReporterRangeQueryTest.java

diff --git a/java/clients/src/main/java/sleeper/status/report/CompactionJobStatusReport.java b/java/clients/src/main/java/sleeper/status/report/CompactionJobStatusReport.java
index d6575f447..98df75c7b 100644
--- a/java/clients/src/main/java/sleeper/status/report/CompactionJobStatusReport.java
+++ b/java/clients/src/main/java/sleeper/status/report/CompactionJobStatusReport.java
@@ -40,15 +40,30 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Scanner;
+import java.util.TimeZone;
 
 public class CompactionJobStatusReport {
     private final CompactionJobStatusReportArguments arguments;
     private final CompactionJobStatusReporter compactionJobStatusReporter;
     private final CompactionJobStatusCollector compactionJobStatusCollector;
-    private final SimpleDateFormat dateInputFormat = new SimpleDateFormat("yyyyMMddhhmmss");
+    private static final String DATE_FORMAT = "yyyyMMddhhmmss";
     private static final Instant DEFAULT_RANGE_START = Instant.now().minus(4L, ChronoUnit.HOURS);
     private static final Instant DEFAULT_RANGE_END = Instant.now();
 
+    private static SimpleDateFormat createDateInputFormat() {
+        SimpleDateFormat dateInputFormat = new SimpleDateFormat(DATE_FORMAT);
+        dateInputFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+        return dateInputFormat;
+    }
+
+    public static Instant parseDate(String input) throws ParseException {
+        return createDateInputFormat().parse(input).toInstant();
+    }
+
+    public static String formatDate(Instant input) {
+        return createDateInputFormat().format(Date.from(input));
+    }
+
     public CompactionJobStatusReport(
             CompactionJobStatusStore compactionJobStatusStore,
             CompactionJobStatusReportArguments arguments) {
@@ -73,10 +88,8 @@ public class CompactionJobStatusReport {
                 Instant startRange;
                 Instant endRange;
                 try {
-                    Date startRangeDate = dateInputFormat.parse(arguments.getQueryParameters().split(",")[0]);
-                    startRange = startRangeDate.toInstant();
-                    Date endRangeDate = dateInputFormat.parse(arguments.getQueryParameters().split(",")[1]);
-                    endRange = endRangeDate.toInstant();
+                    startRange = parseDate(arguments.getQueryParameters().split(",")[0]);
+                    endRange = parseDate(arguments.getQueryParameters().split(",")[1]);
                 } catch (ParseException e) {
                     System.out.println("Error while parsing input string, using system defaults (past 4 hours)");
                     startRange = DEFAULT_RANGE_START;
@@ -145,16 +158,15 @@ public class CompactionJobStatusReport {
         while (true) {
             System.out.printf("Enter %s range in format %s (default is %s):",
                     rangeName,
-                    dateInputFormat.toPattern(),
-                    dateInputFormat.format(Date.from(defaultRange)));
+                    DATE_FORMAT,
+                    formatDate(defaultRange));
             String time = scanner.nextLine();
             if ("".equals(time)) {
-                System.out.printf("Using default %s range %s%n", rangeName, dateInputFormat.format(Date.from(defaultRange)));
+                System.out.printf("Using default %s range %s%n", rangeName, formatDate(defaultRange));
                 break;
             }
             try {
-                Date date = dateInputFormat.parse(time);
-                return date.toInstant();
+                return parseDate(time);
             } catch (ParseException e) {
                 System.out.println("Error while parsing input string");
             }
diff --git a/java/clients/src/test/java/sleeper/status/report/compactionjob/StatusReporterRangeQueryTest.java b/java/clients/src/test/java/sleeper/status/report/compactionjob/StatusReporterRangeQueryTest.java
index 5a36638de..aa48aaa68 100644
--- a/java/clients/src/test/java/sleeper/status/report/compactionjob/StatusReporterRangeQueryTest.java
+++ b/java/clients/src/test/java/sleeper/status/report/compactionjob/StatusReporterRangeQueryTest.java
@@ -18,9 +18,13 @@ package sleeper.status.report.compactionjob;
 
 import org.junit.Test;
 import sleeper.compaction.job.status.CompactionJobStatus;
+import sleeper.status.report.CompactionJobStatusReport;
 
+import java.text.SimpleDateFormat;
+import java.time.Instant;
 import java.util.Collections;
 import java.util.List;
+import java.util.TimeZone;
 
 import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -52,4 +56,23 @@ public class StatusReporterRangeQueryTest extends StatusReporterTestBase {
                 .isEqualTo(example("reports/compactionjobstatus/json/noJobs.json"));
     }
 
+    @Test
+    public void shouldConvertInputRangeToUTC() throws Exception {
+        // Given
+        Instant dateUTC = Instant.parse("2022-09-20T10:00:00.000Z");
+
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddhhmmss");
+        dateFormat.setTimeZone(TimeZone.getTimeZone("PST"));
+        String inputPST = "20220920100000";
+        Instant datePST = dateFormat.parse(inputPST).toInstant();
+
+        // When
+        Instant parsedDatePST = CompactionJobStatusReport.parseDate(inputPST);
+
+        // Then
+        assertThat(parsedDatePST)
+                .isEqualTo(dateUTC);
+        assertThat(parsedDatePST)
+                .isNotEqualTo(datePST);
+    }
 }