Tweak the tzdata format for future changes am: 4da0af149c

Original change: https://android-review.googlesource.com/c/platform/system/timezone/+/1314835

Change-Id: I8158b8f7b5cd2f3097776048ace8ba0c22fa4d13
diff --git a/debug_tools/host/main/java/ZoneSplitter.java b/debug_tools/host/main/java/ZoneSplitter.java
index a8b72e6..cc00254 100644
--- a/debug_tools/host/main/java/ZoneSplitter.java
+++ b/debug_tools/host/main/java/ZoneSplitter.java
@@ -63,6 +63,7 @@
         // int index_offset
         // int data_offset
         // int zonetab_offset
+        // int final_offset
         writeVersionFile(mappedFile, outputDir);
 
         final int fileSize = (int) tzData.length();
@@ -72,18 +73,30 @@
         validateOffset(data_offset, fileSize);
         int zonetab_offset = mappedFile.getInt();
         validateOffset(zonetab_offset, fileSize);
+        int final_offset = mappedFile.getInt();
 
-        if (index_offset >= data_offset || data_offset >= zonetab_offset) {
+        if (index_offset >= data_offset
+                || data_offset >= zonetab_offset
+                || zonetab_offset >= final_offset
+                || final_offset > fileSize) {
             throw new IOException("Invalid offset: index_offset=" + index_offset
                     + ", data_offset=" + data_offset + ", zonetab_offset=" + zonetab_offset
-                    + ", fileSize=" + fileSize);
+                    + ", final_offset=" + final_offset + ", fileSize=" + fileSize);
         }
 
         File zicFilesDir = new File(outputDir, "zones");
         zicFilesDir.mkdir();
         extractZicFiles(mappedFile, index_offset, data_offset, zicFilesDir);
 
-        writeZoneTabFile(mappedFile, zonetab_offset, fileSize - zonetab_offset, outputDir);
+        writeZoneTabFile(mappedFile, zonetab_offset, final_offset - zonetab_offset,
+                outputDir);
+
+        if (final_offset != fileSize) {
+            // This isn't an error, but it's worth noting: it suggests the file may be in a newer
+            // format than the current branch.
+            System.out.println(
+                    "final_offset (" + final_offset + ") != fileSize (" + fileSize + ")");
+        }
     }
 
     static MappedByteBuffer createMappedByteBuffer(File tzData) throws IOException {
@@ -174,7 +187,8 @@
                 if (ids[i].compareTo(ids[i - 1]) <= 0) {
                     throw new IOException(
                             "Index not sorted or contains multiple entries with the same ID"
-                            + ", index=" + i + ", ids[i]=" + ids[i] + ", ids[i - 1]=" + ids[i - 1]);
+                                    + ", index=" + i + ", ids[i]=" + ids[i]
+                                    + ", ids[i - 1]=" + ids[i - 1]);
                 }
             }
         }
diff --git a/input_tools/android/zone_compactor/main/java/ZoneCompactor.java b/input_tools/android/zone_compactor/main/java/ZoneCompactor.java
index c3c1089..81873aa 100644
--- a/input_tools/android/zone_compactor/main/java/ZoneCompactor.java
+++ b/input_tools/android/zone_compactor/main/java/ZoneCompactor.java
@@ -43,13 +43,13 @@
   private static final int MAXNAME = 40;
 
   // Zone name synonyms.
-  private Map<String,String> links = new HashMap<String,String>();
+  private Map<String,String> links = new HashMap<>();
 
   // File offsets by zone name.
-  private Map<String,Integer> offsets = new HashMap<String,Integer>();
+  private Map<String,Integer> offsets = new HashMap<>();
 
   // File lengths by zone name.
