This is an automated email from the ASF dual-hosted git repository.

anmolnar pushed a commit to branch HBASE-28957
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit fa33467310647896aa8675c0a164bd26fbc9d7a0
Author: Kevin Geiszler <[email protected]>
AuthorDate: Wed Feb 4 09:40:39 2026 -0800

    HBASE-29825: Incremental backup is failing due to incorrect timezone (#7683)
    
    Change-Id: I8702eca4adc81bad2c18ea4990d09556c9506a34
---
 .../hadoop/hbase/backup/util/BackupUtils.java      |   2 +
 .../hadoop/hbase/backup/TestBackupUtils.java       | 104 +++++++++++++++++++++
 2 files changed, 106 insertions(+)

diff --git 
a/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/util/BackupUtils.java
 
b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/util/BackupUtils.java
index 5db876dc538..b0665c23fa1 100644
--- 
a/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/util/BackupUtils.java
+++ 
b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/util/BackupUtils.java
@@ -29,6 +29,7 @@ import java.io.IOException;
 import java.net.URLDecoder;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -978,6 +979,7 @@ public final class BackupUtils {
 
     List<String> validDirs = new ArrayList<>();
     SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
+    dateFormat.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC));
 
     for (FileStatus dayDir : dayDirs) {
       if (!dayDir.isDirectory()) {
diff --git 
a/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestBackupUtils.java
 
b/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestBackupUtils.java
index cf69476270c..08f15b37f68 100644
--- 
a/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestBackupUtils.java
+++ 
b/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestBackupUtils.java
@@ -23,6 +23,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.IOException;
 import java.security.PrivilegedAction;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TimeZone;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
@@ -49,6 +54,15 @@ public class TestBackupUtils {
   protected static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
   protected static Configuration conf = TEST_UTIL.getConfiguration();
 
+  private static FileSystem dummyFs;
+  private static Path backupRootDir;
+
+  @BeforeClass
+  public static void setUp() throws IOException {
+    dummyFs = TEST_UTIL.getTestFileSystem();
+    backupRootDir = TEST_UTIL.getDataTestDirOnTestFS("backupUT");
+  }
+
   @Test
   public void testGetBulkOutputDir() {
     // Create a user who is not the current user
@@ -112,6 +126,96 @@ public class TestBackupUtils {
       assertEquals(host + Addressing.HOSTNAME_PORT_SEPARATOR + port,
         BackupUtils.parseHostFromOldLog(testOldWalWithRegionGroupingPath));
     }
+  }
+
+  // Ensure getValidWalDirs() uses UTC timestamps regardless of what time zone 
the test is run in.
+  @Test
+  public void testGetValidWalDirForAllTimeZonesSingleDay() throws IOException {
+    // This UTC test time is a time when it is still "yesterday" in other time 
zones (such as PST)
+    List<String> walDateDirs = List.of("2026-01-23");
+    Path walDir = new Path(backupRootDir, "WALs");
+
+    // 10-minute window in UTC between start and end time
+    long startTime = Instant.parse("2026-01-23T01:00:00Z").toEpochMilli();
+    long endTime = startTime + (10 * 60 * 1000);
+
+    testGetValidWalDirs(startTime, endTime, walDir, walDateDirs, 1, 
walDateDirs);
+  }
+
+  // Ensure getValidWalDirs() works as expected for time ranges across 
multiple days for all time
+  // zones
+  @Test
+  public void testGetValidWalDirsForAllTimeZonesMultiDay() throws IOException {
+    List<String> walDateDirs = List.of("2025-12-30", "2025-12-31", 
"2026-01-01", "2026-01-02");
+    List<String> expectedValidWalDirs = List.of("2025-12-31", "2026-01-01");
+    Path walDir = new Path(backupRootDir, "WALs");
+
+    // 10-minute window in UTC between start and end time that spans over two 
days
+    long startTime = Instant.parse("2025-12-31T23:55:00Z").toEpochMilli();
+    long endTime = Instant.parse("2026-01-01T00:05:00Z").toEpochMilli();
+
+    testGetValidWalDirs(startTime, endTime, walDir, walDateDirs, 2, 
expectedValidWalDirs);
+  }
+
+  @Test
+  public void testGetValidWalDirExactlyMidnightUTC() throws IOException {
+    List<String> walDateDirs = List.of("2026-01-23");
+    Path walDir = new Path(backupRootDir, "WALs");
+    // This instant is UTC
+    long startAndEndTime = 
Instant.parse("2026-01-23T00:00:00.000Z").toEpochMilli();
 
+    testGetValidWalDirs(startAndEndTime, startAndEndTime, walDir, walDateDirs, 
1, walDateDirs);
+  }
+
+  @Test
+  public void testGetValidWalDirOneMsBeforeMidnightUTC() throws IOException {
+    List<String> walDateDirs = List.of("2026-01-23");
+    Path walDir = new Path(backupRootDir, "WALs");
+    // This instant is UTC
+    long startAndEndTime = 
Instant.parse("2026-01-23T23:59:59.999Z").toEpochMilli();
+
+    testGetValidWalDirs(startAndEndTime, startAndEndTime, walDir, walDateDirs, 
1, walDateDirs);
+  }
+
+  protected void testGetValidWalDirs(long startTime, long endTime, Path walDir,
+    List<String> availableWalDateDirs, int numExpectedValidWalDirs,
+    List<String> expectedValidWalDirs) throws IOException {
+    TimeZone defaultTimeZone = TimeZone.getDefault();
+    try {
+      // This UTC test time is a time when it is still "yesterday" in other 
time zones (such as PST)
+      for (String dirName : availableWalDateDirs) {
+        dummyFs.mkdirs(new Path(walDir, dirName));
+      }
+
+      // Ensure we can get valid WAL dirs regardless of the test environment's 
time zone
+      for (String timeZone : ZoneId.getAvailableZoneIds()) {
+        // Force test environment to use specified time zone
+        TimeZone.setDefault(TimeZone.getTimeZone(timeZone));
+
+        List<String> validWalDirs = 
BackupUtils.getValidWalDirs(TEST_UTIL.getConfiguration(),
+          backupRootDir, startTime, endTime);
+
+        // Verify the correct number of valid WAL dirs was found
+        assertEquals("The number of valid WAL dirs should be " + 
numExpectedValidWalDirs
+          + " for time zone " + timeZone, numExpectedValidWalDirs, 
validWalDirs.size());
+
+        // Verify the list of valid WAL dirs is as expected
+        for (String dirName : expectedValidWalDirs) {
+          assertTrue("Expected " + dirName + " to be a valid WAL dir",
+            validWalDirs.stream().anyMatch(path -> path.endsWith("/" + 
dirName)));
+        }
+
+        // Verify the list of valid WAL dirs does not contain anything 
expected to be invalid
+        List<String> expectedInvalidWalDirs = new 
ArrayList<>(availableWalDateDirs);
+        expectedInvalidWalDirs.removeAll(expectedValidWalDirs);
+        for (String dirName : expectedInvalidWalDirs) {
+          assertFalse("Expected " + dirName + " to NOT be a valid WAL dir",
+            validWalDirs.contains(dirName));
+        }
+      }
+    } finally {
+      TimeZone.setDefault(defaultTimeZone);
+      dummyFs.delete(walDir, true);
+    }
   }
 }

Reply via email to