Merge "Migration of ZoneInfo to 64-bit times from 32-bit"
am: 52d46d03e4

Change-Id: I2a216b7dcbcbc3e21093da8bc98cb8e6ed7f82b1
diff --git a/luni/src/main/java/libcore/io/BufferIterator.java b/luni/src/main/java/libcore/io/BufferIterator.java
index b7c5b38..97890c3 100644
--- a/luni/src/main/java/libcore/io/BufferIterator.java
+++ b/luni/src/main/java/libcore/io/BufferIterator.java
@@ -44,13 +44,13 @@
     public abstract int pos();
 
     /**
-     * Copies {@code byteCount} bytes from the current position into {@code dst}, starting at
-     * {@code dstOffset}, and advances the current position {@code byteCount} bytes.
+     * Copies {@code byteCount} bytes from the current position into {@code bytes}, starting at
+     * {@code arrayOffset}, and advances the current position {@code byteCount} bytes.
      *
      * @throws IndexOutOfBoundsException if the read / write would be outside of the buffer / array
      */
     @UnsupportedAppUsage
-    public abstract void readByteArray(byte[] dst, int dstOffset, int byteCount);
+    public abstract void readByteArray(byte[] bytes, int arrayOffset, int byteCount);
 
     /**
      * Returns the byte at the current position, and advances the current position one byte.
@@ -69,13 +69,21 @@
     public abstract int readInt();
 
     /**
-     * Copies {@code intCount} 32-bit ints from the current position into {@code dst}, starting at
-     * {@code dstOffset}, and advances the current position {@code 4 * intCount} bytes.
+     * Copies {@code intCount} 32-bit ints from the current position into {@code ints}, starting at
+     * {@code arrayOffset}, and advances the current position {@code 4 * intCount} bytes.
      *
      * @throws IndexOutOfBoundsException if the read / write would be outside of the buffer / array
      */
     @UnsupportedAppUsage
