Merge "Add support for 64-bit data to ZoneInfoTestHelper"
am: ef26578fcf

Change-Id: I255ee109e371d0fbd07535faa751153d10b8c2be
diff --git a/testing/src/main/java/libcore/timezone/testing/ZoneInfoTestHelper.java b/testing/src/main/java/libcore/timezone/testing/ZoneInfoTestHelper.java
index f69556a..224b193 100644
--- a/testing/src/main/java/libcore/timezone/testing/ZoneInfoTestHelper.java
+++ b/testing/src/main/java/libcore/timezone/testing/ZoneInfoTestHelper.java
@@ -31,16 +31,13 @@
     private ZoneInfoTestHelper() {}
 
     /**
-     * Constructs valid and invalid zic data for tests. Currently it only creates minimal TZif
-     * version 1 data necessary to be compatible with Android's ZoneInfo class.
+     * Constructs valid and invalid zic data for tests.
      */
     public static class ZicDataBuilder {
 
         private int magic = 0x545a6966; // Default, valid magic.
-        private Integer transitionCountOverride; // Used to override the correct transition count.
-        private int[] transitionTimes; // Time of each transition, one per transition.
-        private byte[] transitionTypes; // Type of each transition, one per transition.
-        private Integer typesCountOverride; // Used to override the correct type count.
+        private long[] transitionTimes64Bit; // Time of each transition, one per transition.
+        private byte[] transitionTypes64Bit; // Type of each transition, one per transition.
         private int[] isDsts; // Whether a type uses DST, one per type.
         private int[] offsetsSeconds; // The UTC offset, one per type.
 
@@ -51,42 +48,32 @@
             return this;
         }
 
-        public ZicDataBuilder setTypeCountOverride(int typesCountOverride) {
-            this.typesCountOverride = typesCountOverride;
-            return this;
-        }
-
-        public ZicDataBuilder setTransitionCountOverride(int transitionCountOverride) {
-            this.transitionCountOverride = transitionCountOverride;
-            return this;
-        }
-
         /**
-         * See {@link #setTransitions(int[][])} and {@link #setTypes(int[][])}.
+         * See {@link #setTransitions(long[][])} and {@link #setTypes(int[][])}.
          */
         public ZicDataBuilder setTransitionsAndTypes(
-                int[][] transitionPairs, int[][] typePairs) {
+                long[][] transitionPairs, int[][] typePairs) {
             setTransitions(transitionPairs);
             setTypes(typePairs);
             return this;
         }
         /**
-         * Sets transition information using an array of pairs of ints. e.g.
+         * Sets transition information using an array of pairs of longs. e.g.
          *
-         * new int[][] {
+         * new long[][] {
          *   { transitionTimeSeconds1, typeIndex1 },
          *   { transitionTimeSeconds2, typeIndex1 },
          * }
          */