-  private Map<String,Integer> lengths = new HashMap<String,Integer>();
+  private Map<String,Integer> lengths = new HashMap<>();
 
   // Concatenate the contents of 'inFile' onto 'out'.
   private static void copyFile(File inFile, OutputStream out) throws Exception {
@@ -80,19 +80,20 @@
     int offset = 0;
     while ((s = reader.readLine()) != null) {
       s = s.trim();
-      if (s.startsWith("Link")) {
-        StringTokenizer st = new StringTokenizer(s);
-        st.nextToken();
+      StringTokenizer st = new StringTokenizer(s);
+      String lineType = st.nextToken();
+      if (lineType.startsWith("Link")) {
         String to = st.nextToken();
         String from = st.nextToken();
         links.put(from, to);
-      } else {
-        String link = links.get(s);
+      } else if (lineType.startsWith("Zone")) {
+        String zoneId = st.nextToken();
+        String link = links.get(zoneId);
         if (link == null) {
-          File sourceFile = new File(dataDirectory, s);
+          File sourceFile = new File(dataDirectory, zoneId);
           long length = sourceFile.length();
-          offsets.put(s, offset);
-          lengths.put(s, (int) length);
+          offsets.put(zoneId, offset);
+          lengths.put(zoneId, (int) length);
 
           offset += length;
           copyFile(sourceFile, allData);
@@ -102,9 +103,7 @@
     reader.close();
 
     // Fill in fields for links.
-    Iterator<String> it = links.keySet().iterator();
-    while (it.hasNext()) {
-      String from = it.next();
+    for (String from : links.keySet()) {
       String to = links.get(from);
 
       offsets.put(from, offsets.get(to));
@@ -121,11 +120,12 @@
     // int index_offset -- so we can slip in extra header fields in a backwards-compatible way
     // int data_offset
     // int zonetab_offset
+    // int final_offset
 
     // tzdata_version
     f.write(toAscii(new byte[12], version));
 
-    // Write dummy values for the three offsets, and remember where we need to seek back to later
+    // Write dummy values for the offsets, and remember where we need to seek back to later
     // when we have the real values.
     int index_offset_offset = (int) f.getFilePointer();
     f.writeInt(0);
@@ -133,6 +133,12 @@
     f.writeInt(0);
     int zonetab_offset_offset = (int) f.getFilePointer();
     f.writeInt(0);
+    // The final offset serves as a placeholder for sections that might be added in future and
+    // ensures we know the size of the final "real" section. Relying on the last section ending at
+    // EOF would make it harder to append sections to the end of the file in a backward compatible
+    // way.
+    int final_offset_offset = (int) f.getFilePointer();
+    f.writeInt(0);
 
     int index_offset = (int) f.getFilePointer();
 
@@ -140,9 +146,7 @@
     ArrayList<String> sortedOlsonIds = new ArrayList<String>();
     sortedOlsonIds.addAll(offsets.keySet());
     Collections.sort(sortedOlsonIds);
-    it = sortedOlsonIds.iterator();
-    while (it.hasNext()) {
-      String zoneName = it.next();
+    for (String zoneName : sortedOlsonIds) {
       if (zoneName.length() >= MAXNAME) {
         throw new RuntimeException("zone filename too long: " + zoneName.length());
       }
@@ -176,6 +180,8 @@
     }
     reader.close();
 
+    int final_offset = (int) f.getFilePointer();
+
     // Go back and fix up the offsets in the header.
     f.seek(index_offset_offset);
     f.writeInt(index_offset);
@@ -183,6 +189,8 @@
     f.writeInt(data_offset);
     f.seek(zonetab_offset_offset);
     f.writeInt(zonetab_offset);
+    f.seek(final_offset_offset);
+    f.writeInt(final_offset);
 
     f.close();
   }
@@ -199,7 +207,8 @@
 
   public static void main(String[] args) throws Exception {
     if (args.length != 5) {
-      System.err.println("usage: java ZoneCompactor <setup file> <data directory> <zone.tab file> <output directory> <tzdata version>");
+      System.err.println("usage: java ZoneCompactor <setup file> <data directory> <zone.tab file>"
+          + " <output directory> <tzdata version>");
       System.exit(0);
     }
     new ZoneCompactor(args[0], args[1], args[2], args[3], args[4]);
diff --git a/output_data/distro/distro.zip b/output_data/distro/distro.zip
index 4306024..c22c454 100644
--- a/output_data/distro/distro.zip
+++ b/output_data/distro/distro.zip
Binary files differ
diff --git a/output_data/iana/tzdata b/output_data/iana/tzdata
index 301e264..372a82a 100644
--- a/output_data/iana/tzdata
+++ b/output_data/iana/tzdata
Binary files differ
diff --git a/testing/data/test1/output_data/distro/distro.zip b/testing/data/test1/output_data/distro/distro.zip
index eeff942..4a852a6 100644
--- a/testing/data/test1/output_data/distro/distro.zip
+++ b/testing/data/test1/output_data/distro/distro.zip
Binary files differ
diff --git a/testing/data/test1/output_data/iana/tzdata b/testing/data/test1/output_data/iana/tzdata
index c5a28a4..badbbc5 100644
--- a/testing/data/test1/output_data/iana/tzdata
+++ b/testing/data/test1/output_data/iana/tzdata
Binary files differ
diff --git a/testing/data/test2/output_data/distro/distro.zip b/testing/data/test2/output_data/distro/distro.zip
index 375d438..efd5c23 100644
--- a/testing/data/test2/output_data/distro/distro.zip
+++ b/testing/data/test2/output_data/distro/distro.zip
Binary files differ
diff --git a/testing/data/test2/output_data/iana/tzdata b/testing/data/test2/output_data/iana/tzdata
index c2d09f8..4efdbf1 100644
--- a/testing/data/test2/output_data/iana/tzdata
+++ b/testing/data/test2/output_data/iana/tzdata
Binary files differ
diff --git a/testing/data/test3/output_data/distro/distro.zip b/testing/data/test3/output_data/distro/distro.zip
index b3a284f..ec45f61 100644
--- a/testing/data/test3/output_data/distro/distro.zip
+++ b/testing/data/test3/output_data/distro/distro.zip
Binary files differ
diff --git a/testing/data/test3/output_data/iana/tzdata b/testing/data/test3/output_data/iana/tzdata
index c5a28a4..badbbc5 100644
--- a/testing/data/test3/output_data/iana/tzdata
+++ b/testing/data/test3/output_data/iana/tzdata
Binary files differ
diff --git a/testing/src/main/java/libcore/timezone/testing/ZoneInfoTestHelper.java b/testing/src/main/java/libcore/timezone/testing/ZoneInfoTestHelper.java
index 224b193..20b86b0 100644
--- a/testing/src/main/java/libcore/timezone/testing/ZoneInfoTestHelper.java
+++ b/testing/src/main/java/libcore/timezone/testing/ZoneInfoTestHelper.java
@@ -226,6 +226,7 @@
         private Integer indexOffsetOverride;
         private Integer dataOffsetOverride;
         private Integer zoneTabOffsetOverride;
+        private Integer finalOffsetOverride;
 
         public TzDataBuilder() {}
 
@@ -250,6 +251,11 @@
             return this;
         }
 
+        public TzDataBuilder setFinalOffsetOverride(int finalOffset) {
+            this.finalOffsetOverride = finalOffset;
+            return this;
+        }
+
         /**
          * Adds data for a new zone. These must be added in ID string order to generate
          * a valid file.
@@ -290,6 +296,8 @@
             writeInt(baos, 0);
             int zoneTabOffsetOffset = baos.size();
             writeInt(baos, 0);
+            int finalOffsetOffset = baos.size();
+            writeInt(baos, 0);
 
             // Construct the data section in advance, so we know the offsets.
             ByteArrayOutputStream dataBytes = new ByteArrayOutputStream();
@@ -328,6 +336,8 @@
             byte[] zoneTabBytes = zoneTab.getBytes(StandardCharsets.US_ASCII);
             writeByteArray(baos, zoneTabBytes);
 
+            int finalOffset = baos.size();
+
             byte[] bytes = baos.toByteArray();
             setInt(bytes, indexOffsetOffset,
                     indexOffsetOverride != null ? indexOffsetOverride : indexOffset);
@@ -335,6 +345,8 @@
                     dataOffsetOverride != null ? dataOffsetOverride : dataOffset);
             setInt(bytes, zoneTabOffsetOffset,
                     zoneTabOffsetOverride != null ? zoneTabOffsetOverride : zoneTabOffset);
+            setInt(bytes, finalOffsetOffset,
+                    finalOffsetOverride != null ? finalOffsetOverride : finalOffset);
             return bytes;
         }
 
diff --git a/update-tzdata.py b/update-tzdata.py
index ee370c1..9a9bd2f 100755
--- a/update-tzdata.py
+++ b/update-tzdata.py
@@ -72,15 +72,30 @@
   for line in open(zic_input_file):
     fields = line.split()
     if fields:
-      if fields[0] == 'Link':
-        links.append('%s %s %s' % (fields[0], fields[1], fields[2]))
-        zones.append(fields[2])
-      elif fields[0] == 'Zone':
-        zones.append(fields[1])
-  zones.sort()
+      line_type = fields[0]
+      if line_type == 'Link':
+        # Each "Link" line requires the creation of a link from an old tz ID to
+        # a new tz ID, and implies the existence of a zone with the old tz ID.
+        #
+        # IANA terminology:
+        # TARGET = the new tz ID, LINK-NAME = the old tz ID
+        target = fields[1]
+        link_name = fields[2]
+        links.append('Link %s %s' % (target, link_name))
+        zones.append('Zone %s' % link_name)
+      elif line_type == 'Zone':
+        # Each "Zone" line indicates the existence of a tz ID.
+        #
+        # IANA terminology:
+        # NAME is the tz ID, other fields like STDOFF, RULES, FORMAT,[UNTIL] are
+        # ignored.
+        name = fields[1]
+        zones.append('Zone %s' % name)
 
   zone_compactor_setup_file = '%s/setup' % tmp_dir
   setup = open(zone_compactor_setup_file, 'w')
+
+  # Ordering requirement from ZoneCompactor: Links must come first.
   for link in sorted(set(links)):
     setup.write('%s\n' % link)
   for zone in sorted(set(zones)):