-    public abstract void readIntArray(int[] dst, int dstOffset, int intCount);
+    public abstract void readIntArray(int[] ints, int arrayOffset, int intCount);
+
+    /**
+     * Copies {@code longCount} 64-bit ints from the current position into {@code longs}, starting
+     * at {@code arrayOffset}, and advances the current position {@code 8 * longCount} bytes.
+     *
+     * @throws IndexOutOfBoundsException if the read / write would be outside of the buffer / array
+     */
+    public abstract void readLongArray(long[] longs, int arrayOffset, int longCount);
 
     /**
      * Returns the 16-bit short at the current position, and advances the current position two bytes.
diff --git a/luni/src/main/java/libcore/io/NioBufferIterator.java b/luni/src/main/java/libcore/io/NioBufferIterator.java
index 263666d..1554519 100644
--- a/luni/src/main/java/libcore/io/NioBufferIterator.java
+++ b/luni/src/main/java/libcore/io/NioBufferIterator.java
@@ -50,10 +50,12 @@
         this.swap = swap;
     }
 
+    @Override
     public void seek(int offset) {
         position = offset;
     }
 
+    @Override
     public void skip(int byteCount) {
         position += byteCount;
     }
@@ -63,14 +65,16 @@
         return position;
     }
 
-    public void readByteArray(byte[] dst, int dstOffset, int byteCount) {
-        checkDstBounds(dstOffset, dst.length, byteCount);
+    @Override
+    public void readByteArray(byte[] bytes, int arrayOffset, int byteCount) {
+        checkArrayBounds(arrayOffset, bytes.length, byteCount);
         file.checkNotClosed();
         checkReadBounds(position, length, byteCount);
-        Memory.peekByteArray(address + position, dst, dstOffset, byteCount);
+        Memory.peekByteArray(address + position, bytes, arrayOffset, byteCount);
         position += byteCount;
     }
 
+    @Override
     public byte readByte() {
         file.checkNotClosed();
         checkReadBounds(position, length, 1);
@@ -79,6 +83,7 @@
         return result;
     }
 
+    @Override
     public int readInt() {
         file.checkNotClosed();
         checkReadBounds(position, length, Integer.BYTES);
@@ -87,15 +92,27 @@
         return result;
     }
 
-    public void readIntArray(int[] dst, int dstOffset, int intCount) {
-        checkDstBounds(dstOffset, dst.length, intCount);
+    @Override
+    public void readIntArray(int[] ints, int arrayOffset, int intCount) {
+        checkArrayBounds(arrayOffset, ints.length, intCount);
         file.checkNotClosed();
         final int byteCount = Integer.BYTES * intCount;
         checkReadBounds(position, length, byteCount);
-        Memory.peekIntArray(address + position, dst, dstOffset, intCount, swap);
+        Memory.peekIntArray(address + position, ints, arrayOffset, intCount, swap);
         position += byteCount;
     }
 
+    @Override
+    public void readLongArray(long[] longs, int arrayOffset, int longCount) {
+        checkArrayBounds(arrayOffset, longs.length, longCount);
+        file.checkNotClosed();
+        final int byteCount = Long.BYTES * longCount;
+        checkReadBounds(position, length, byteCount);
+        Memory.peekLongArray(address + position, longs, arrayOffset, longCount, swap);
+        position += byteCount;
+    }
+
+    @Override
     public short readShort() {
         file.checkNotClosed();
         checkReadBounds(position, length, Short.BYTES);
@@ -118,18 +135,18 @@
         }
     }
 
-    private static void checkDstBounds(int dstOffset, int dstLength, int count) {
-        if (dstOffset < 0 || count < 0) {
+    private static void checkArrayBounds(int arrayOffset, int arrayLength, int count) {
+        if (arrayOffset < 0 || count < 0) {
             throw new IndexOutOfBoundsException(
-                    "Invalid dst args: offset=" + dstLength + ", count=" + count);
+                    "Invalid args: arrayOffset=" + arrayOffset + ", count=" + count);
         }
-        // Use of int here relies on dstLength being an int <= Integer.MAX_VALUE, which it has to
+        // Use of int here relies on arrayLength being an int <= Integer.MAX_VALUE, which it has to
         // be because it's an array length.
-        final int targetPos = dstOffset + count;
-        if (targetPos < 0 || targetPos > dstLength) {
+        final int targetPos = arrayOffset + count;
+        if (targetPos < 0 || targetPos > arrayLength) {
             throw new IndexOutOfBoundsException(
-                    "Write outside range: dst.length=" + dstLength + ", offset="
-                            + dstOffset + ", count=" + count);
+                    "Write outside range: arrayLength=" + arrayLength + ", arrayOffset="
+                            + arrayOffset + ", count=" + count);
         }
     }
 }
diff --git a/luni/src/main/java/libcore/util/ZoneInfo.java b/luni/src/main/java/libcore/util/ZoneInfo.java
index abd98b1..48665ca 100644
--- a/luni/src/main/java/libcore/util/ZoneInfo.java
+++ b/luni/src/main/java/libcore/util/ZoneInfo.java
@@ -48,20 +48,9 @@
  * and storing it a representation to support the {@link TimeZone} and {@link GregorianCalendar}
  * implementations. See {@link ZoneInfo#readTimeZone(String, BufferIterator, long)}.
  *
- * <p>The main difference between {@code tzfile} and the compacted form is that the
- * {@code struct ttinfo} only uses a single byte for {@code tt_isdst} and {@code tt_abbrind}.
- *
  * <p>This class does not use all the information from the {@code tzfile}; it uses:
  * {@code tzh_timecnt} and the associated transition times and type information. For each type
- * (described by {@code struct ttinfo}) it uses {@code tt_gmtoff} and {@code tt_isdst}. Note, that
- * the definition of {@code struct ttinfo} uses {@code long}, and {@code int} but they do not have
- * the same meaning as Java. The prose following the definition makes it clear that the {@code long}
- * is 4 bytes and the {@code int} fields are 1 byte.
- *
- * <p>As the data uses 32 bits to store the time in seconds the time range is limited to roughly
- * 69 years either side of the epoch (1st Jan 1970 00:00:00) that means that it cannot handle any
- * dates before 1900 and after 2038. There is an extended version of the table that uses 64 bits
- * to store the data but that information is not used by this.
+ * (described by {@code struct ttinfo}) it uses {@code tt_gmtoff} and {@code tt_isdst}.
  *
  * <p>This class should be in libcore.timezone but this class is Serializable so cannot
  * be moved there without breaking apps that have (for some reason) serialized TimeZone objects.
@@ -197,6 +186,19 @@
 
     public static ZoneInfo readTimeZone(String id, BufferIterator it, long currentTimeMillis)
             throws IOException {
+
+        // Skip over the superseded 32-bit header and data.
+        skipOver32BitData(id, it);
+
+        // Read the v2+ 64-bit header and data.
+        return read64BitData(id, it, currentTimeMillis);
+    }
+
+    /**
+     * Skip over the 32-bit data with some minimal validation to make sure sure we reading a valid
+     * and supported file.
+     */
+    private static void skipOver32BitData(String id, BufferIterator it) throws IOException {
         // Variable names beginning tzh_ correspond to those in "tzfile.h".
 
         // Check tzh_magic.
@@ -205,24 +207,82 @@
             throw new IOException("Timezone id=" + id + " has an invalid header=" + tzh_magic);
         }
 