-        public ZicDataBuilder setTransitions(int[][] transitionPairs) {
-            int[] transitions = new int[transitionPairs.length];
+        public ZicDataBuilder setTransitions(long[][] transitionPairs) {
+            long[] transitions = new long[transitionPairs.length];
             byte[] types = new byte[transitionPairs.length];
             for (int i = 0; i < transitionPairs.length; i++) {
                 transitions[i] = transitionPairs[i][0];
                 types[i] = (byte) transitionPairs[i][1];
             }
-            this.transitionTimes = transitions;
-            this.transitionTypes = types;
+            this.transitionTimes64Bit = transitions;
+            this.transitionTypes64Bit = types;
             return this;
         }
 
@@ -94,8 +81,8 @@
          * Sets transition information using an array of pairs of ints. e.g.
          *
          * new int[][] {
-         *   { typeIsDst1, offsetSeconds1 },
-         *   { typeIsDst2, offsetSeconds2 },
+         *   { offsetSeconds1, typeIsDst1 },
+         *   { offsetSeconds2, typeIsDst2 },
          * }
          */
         public ZicDataBuilder setTypes(int[][] typePairs) {
@@ -112,7 +99,7 @@
 
         /** Initializes to a minimum viable ZoneInfo data. */
         public ZicDataBuilder initializeToValid() {
-            setTransitions(new int[0][0]);
+            setTransitions(new long[0][0]);
             setTypes(new int[][] {
                     { 3600, 0}
             });
@@ -122,40 +109,106 @@
         public byte[] build() {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
-            // Magic number.
-            writeInt(baos, magic);
-
-            // Some useless stuff in the header.
-            for (int i = 0; i < 28; ++i) {
-                baos.write(i);
+            // Compute the 32-bit transitions / types.
+            List<Integer> transitionTimes32Bit = new ArrayList<>();
+            List<Byte> transitionTypes32Bit = new ArrayList<>();
+            if (transitionTimes64Bit.length > 0) {
+                // Skip transitions < Integer.MIN_VALUE.
+                int i = 0;
+                while (i < transitionTimes64Bit.length
+                        && transitionTimes64Bit[i] < Integer.MIN_VALUE) {
+                    i++;
+                }
+                // If we skipped any, add a transition at Integer.MIN_VALUE like zic does.
+                if (i > 0) {
+                    transitionTimes32Bit.add(Integer.MIN_VALUE);
+                    transitionTypes32Bit.add(transitionTypes64Bit[i - 1]);
+                }
+                // Copy remaining transitions / types that fit in the 32-bit range.
+                while (i < transitionTimes64Bit.length
+                        && transitionTimes64Bit[i] <= Integer.MAX_VALUE) {
+                    transitionTimes32Bit.add((int) transitionTimes64Bit[i]);
+                    transitionTypes32Bit.add(transitionTypes64Bit[i]);
+                    i++;
+                }
             }
 
-            // Transition time count
-            int transitionsCount = transitionCountOverride != null
-                    ? transitionCountOverride : transitionTimes.length;
-            writeInt(baos, transitionsCount);
+            int tzh_timecnt_32 = transitionTimes32Bit.size();
+            int tzh_timecnt_64 = transitionTimes64Bit.length;
+            int tzh_typecnt = offsetsSeconds.length;
+            int tzh_charcnt = 0;
+            int tzh_leapcnt = 0;
+            int tzh_ttisstdcnt = 0;
+            int tzh_ttisgmtcnt = 0;
 
-            // Transition type count.
-            int typesCount = typesCountOverride != null
-                    ? typesCountOverride : offsetsSeconds.length;
-            writeInt(baos, typesCount);
-            // Useless stuff.
-            writeInt(baos, 0xdeadbeef);
+            // Write 32-bit data.
+            writeHeader(baos, magic, tzh_timecnt_32, tzh_typecnt, tzh_charcnt, tzh_leapcnt,
+                    tzh_ttisstdcnt, tzh_ttisgmtcnt);
+            writeIntList(baos, transitionTimes32Bit);
+            writeByteList(baos, transitionTypes32Bit);
+            writeTypes(baos, offsetsSeconds, isDsts);
+            writeTrailingUnusued32BitData(baos, tzh_charcnt, tzh_leapcnt, tzh_ttisstdcnt,
+                    tzh_ttisgmtcnt);
 
-            // Transition time array, as ints.
-            writeIntArray(baos, transitionTimes);
+            // 64-bit data.
+            writeHeader(baos, magic, tzh_timecnt_64, tzh_typecnt, tzh_charcnt, tzh_leapcnt,
+                    tzh_ttisstdcnt, tzh_ttisgmtcnt);
+            writeLongArray(baos, transitionTimes64Bit);
+            writeByteArray(baos, transitionTypes64Bit);
+            writeTypes(baos, offsetsSeconds, isDsts);
 
-            // Transition type array.
-            writeByteArray(baos, transitionTypes);
+            return baos.toByteArray();
+        }
 
+        private static void writeTypes(
+                ByteArrayOutputStream baos, int[] offsetsSeconds, int[] isDsts) {
             // Offset / DST
             for (int i = 0; i < offsetsSeconds.length; i++) {
                 writeInt(baos, offsetsSeconds[i]);
                 writeByte(baos, isDsts[i]);
-                // Useless stuff.
-                writeByte(baos, i);
+                // Unused data on Android (abbreviation list index).
+                writeByte(baos, 0);
             }
-            return baos.toByteArray();
+        }
+
+        /**
+         * Writes the data after types information Android doesn't currently use but is needed so
+         * that the start of the 64-bit data can be found.
+         */
+        private static void writeTrailingUnusued32BitData(
+                ByteArrayOutputStream baos, int tzh_charcnt, int tzh_leapcnt,
+                int tzh_ttisstdcnt, int tzh_ttisgmtcnt) {
+
+            // Time zone abbreviations text.
+            writeByteArray(baos, new byte[tzh_charcnt]);
+            // tzh_leapcnt repetitions of leap second transition time + correction.
+            int leapInfoSize = 4 + 4;
+            writeByteArray(baos, new byte[tzh_leapcnt * leapInfoSize]);
+            // tzh_ttisstdcnt bytes
+            writeByteArray(baos, new byte[tzh_ttisstdcnt]);
+            // tzh_ttisgmtcnt bytes
+            writeByteArray(baos, new byte[tzh_ttisgmtcnt]);
+        }
+
+        private static void writeHeader(ByteArrayOutputStream baos, int magic, int tzh_timecnt,
+                int tzh_typecnt, int tzh_charcnt, int tzh_leapcnt, int tzh_ttisstdcnt,
+                int tzh_ttisgmtcnt) {
+            // Magic number.
+            writeInt(baos, magic);
+            // tzh_version
+            writeByte(baos, '2');
+
+            // Write out the unused part of the header.
+            for (int i = 0; i < 15; ++i) {
+                baos.write(i);
+            }
+            // Write out the known header fields.
+            writeInt(baos, tzh_ttisgmtcnt);
+            writeInt(baos, tzh_ttisstdcnt);
+            writeInt(baos, tzh_leapcnt);
+            writeInt(baos, tzh_timecnt);
+            writeInt(baos, tzh_typecnt);
+            writeInt(baos, tzh_charcnt);
         }
     }
 
@@ -300,12 +353,24 @@
         baos.write(array, 0, array.length);
     }
 
+    static void writeByteList(ByteArrayOutputStream baos, List<Byte> list) {
+        for (byte value : list) {
+            baos.write(value);
+        }
+    }
+
     static void writeByte(ByteArrayOutputStream baos, int value) {
         baos.write(value);
     }
 
-    static void writeIntArray(ByteArrayOutputStream baos, int[] array) {
-        for (int value : array) {
+    static void writeLongArray(ByteArrayOutputStream baos, long[] array) {
+        for (long value : array) {
+            writeLong(baos, value);
+        }
+    }
+
+    static void writeIntList(ByteArrayOutputStream baos, List<Integer> list) {
+        for (int value : list) {
             writeInt(baos, value);
         }
     }
@@ -315,6 +380,11 @@
         writeByteArray(os, bytes);
     }
 
+    static void writeLong(ByteArrayOutputStream os, long value) {
+        byte[] bytes = ByteBuffer.allocate(8).putLong(value).array();
+        writeByteArray(os, bytes);
+    }
+
     static void setInt(byte[] bytes, int offset, int value) {
         bytes[offset] = (byte) (value >>> 24);
         bytes[offset + 1] = (byte) (value >>> 16);