-        // Skip the uninteresting part of the header.
-        it.skip(28);
+        byte tzh_version = it.readByte();
+        checkTzifVersionAcceptable(id, tzh_version);
+
+        // Skip the unused bytes.
+        it.skip(15);
+
+        // Read the header values necessary to read through all the 32-bit data.
+        int tzh_ttisgmtcnt = it.readInt();
+        int tzh_ttisstdcnt = it.readInt();
+        int tzh_leapcnt = it.readInt();
+        int tzh_timecnt = it.readInt();
+        int tzh_typecnt = it.readInt();
+        int tzh_charcnt = it.readInt();
+
+        // Skip transitions data, 4 bytes for each 32-bit time + 1 byte for isDst.
+        final int transitionInfoSize = 4 + 1;
+        it.skip(tzh_timecnt * transitionInfoSize);
+
+        // Skip ttinfos.
+        // struct ttinfo {
+        //     int32_t       tt_gmtoff;
+        //     unsigned char tt_isdst;
+        //     unsigned char tt_abbrind;
+        // };
+        final int ttinfoSize = 4 + 1 + 1;
+        it.skip(tzh_typecnt * ttinfoSize);
+
+        // Skip tzh_charcnt time zone abbreviations.
+        it.skip(tzh_charcnt);
+
+        // Skip tzh_leapcnt repetitions of a 32-bit time + a 32-bit correction.
+        int leapInfoSize = 4 + 4;
+        it.skip(tzh_leapcnt * leapInfoSize);
+
+        // Skip ttisstds and ttisgmts information. These can be ignored for our usecases as per
+        // https://mm.icann.org/pipermail/tz/2006-February/013359.html
+        it.skip(tzh_ttisstdcnt + tzh_ttisgmtcnt);
+    }
+
+    /**
+     * Read the 64-bit header and data for {@code id} from the current position of {@code it} and
+     * return a ZoneInfo.
+     */
+    private static ZoneInfo read64BitData(String id, BufferIterator it, long currentTimeMillis)
+            throws IOException {
+        // Variable names beginning tzh_ correspond to those in "tzfile.h".
+
+        // Check tzh_magic.
+        int tzh_magic = it.readInt();
+        if (tzh_magic != 0x545a6966) { // "TZif"
+            throw new IOException("Timezone id=" + id + " has an invalid header=" + tzh_magic);
+        }
+
+        byte tzh_version = it.readByte();
+        checkTzifVersionAcceptable(id, tzh_version);
+
+        // Skip the uninteresting parts of the header.
+        it.skip(27);
 
         // Read the sizes of the arrays we're about to read.
         int tzh_timecnt = it.readInt();
+
         // Arbitrary ceiling to prevent allocating memory for corrupt data.
-        // 2 per year with 2^32 seconds would give ~272 transitions.
         final int MAX_TRANSITIONS = 2000;
         if (tzh_timecnt < 0 || tzh_timecnt > MAX_TRANSITIONS) {
             throw new IOException(
-                    "Timezone id=" + id + " has an invalid number of transitions=" + tzh_timecnt);
+                    "Timezone id=" + id + " has an invalid number of transitions="
+                            + tzh_timecnt);
         }
 
         int tzh_typecnt = it.readInt();
         final int MAX_TYPES = 256;
         if (tzh_typecnt < 1) {
             throw new IOException("ZoneInfo requires at least one type "
-                    + "to be provided for each timezone but could not find one for '" + id + "'");
+                    + "to be provided for each timezone but could not find one for '" + id
+                    + "'");
         } else if (tzh_typecnt > MAX_TYPES) {
             throw new IOException(
                     "Timezone with id " + id + " has too many types=" + tzh_typecnt);
@@ -230,18 +290,9 @@
 
         it.skip(4); // Skip tzh_charcnt.
 
-        // Transitions are signed 32 bit integers, but we store them as signed 64 bit
-        // integers since it's easier to compare them against 64 bit inputs (see getOffset
-        // and isDaylightTime) with much less risk of an overflow in our calculations.
-        //
-        // The alternative of checking the input against the first and last transition in
-        // the array is far more awkward and error prone.
-        int[] transitions32 = new int[tzh_timecnt];
-        it.readIntArray(transitions32, 0, transitions32.length);
-
         long[] transitions64 = new long[tzh_timecnt];
+        it.readLongArray(transitions64, 0, transitions64.length);
         for (int i = 0; i < tzh_timecnt; ++i) {
-            transitions64[i] = transitions32[i];
             if (i > 0 && transitions64[i] <= transitions64[i - 1]) {
                 throw new IOException(
                         id + " transition at " + i + " is not sorted correctly, is "
@@ -249,13 +300,14 @@
             }
         }
 
-        byte[] type = new byte[tzh_timecnt];
-        it.readByteArray(type, 0, type.length);
-        for (int i = 0; i < type.length; i++) {
-            int typeIndex = type[i] & 0xff;
+        byte[] types = new byte[tzh_timecnt];
+        it.readByteArray(types, 0, types.length);
+        for (int i = 0; i < types.length; i++) {
+            int typeIndex = types[i] & 0xff;
             if (typeIndex >= tzh_typecnt) {
                 throw new IOException(
-                        id + " type at " + i + " is not < " + tzh_typecnt + ", is " + typeIndex);
+                        id + " type at " + i + " is not < " + tzh_typecnt + ", is "
+                                + typeIndex);
             }
         }
 
@@ -277,8 +329,19 @@
             // for any locale. (The RI doesn't do any better than us here either.)
             it.skip(1);
         }
+        return new ZoneInfo(id, transitions64, types, gmtOffsets, isDsts, currentTimeMillis);
+    }
 
-        return new ZoneInfo(id, transitions64, type, gmtOffsets, isDsts, currentTimeMillis);
+    private static void checkTzifVersionAcceptable(String id, byte tzh_version) throws IOException {
+        char tzh_version_char = (char) tzh_version;
+        // Version >= 2 is required because the 64-bit time section is required. v3 is the latest
+        // version known at the time of writing and is identical to v2 in the parts used by this
+        // class.
+        if (tzh_version_char != '2' && tzh_version_char != '3') {
+            throw new IOException(
+                    "Timezone id=" + id + " has an invalid format version=\'" + tzh_version_char
+                            + "\' (" + tzh_version + ")");
+        }
     }
 
     private ZoneInfo(String name, long[] transitions, byte[] types, int[] gmtOffsets, byte[] isDsts,
diff --git a/luni/src/test/java/libcore/java/util/TimeZoneTest.java b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
index 481766c..da4a04f 100644
--- a/luni/src/test/java/libcore/java/util/TimeZoneTest.java
+++ b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
@@ -59,7 +59,7 @@
 
     // http://code.google.com/p/android/issues/detail?id=14395
     public void testPreHistoricInDaylightTime() {
-        // A replacement for testPreHistoricInDaylightTime_old() using a zone that still lacks an
+        // A replacement for testPreHistoricInDaylightTime_old() using a zone that lacks an
         // explicit transition at Integer.MIN_VALUE with zic 2019a and 2019a data.
         TimeZone tz = TimeZone.getTimeZone("CET");
 
@@ -76,9 +76,7 @@
     public void testPreHistoricInDaylightTime_old() throws Exception {
         // Originally this test was intended to assert what happens when the first transition for a
         // time zone was a "to DST" transition. i.e. that the (implicit) offset / DST state before
-        // the first was treated as a non-DST state. Since zic version 2014c some zones have an
-        // explicit non-DST transition at time -2^31 seconds so it is no longer possible to test
-        // this with America/Los_Angeles.
+        // the first was treated as a non-DST state. With the latest data this is no longer true.
         // This regression test has been kept in case that changes again in future and to prove the
         // behavior has remained consistent.
 
@@ -92,7 +90,7 @@
         assertFalse(tz.inDaylightTime(date));
         assertEquals("Fri Oct 31 08:00:00 PST 1902", date.toString());
         assertEquals("31 Oct 1902 16:00:00 GMT", date.toGMTString());
-        // For zic versions <= 2014b, this would be before the first transition.
+        // For zic versions <= 2014b with 32-bit data, this would be before the first transition.
         date = sdf.parse("1902-06-01T00:00:00.000+0800");
         assertEquals(-28800000, tz.getOffset(date.getTime()));
         assertFalse(tz.inDaylightTime(date));
@@ -109,7 +107,7 @@
         // prehistoric offsets. http://b/118835133
         // "Africa/Bissau" has just a few known transitions:
         // Transition time             : Offset    : DST / non-DST
-        // <Integer.MIN_VALUE secs>[1] : -01:02:20 : non-DST
+        // <Before first transition>[1]: -01:02:20 : non-DST
         // 1912-01-01 01:00:00 GMT     : -01:00:00 : non-DST
         // 1975-01-01 01:00:00 GMT     :  00:00:00 : non-DST
         //
@@ -117,7 +115,8 @@
         // generate the data. When implicit, the first non-DST type defn should be used.
         TimeZone tz = TimeZone.getTimeZone("Africa/Bissau");
 
-        // Times before Integer.MIN_VALUE should assume we're using the first non-DST type.
+        // Integer.MIN_VALUE seconds should not be significant for TimeZone on Android since it
+        // switched to using 64-bit data but we try a time before to make sure that is true.
         assertNonDaylightOffset(-3740, parseIsoTime("1900-01-01T00:00:00.0+0000"), tz);
 
         // Time before 1912-01-01 01:00:00 but after Integer.MIN_VALUE.
@@ -351,11 +350,6 @@
      * calculations. A bug (http://b/18839557) was reported when someone noticed that Android's
      * TimeZone didn't produce the same answers as other libraries at times just outside the range
      * of Integer seconds. The reason was because of int overflow / underflow which has been fixed.
-     * At the time of writing, Android's java.util.TimeZone implementation only supports reading
-     * TZif version 1 data (32-bit times) and provides one additional "before first transition"
-     * type. This makes Android's time zone information outside of the Integer range unreliable and
-     * unlikely to match libraries that use 64-bit times for transitions and/or calculate times
-     * outside of the range using rules (e.g. like ICU4J does).
      */
     public void testOverflowing32BitUnixDates() {
         final TimeZone tz = TimeZone.getTimeZone("America/New_York");
@@ -370,13 +364,8 @@
         // This timezone didn't have any daylight savings prior to 1917 and this date is in 1900.
         assertFalse(tz.inDaylightTime(new Date(lowerTimeMillis)));
 
-        // http://b/118835133:
-        // zic <= 2014b produces data that suggests before -1633280400 seconds (Sun, 31 Mar 1918
-        // 07:00:00 GMT) the offset was -18000000.
-        // zic > 2014b produces data that suggests before Integer.MIN_VALUE seconds the offset was
-        // -17762000 and between Integer.MIN_VALUE and -1633280400 it was -18000000.
         int actualOffset = tz.getOffset(lowerTimeMillis);
-        assertEquals(-17762000, actualOffset);
+        assertEquals(-18000000, actualOffset);
 
         // Nov 30th 2039, no daylight savings as per current rules.
         assertFalse(tz.inDaylightTime(new Date(upperTimeMillis)));
diff --git a/luni/src/test/java/libcore/libcore/util/ZoneInfoTest.java b/luni/src/test/java/libcore/libcore/util/ZoneInfoTest.java
index 6cf4a00..fba0b47 100644
--- a/luni/src/test/java/libcore/libcore/util/ZoneInfoTest.java
+++ b/luni/src/test/java/libcore/libcore/util/ZoneInfoTest.java
@@ -315,24 +315,17 @@
   }
 
   /**
-   * TimeZone APIs use long times in millis. Android uses TZif version 1 format data which
-   * uses 32-bit time values for transitions so it only gives accurate results for times in that
-   * range.
+   * TimeZone APIs use Java long times in millis.
    *
-   * <p>Newer versions of zic after 2014b introduce an explicit transition at the earliest
-   * representable time, which is Integer.MIN_VALUE for TZif version 1 files. Previously the type
-   * used was left implicit and readers were expected to use the first non-DST type in the file.
-   * This extra transition mostly went away again with zic 2018f.
-   *
-   * <p>Testing newer zic versions demonstrated that Android had been mishandling the lookup of
-   * offset for times before the first transition. The logic has been corrected. This test would
-   * fail on versions of Android <= P.
+   * <p>Android has historically mishandled the lookup of offset for times before Integer.MIN_VALUE
+   * seconds for various reasons. The logic has been corrected. This test would fail on versions of
+   * Android <= P.
    */
   public void testReadTimeZone_Bug118835133_extraFirstTransition() throws Exception {
-    // A time before the first representable time in a TZif version 1 file.
+    // A time before the first representable time in seconds with a java int.
     Instant before32BitTime = timeFromSeconds(Integer.MIN_VALUE).minusMillis(1);
 
-    // Times between the start of the 32-bit time range and the first "official" transition.
+    // Times around the 32-bit seconds minimum.
     Instant[] earlyTimes = {
             timeFromSeconds(Integer.MIN_VALUE),
             timeFromSeconds(Integer.MIN_VALUE).plusMillis(1),
@@ -358,8 +351,7 @@
             { offsetToSeconds(type2Offset), 0 },
     };
 
-    // Creates a simulation of zic version <= 2014b or zic version >= 2018f where there is often
-    // no explicit transition at Integer.MIN_VALUE seconds in TZif version 1 data.
+    // Simulates a zone with a single transition.
     {
       long[][] transitions = {
               { timeToSeconds(firstRealTransitionTime), 2 /* type 2 */ },
@@ -375,11 +367,11 @@
       assertOffsetAt(oldZoneInfo, type2Offset, afterFirstRealTransitionTimes);
     }
 
-    // Creates a simulation of zic version > 2014b and zic version < 2018f where there is usually an
-    // explicit transition at Integer.MIN_VALUE seconds for TZif version 1 data.
+    // Simulation a zone where there is an explicit transition at Integer.MIN_VALUE seconds. This
+    // used to be common when Android used 32-bit data from the TZif file.
     {
       long[][] transitions = {
-              { Integer.MIN_VALUE, 1 /* type 1 */ }, // The extra transition added by zic.
+              { Integer.MIN_VALUE, 1 /* type 1 */ },
               { timeToSeconds(firstRealTransitionTime), 2 /* type 2 */ },
       };
       ZoneInfo newZoneInfo = createZoneInfo(transitions, types, currentTime);
@@ -398,7 +390,7 @@
 
   /**
    * Newer versions of zic after 2014b sometime introduce an explicit transition at
-   * Integer.MAX_VALUE.
+   * Integer.MAX_VALUE seconds.
    */
   public void testReadTimeZone_Bug118835133_extraLastTransition() throws Exception {
     // An arbitrary time to use as currentTime. Not important for this test.
@@ -419,8 +411,8 @@
     };
     Duration expectedLateOffset = offsetFromSeconds(latestOffsetSeconds);
 
-    // Create a simulation of zic version <= 2014b where there is usually no explicit transition at
-    // Integer.MAX_VALUE seconds.
+    // Create a simulation of a zone where there is no explicit transition at Integer.MAX_VALUE
+    // seconds.
     {
       long[][] transitions = {
               { 1000, 0 },
@@ -430,8 +422,8 @@
       assertOffsetAt(oldZoneInfo, expectedLateOffset, timesToCheck);
     }
 
-    // Create a simulation of zic version > 2014b where there is sometimes an explicit transition at
-    // Integer.MAX_VALUE seconds.
+    // Create a simulation of a zone where there is an explicit transition at Integer.MAX_VALUE
+    // seconds.
     {
       long[][] transitions = {
               { 1000, 0 },
@@ -500,7 +492,7 @@
     assertNotNull(createZoneInfo(getName(), Instant.now(), builder.build()));
   }
 
-  public void testReadTimeZone_BadMagic() throws Exception {
+  public void testReadTimeZone_BadMagic() {
     ZoneInfoTestHelper.ZicDataBuilder builder =
             new ZoneInfoTestHelper.ZicDataBuilder()
                     .initializeToValid()
@@ -683,16 +675,12 @@
     }
   }
 
-  private static Instant timeFromSeconds(int timeInSeconds) {
+  private static Instant timeFromSeconds(long timeInSeconds) {
     return Instant.ofEpochSecond(timeInSeconds);
   }
 
-  private static int timeToSeconds(Instant time) {
-    long seconds = time.getEpochSecond();
-    if (seconds < Integer.MIN_VALUE || seconds > Integer.MAX_VALUE) {
-      fail("Time out of seconds range: " + time);
-    }
-    return (int) seconds;
+  private static long timeToSeconds(Instant time) {
+    return time.getEpochSecond();
   }
 
   private static Duration offsetFromSeconds(int offsetSeconds) {
@@ -792,8 +780,8 @@
     }
 
     @Override
-    public void readByteArray(byte[] dst, int dstOffset, int byteCount) {
-      buffer.get(dst, dstOffset, byteCount);
+    public void readByteArray(byte[] bytes, int arrayOffset, int byteCount) {
+      buffer.get(bytes, arrayOffset, byteCount);
     }
 
     @Override
@@ -806,16 +794,24 @@
       int value = buffer.asIntBuffer().get();
       // Using a separate view does not update the position of this buffer so do it
       // explicitly.
-      skip(4);
+      skip(Integer.BYTES);
       return value;
     }
 
     @Override
-    public void readIntArray(int[] dst, int dstOffset, int intCount) {
-      buffer.asIntBuffer().get(dst, dstOffset, intCount);
+    public void readIntArray(int[] ints, int arrayOffset, int intCount) {
+      buffer.asIntBuffer().get(ints, arrayOffset, intCount);
       // Using a separate view does not update the position of this buffer so do it
       // explicitly.
-      skip(4 * intCount);
+      skip(Integer.BYTES * intCount);
+    }
+
+    @Override
+    public void readLongArray(long[] longs, int arrayOffset, int longCount) {
+      buffer.asLongBuffer().get(longs, arrayOffset, longCount);
+      // Using a separate view does not update the position of this buffer so do it
+      // explicitly.
+      skip(Long.BYTES * longCount);
     }
 
     @Override
@@ -823,7 +819,7 @@
       short value = buffer.asShortBuffer().get();
       // Using a separate view does not update the position of this buffer so do it
       // explicitly.
-      skip(2);
+      skip(Short.BYTES);
       return value;
     }
   }