JAVA: Added the phone number to timezones mapper, including tests, binary generation...
authorroes@google.com <roes@google.com@ee073f10-1060-11df-b6a4-87a95322a99c>
Mon, 7 Oct 2013 12:27:55 +0000 (12:27 +0000)
committerroes@google.com <roes@google.com@ee073f10-1060-11df-b6a4-87a95322a99c>
Mon, 7 Oct 2013 12:27:55 +0000 (12:27 +0000)
git-svn-id: http://libphonenumber.googlecode.com/svn/trunk@616 ee073f10-1060-11df-b6a4-87a95322a99c

19 files changed:
java/build.xml
java/demo/src/com/google/phonenumbers/PhoneNumberParserServlet.java
java/geocoder/pom.xml
java/geocoder/src/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapper.java [new file with mode: 0644]
java/geocoder/src/com/google/i18n/phonenumbers/timezones/data/map_data [new file with mode: 0644]
java/geocoder/test/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapperTest.java [new file with mode: 0644]
java/geocoder/test/com/google/i18n/phonenumbers/timezones/testing_data/map_data [new file with mode: 0644]
java/internal/prefixmapper/src/com/google/i18n/phonenumbers/prefixmapper/PhonePrefixMap.java
java/internal/prefixmapper/src/com/google/i18n/phonenumbers/prefixmapper/PrefixTimeZonesMap.java [new file with mode: 0644]
java/internal/prefixmapper/test/com/google/i18n/phonenumbers/prefixmapper/PrefixTimeZonesMapTest.java [new file with mode: 0644]
java/release_notes.txt
resources/test/timezones/map_data.txt [new file with mode: 0644]
resources/timezones/map_data.txt [new file with mode: 0644]
tools/java/cpp-build/target/cpp-build-1.0-SNAPSHOT-jar-with-dependencies.jar
tools/java/java-build/src/com/google/i18n/phonenumbers/EntryPoint.java
tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapData.java [new file with mode: 0644]
tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapDataEntryPoint.java [new file with mode: 0644]
tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar
tools/java/java-build/test/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapDataTest.java [new file with mode: 0644]

index a0553cd..18e30b2 100644 (file)
     </exec>
   </target>
 
+  <target name="build-timezones-data">
+    <exec executable="java">
+      <arg value="-jar" />
+      <arg value="${build.tools.jar}"/>
+      <arg value="GenerateTimeZonesMapData"/>
+      <arg value="${resources.dir}/timezones/map_data.txt"/>
+      <arg value="geocoder/src/com/google/i18n/phonenumbers/timezones/data"/>
+    </exec>
+  </target>
+
+  <target name="build-timezones-test-data">
+    <exec executable="java">
+      <arg value="-jar" />
+      <arg value="${build.tools.jar}"/>
+      <arg value="GenerateTimeZonesMapData"/>
+      <arg value="${resources.dir}/test/timezones/map_data.txt"/>
+      <arg value="geocoder/test/com/google/i18n/phonenumbers/timezones/testing_data"/>
+    </exec>
+  </target>
+
   <target name="build-js-metadata">
     <exec executable="java">
       <arg value="-jar" />
   </target>
 
   <target name="compile" description="Compile Java source."
-          depends="build-phone-metadata,build-short-metadata,build-alternate-metadata,build-carrier-data,build-geo-data">
+          depends="build-phone-metadata,build-short-metadata,build-alternate-metadata,build-carrier-data,build-geo-data,build-timezones-data">
     <mkdir dir="${classes.dir}"/>
     <javac srcdir="${libphonenumber.src.dir};${carrier.src.dir};${geocoder.src.dir};${prefixmapper.src.dir}"
            destdir="${classes.dir}" classpathref="classpath" includeAntRuntime="false"/>
         <include name="**/*.class"/>
         <exclude name="**/*Test*"/>
         <exclude name="**/BuildMetadata*"/>
-        <exclude name="**/PhoneNumberToCarrierMapper*"/>
+        <exclude name="**/PhoneNumberTo*"/>
         <exclude name="**/geocoding/*"/>
         <exclude name="**/prefixmapper/*"/>
       </fileset>
     <jar destfile="${jar.dir}/offline-geocoder.jar">
       <fileset dir="${classes.dir}">
         <include name="**/geocoding/*.class"/>
+        <include name="**/PhoneNumberToTimeZonesMapper*"/>
         <include name="**/prefixmapper/*.class"/>
         <exclude name="**/*Test*"/>
       </fileset>
       <fileset dir="${geocoder.src.dir}">
         <include name="**/geocoding/data/*"/>
+        <include name="**/timezones/data/*"/>
       </fileset>
     </jar>
   </target>
 
   <target name="test-jar"
-          depends="compile,build-test-metadata,build-carrier-test-data,build-geo-test-data">
+          depends="compile,build-test-metadata,build-carrier-test-data,build-geo-test-data,build-timezones-test-data">
     <mkdir dir="${jar.dir}"/>
     <jar destfile="${jar.dir}/${ant.project.name}-test.jar">
       <fileset dir="${classes.dir}">
       </fileset>
       <fileset dir="${geocoder.test.dir}">
         <include name="**/geocoding/testing_data/*"/>
+        <include name="**/timezones/testing_data/*"/>
       </fileset>
     </jar>
   </target>
index 9344fbb..77fa2a9 100644 (file)
@@ -21,6 +21,7 @@ package com.google.phonenumbers;
 import com.google.i18n.phonenumbers.AsYouTypeFormatter;
 import com.google.i18n.phonenumbers.NumberParseException;
 import com.google.i18n.phonenumbers.PhoneNumberToCarrierMapper;
+import com.google.i18n.phonenumbers.PhoneNumberToTimeZonesMapper;
 import com.google.i18n.phonenumbers.PhoneNumberUtil;
 import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
 import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
@@ -264,6 +265,16 @@ public class PhoneNumberParserServlet extends HttpServlet {
         output.append("</TABLE>");
         output.append("</DIV>");
 
+        output.append("<DIV>");
+        output.append("<TABLE border=1>");
+        output.append("<TR><TD colspan=2>PhoneNumberToTimeZonesMapper Results</TD></TR>");
+        appendLine(
+            "Time zone(s)",
+            PhoneNumberToTimeZonesMapper.getInstance().getTimeZonesForNumber(number).toString(),
+            output);
+        output.append("</TABLE>");
+        output.append("</DIV>");
+
         if (numberType == PhoneNumberType.MOBILE ||
             numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE ||
             numberType == PhoneNumberType.PAGER) {
index 3f5635a..72a212c 100644 (file)
         <directory>src/com/google/i18n/phonenumbers/geocoding/data</directory>
         <targetPath>com/google/i18n/phonenumbers/geocoding/data</targetPath>
       </resource>
+      <resource>
+        <directory>src/com/google/i18n/phonenumbers/timezones/data</directory>
+        <targetPath>com/google/i18n/phonenumbers/timezones/data</targetPath>
+      </resource>
     </resources>
     <testResources>
       <testResource>
         <directory>test/com/google/i18n/phonenumbers/geocoding/testing_data</directory>
         <targetPath>com/google/i18n/phonenumbers/geocoding/testing_data</targetPath>
       </testResource>
+      <testResource>
+        <directory>test/com/google/i18n/phonenumbers/timezones/testing_data</directory>
+        <targetPath>com/google/i18n/phonenumbers/timezones/testing_data</targetPath>
+      </testResource>
     </testResources>
   </build>
 
diff --git a/java/geocoder/src/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapper.java b/java/geocoder/src/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapper.java
new file mode 100644 (file)
index 0000000..d1d4c44
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2012 The Libphonenumber Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.i18n.phonenumbers;
+
+import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import com.google.i18n.phonenumbers.prefixmapper.PrefixTimeZonesMap;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * An offline mapper from phone numbers to time zones.
+ *
+ * @author Walter Erquinigo
+ */
+public class PhoneNumberToTimeZonesMapper {
+  private static PhoneNumberToTimeZonesMapper instance = null;
+  private static final String MAPPING_DATA_DIRECTORY =
+      "/com/google/i18n/phonenumbers/timezones/data/";
+  private static final String MAPPING_DATA_FILE_NAME = "map_data";
+  // This is defined by ICU as the unknown time zone.
+  private static final String UNKNOWN_TIMEZONE = "Etc/Unknown";
+  // A list with the ICU unknown time zone as single element.
+  // @VisibleForTesting
+  static final List<String> UNKNOWN_TIME_ZONE_LIST = new ArrayList<String>(1);
+  static {
+    UNKNOWN_TIME_ZONE_LIST.add(UNKNOWN_TIMEZONE);
+  }
+
+  private static final Logger LOGGER =
+      Logger.getLogger(PhoneNumberToTimeZonesMapper.class.getName());
+
+  private PrefixTimeZonesMap prefixTimeZonesMap = null;
+
+  // @VisibleForTesting
+  PhoneNumberToTimeZonesMapper(String prefixTimeZonesMapDataDirectory) {
+    this.prefixTimeZonesMap = loadPrefixTimeZonesMapFromFile(
+        prefixTimeZonesMapDataDirectory + MAPPING_DATA_FILE_NAME);
+  }
+
+  private PhoneNumberToTimeZonesMapper(PrefixTimeZonesMap prefixTimeZonesMap) {
+    this.prefixTimeZonesMap = prefixTimeZonesMap;
+  }
+
+  private static PrefixTimeZonesMap loadPrefixTimeZonesMapFromFile(String path) {
+    InputStream source = PhoneNumberToTimeZonesMapper.class.getResourceAsStream(path);
+    ObjectInputStream in = null;
+    PrefixTimeZonesMap map = new PrefixTimeZonesMap();
+    try {
+      in = new ObjectInputStream(source);
+      map.readExternal(in);
+    } catch (IOException e) {
+      LOGGER.log(Level.WARNING, e.toString());
+    } finally {
+      close(in);
+    }
+    return map;
+  }
+
+  private static void close(InputStream in) {
+    if (in != null) {
+      try {
+        in.close();
+      } catch (IOException e) {
+        LOGGER.log(Level.WARNING, e.toString());
+      }
+    }
+  }
+
+  /**
+   * Helper class used for lazy instantiation of a PhoneNumberToTimeZonesMapper. This also loads the
+   * map data in a thread-safe way.
+   */
+  private static class LazyHolder {
+    private static final PhoneNumberToTimeZonesMapper INSTANCE;
+    static {
+      PrefixTimeZonesMap map =
+          loadPrefixTimeZonesMapFromFile(MAPPING_DATA_DIRECTORY + MAPPING_DATA_FILE_NAME);
+      INSTANCE = new PhoneNumberToTimeZonesMapper(map);
+    }
+  }
+
+  /**
+   * Gets a {@link PhoneNumberToTimeZonesMapper} instance.
+   *
+   * <p> The {@link PhoneNumberToTimeZonesMapper} is implemented as a singleton. Therefore, calling
+   * this method multiple times will only result in one instance being created.
+   *
+   * @return  a {@link PhoneNumberToTimeZonesMapper} instance
+   */
+  public static synchronized PhoneNumberToTimeZonesMapper getInstance() {
+    return LazyHolder.INSTANCE;
+  }
+
+  /**
+   * Returns a list of time zones to which a phone number belongs.
+   *
+   * <p>This method assumes the validity of the number passed in has already been checked, and that
+   * the number is geo-localizable. We consider fixed-line and mobile numbers possible candidates
+   * for geo-localization.
+   *
+   * @param number  a valid phone number for which we want to get the time zones to which it belongs
+   * @return  a list of the corresponding time zones or a single element list with the default
+   *     unknown time zone if no other time zone was found or if the number was invalid
+   */
+  public List<String> getTimeZonesForGeographicalNumber(PhoneNumber number) {
+    return getTimeZonesForGeocodableNumber(number);
+  }
+
+  /**
+   * As per {@link #getTimeZonesForGeographicalNumber(PhoneNumber)} but explicitly checks
+   * the validity of the number passed in.
+   *
+   * @param number  the phone number for which we want to get the time zones to which it belongs
+   * @return  a list of the corresponding time zones or a single element list with the default
+   *     unknown time zone if no other time zone was found or if the number was invalid
+   */
+  public List<String> getTimeZonesForNumber(PhoneNumber number) {
+    PhoneNumberType numberType = PhoneNumberUtil.getInstance().getNumberType(number);
+    if (numberType == PhoneNumberType.UNKNOWN) {
+      return UNKNOWN_TIME_ZONE_LIST;
+    } else if (!canBeGeocoded(numberType)) {
+      return getCountryLevelTimeZonesforNumber(number);
+    }
+    return getTimeZonesForGeographicalNumber(number);
+  }
+
+  /**
+   * A similar method is implemented as PhoneNumberUtil.isNumberGeographical, which performs a
+   * stricter check, as it determines if a number has a geographical association. Also, if new
+   * phone number types were added, we should check if this other method should be updated too.
+   * TODO: Remove duplication by completing the logic in the method in PhoneNumberUtil.
+   *                   For more information, see the comments in that method.
+   */
+  private boolean canBeGeocoded(PhoneNumberType numberType) {
+    return (numberType == PhoneNumberType.FIXED_LINE ||
+            numberType == PhoneNumberType.MOBILE ||
+            numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE);
+  }
+
+  /**
+   * Returns a String with the ICU unknown time zone.
+   */
+  public static String getUnknownTimeZone() {
+    return UNKNOWN_TIMEZONE;
+  }
+
+  /**
+   * Returns a list of time zones to which a geocodable phone number belongs.
+   *
+   * @param number  the phone number for which we want to get the time zones to which it belongs
+   * @return  the list of corresponding  time zones or a single element list with the default
+   *     unknown time zone if no other time zone was found or if the number was invalid
+   */
+  private List<String> getTimeZonesForGeocodableNumber(PhoneNumber number) {
+    List<String> timezones = prefixTimeZonesMap.lookupTimeZonesForNumber(number);
+    return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST
+                                                            : timezones);
+  }
+
+  /**
+   * Returns the list of time zones corresponding to the country calling code of {@code number}.
+   *
+   * @param number  the phone number to look up
+   * @return  the list of corresponding time zones or a single element list with the default
+   *     unknown time zone if no other time zone was found
+   */
+  private List<String> getCountryLevelTimeZonesforNumber(PhoneNumber number) {
+    List<String> timezones = prefixTimeZonesMap.lookupCountryLevelTimeZonesForNumber(number);
+    return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST
+                                                            : timezones);
+  }
+}
diff --git a/java/geocoder/src/com/google/i18n/phonenumbers/timezones/data/map_data b/java/geocoder/src/com/google/i18n/phonenumbers/timezones/data/map_data
new file mode 100644 (file)
index 0000000..aa574da
Binary files /dev/null and b/java/geocoder/src/com/google/i18n/phonenumbers/timezones/data/map_data differ
diff --git a/java/geocoder/test/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapperTest.java b/java/geocoder/test/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapperTest.java
new file mode 100644 (file)
index 0000000..8bb3423
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2012 The Libphonenumber Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.i18n.phonenumbers;
+
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for PhoneNumberToTimeZonesMapper.java
+ *
+ * @author Walter Erquinigo
+ */
+public class PhoneNumberToTimeZonesMapperTest extends TestCase {
+  private final PhoneNumberToTimeZonesMapper prefixTimeZonesMapper =
+      new PhoneNumberToTimeZonesMapper(TEST_MAPPING_DATA_DIRECTORY);
+  private static final String TEST_MAPPING_DATA_DIRECTORY =
+      "/com/google/i18n/phonenumbers/timezones/testing_data/";
+  // Set up some test numbers to re-use.
+  private static final PhoneNumber AU_NUMBER =
+      new PhoneNumber().setCountryCode(61).setNationalNumber(236618300L);
+  private static final PhoneNumber CA_NUMBER =
+      new PhoneNumber().setCountryCode(1).setNationalNumber(6048406565L);
+  private static final PhoneNumber KO_NUMBER =
+      new PhoneNumber().setCountryCode(82).setNationalNumber(22123456L);
+  private static final PhoneNumber KO_INVALID_NUMBER =
+      new PhoneNumber().setCountryCode(82).setNationalNumber(1234L);
+  private static final PhoneNumber US_NUMBER1 =
+      new PhoneNumber().setCountryCode(1).setNationalNumber(6509600000L);
+  private static final PhoneNumber US_NUMBER2 =
+      new PhoneNumber().setCountryCode(1).setNationalNumber(2128120000L);
+  private static final PhoneNumber US_NUMBER3 =
+      new PhoneNumber().setCountryCode(1).setNationalNumber(6174240000L);
+  private static final PhoneNumber US_INVALID_NUMBER =
+      new PhoneNumber().setCountryCode(1).setNationalNumber(123456789L);
+  private static final PhoneNumber NUMBER_WITH_INVALID_COUNTRY_CODE =
+      new PhoneNumber().setCountryCode(999).setNationalNumber(2423651234L);
+  private static final PhoneNumber INTERNATIONAL_TOLL_FREE =
+      new PhoneNumber().setCountryCode(800).setNationalNumber(12345678L);
+
+  // NANPA time zones.
+  private static final String CHICAGO_TZ = "America/Chicago";
+  private static final String LOS_ANGELES_TZ = "America/Los_Angeles";
+  private static final String NEW_YORK_TZ = "America/New_York";
+  private static final String WINNIPEG_TZ = "America/Winnipeg";
+  // Non NANPA time zones.
+  private static final String SEOUL_TZ = "Asia/Seoul";
+  private static final String SYDNEY_TZ = "Australia/Sydney";
+
+  static List<String> buildListOfTimeZones(String ... timezones) {
+    ArrayList<String> timezonesList = new ArrayList<String>(timezones.length);
+    for (String timezone : timezones) {
+      timezonesList.add(timezone);
+    }
+    return timezonesList;
+  }
+
+  private static List<String> getNanpaTimeZonesList() {
+    return buildListOfTimeZones(NEW_YORK_TZ, CHICAGO_TZ, WINNIPEG_TZ, LOS_ANGELES_TZ);
+  }
+
+  public void testGetTimeZonesForNumber() {
+    // Test with invalid numbers even when their country code prefixes exist in the mapper.
+    assertEquals(PhoneNumberToTimeZonesMapper.UNKNOWN_TIME_ZONE_LIST,
+                 prefixTimeZonesMapper.getTimeZonesForNumber(US_INVALID_NUMBER));
+    assertEquals(PhoneNumberToTimeZonesMapper.UNKNOWN_TIME_ZONE_LIST,
+                 prefixTimeZonesMapper.getTimeZonesForNumber(KO_INVALID_NUMBER));
+    // Test with valid prefixes.
+    assertEquals(buildListOfTimeZones(SYDNEY_TZ),
+                 prefixTimeZonesMapper.getTimeZonesForNumber(AU_NUMBER));
+    assertEquals(buildListOfTimeZones(SEOUL_TZ),
+                 prefixTimeZonesMapper.getTimeZonesForNumber(KO_NUMBER));
+    assertEquals(buildListOfTimeZones(WINNIPEG_TZ),
+                 prefixTimeZonesMapper.getTimeZonesForNumber(CA_NUMBER));
+    assertEquals(buildListOfTimeZones(LOS_ANGELES_TZ),
+                 prefixTimeZonesMapper.getTimeZonesForNumber(US_NUMBER1));
+    assertEquals(buildListOfTimeZones(NEW_YORK_TZ),
+                 prefixTimeZonesMapper.getTimeZonesForNumber(US_NUMBER2));
+    // Test with an invalid country code.
+    assertEquals(PhoneNumberToTimeZonesMapper.UNKNOWN_TIME_ZONE_LIST,
+                 prefixTimeZonesMapper.getTimeZonesForNumber(NUMBER_WITH_INVALID_COUNTRY_CODE));
+    // Test with a non geographical phone number.
+    assertEquals(PhoneNumberToTimeZonesMapper.UNKNOWN_TIME_ZONE_LIST,
+                 prefixTimeZonesMapper.getTimeZonesForNumber(INTERNATIONAL_TOLL_FREE));
+  }
+
+  public void testGetTimeZonesForValidNumber() {
+    // Test with invalid numbers even when their country code prefixes exist in the mapper.
+    assertEquals(getNanpaTimeZonesList(),
+                 prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(US_INVALID_NUMBER));
+    assertEquals(buildListOfTimeZones(SEOUL_TZ),
+                 prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(KO_INVALID_NUMBER));
+    // Test with valid prefixes.
+    assertEquals(buildListOfTimeZones(SYDNEY_TZ),
+                 prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(AU_NUMBER));
+    assertEquals(buildListOfTimeZones(SEOUL_TZ),
+                 prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(KO_NUMBER));
+    assertEquals(buildListOfTimeZones(WINNIPEG_TZ),
+                 prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(CA_NUMBER));
+    assertEquals(buildListOfTimeZones(LOS_ANGELES_TZ),
+                 prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(US_NUMBER1));
+    assertEquals(buildListOfTimeZones(NEW_YORK_TZ),
+                 prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(US_NUMBER2));
+    // Test with an invalid country code.
+    assertEquals(PhoneNumberToTimeZonesMapper.UNKNOWN_TIME_ZONE_LIST,
+                 prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(
+                     NUMBER_WITH_INVALID_COUNTRY_CODE));
+    // Test with a non geographical phone number.
+    assertEquals(PhoneNumberToTimeZonesMapper.UNKNOWN_TIME_ZONE_LIST,
+                 prefixTimeZonesMapper.getTimeZonesForGeographicalNumber(
+                     INTERNATIONAL_TOLL_FREE));
+  }
+
+  public void testGetTimeZonesForValidNumberSearchingAtCountryCodeLevel() {
+    // Test that the country level time zones are returned when the number passed in is valid but
+    // not covered by any non-country level prefixes in the mapper.
+    assertEquals(prefixTimeZonesMapper.getTimeZonesForNumber(US_NUMBER3),
+                 getNanpaTimeZonesList());
+  }
+}
diff --git a/java/geocoder/test/com/google/i18n/phonenumbers/timezones/testing_data/map_data b/java/geocoder/test/com/google/i18n/phonenumbers/timezones/testing_data/map_data
new file mode 100644 (file)
index 0000000..0150711
Binary files /dev/null and b/java/geocoder/test/com/google/i18n/phonenumbers/timezones/testing_data/map_data differ
index 0d65e07..fdab3ec 100644 (file)
@@ -144,13 +144,12 @@ public class PhonePrefixMap implements Externalizable {
    * @param number  the phone number to look up
    * @return  the description of the number
    */
-  String lookup(PhoneNumber number) {
-   int numOfEntries = phonePrefixMapStorage.getNumOfEntries();
+  String lookup(long number) {
+    int numOfEntries = phonePrefixMapStorage.getNumOfEntries();
     if (numOfEntries == 0) {
       return null;
     }
-    long phonePrefix =
-        Long.parseLong(number.getCountryCode() + phoneUtil.getNationalSignificantNumber(number));
+    long phonePrefix = number;
     int currentIndex = numOfEntries - 1;
     SortedSet<Integer> currentSetOfLengths = phonePrefixMapStorage.getPossibleLengths();
     while (currentSetOfLengths.size() > 0) {
@@ -173,6 +172,18 @@ public class PhonePrefixMap implements Externalizable {
   }
 
   /**
+   * As per {@link #lookup(long)}, but receives the number as a PhoneNumber instead of a long.
+   *
+   * @param number  the phone number to look up
+   * @return  the description corresponding to the prefix that best matches this phone number
+   */
+  public String lookup(PhoneNumber number) {
+    long phonePrefix =
+        Long.parseLong(number.getCountryCode() + phoneUtil.getNationalSignificantNumber(number));
+    return lookup(phonePrefix);
+  }
+
+  /**
    * Does a binary search for {@code value} in the provided array from {@code start} to {@code end}
    * (inclusive). Returns the position if {@code value} is found; otherwise, returns the
    * position which has the largest value that is less than {@code value}. This means if
diff --git a/java/internal/prefixmapper/src/com/google/i18n/phonenumbers/prefixmapper/PrefixTimeZonesMap.java b/java/internal/prefixmapper/src/com/google/i18n/phonenumbers/prefixmapper/PrefixTimeZonesMap.java
new file mode 100644 (file)
index 0000000..8a2ce02
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2012 The Libphonenumber Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.i18n.phonenumbers.prefixmapper;
+
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.StringTokenizer;
+
+/**
+ * A utility that maps phone number prefixes to a list of strings describing the time zones to
+ * which each prefix belongs.
+ *
+ * @author Walter Erquinigo
+ */
+public class PrefixTimeZonesMap implements Externalizable {
+  private final PhonePrefixMap phonePrefixMap = new PhonePrefixMap();
+  private static final String RAW_STRING_TIMEZONES_SEPARATOR = "&";
+
+  /**
+    * Creates a {@link PrefixTimeZoneMap} initialized with {@code sortedPrefixTimeZoneMap}.  Note
+    * that the underlying implementation of this method is expensive thus should not be called by
+    * time-critical applications.
+    *
+    * @param sortedPrefixTimeZoneMap  a map from phone number prefixes to their corresponding time
+    * zones, sorted in ascending order of the phone number prefixes as integers.
+    */
+  public void readPrefixTimeZonesMap(SortedMap<Integer, String> sortedPrefixTimeZoneMap) {
+    phonePrefixMap.readPhonePrefixMap(sortedPrefixTimeZoneMap);
+  }
+
+  /**
+   * Supports Java Serialization.
+   */
+  public void writeExternal(ObjectOutput objectOutput) throws IOException {
+    phonePrefixMap.writeExternal(objectOutput);
+  }
+
+  public void readExternal(ObjectInput objectInput) throws IOException {
+    phonePrefixMap.readExternal(objectInput);
+  }
+
+  /**
+   * Returns the list of time zones {@code key} corresponds to.
+   *
+   * <p>{@code key} could be the calling country code and the full significant number of a
+   * certain number, or it could be just a phone-number prefix.
+   * For example, the full number 16502530000 (from the phone-number +1 650 253 0000) is a valid
+   * input. Also, any of its prefixes, such as 16502, is also valid.
+   *
+   * @param key  the key to look up
+   * @return  the list of corresponding time zones
+   */
+  private List<String> lookupTimeZonesForNumber(long key) {
+    // Lookup in the map data. The returned String may consist of several time zones, so it must be
+    // split.
+    String timezonesString = phonePrefixMap.lookup(key);
+    if (timezonesString == null) {
+      return new LinkedList<String>();
+    }
+    return tokenizeRawOutputString(timezonesString);
+  }
+
+  /**
+   * As per {@link #lookupTimeZonesForNumber(long)}, but receives the number as a PhoneNumber
+   * instead of a long.
+   *
+   * @param number  the phone number to look up
+   * @return  the list of corresponding time zones
+   */
+  public List<String> lookupTimeZonesForNumber(PhoneNumber number) {
+    long phonePrefix = Long.parseLong(number.getCountryCode() +
+        PhoneNumberUtil.getInstance().getNationalSignificantNumber(number));
+    return lookupTimeZonesForNumber(phonePrefix);
+  }
+
+  /**
+   * Returns the list of time zones {@code number}'s calling country code corresponds to.
+   *
+   * @param number  the phone number to look up
+   * @return  the list of corresponding time zones
+   */
+  public List<String> lookupCountryLevelTimeZonesForNumber(PhoneNumber number) {
+    return lookupTimeZonesForNumber(number.getCountryCode());
+  }
+
+  /**
+   * Split {@code timezonesString} into all the time zones that are part of it.
+   */
+  private List<String> tokenizeRawOutputString(String timezonesString) {
+    StringTokenizer tokenizer = new StringTokenizer(timezonesString,
+                                                    RAW_STRING_TIMEZONES_SEPARATOR);
+    LinkedList<String> timezonesList = new LinkedList<String>();
+    while (tokenizer.hasMoreTokens()) {
+      timezonesList.add(tokenizer.nextToken());
+    }
+    return timezonesList;
+  }
+
+  /**
+   * Dumps the mappings contained in the phone prefix map.
+   */
+  @Override
+  public String toString() {
+    return phonePrefixMap.toString();
+  }
+}
diff --git a/java/internal/prefixmapper/test/com/google/i18n/phonenumbers/prefixmapper/PrefixTimeZonesMapTest.java b/java/internal/prefixmapper/test/com/google/i18n/phonenumbers/prefixmapper/PrefixTimeZonesMapTest.java
new file mode 100644 (file)
index 0000000..c6ac492
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2012 The Libphonenumber Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.i18n.phonenumbers.prefixmapper;
+
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * Unittests for PrefixTimeZonesMap.java
+ *
+ * @author Walter Erquinigo
+ */
+public class PrefixTimeZonesMapTest extends TestCase {
+  private final PrefixTimeZonesMap prefixTimeZonesMapForUS = new PrefixTimeZonesMap();
+  private final PrefixTimeZonesMap prefixTimeZonesMapForRU = new PrefixTimeZonesMap();
+
+  // US time zones
+  private static final String CHICAGO_TZ = "America/Chicago";
+  private static final String DENVER_TZ = "America/Denver";
+  private static final String LOS_ANGELES_TZ = "America/Los_Angeles";
+  private static final String NEW_YORK_TZ = "America/New_York";
+
+  // Russian time zones
+  private static final String IRKUTSK_TZ = "Asia/Irkutsk";
+  private static final String MOSCOW_TZ = "Europe/Moscow";
+  private static final String VLADIVOSTOK_TZ = "Asia/Vladivostok";
+  private static final String YEKATERINBURG_TZ = "Asia/Yekaterinburg";
+
+  public PrefixTimeZonesMapTest() {
+    SortedMap<Integer, String> sortedMapForUS = new TreeMap<Integer, String>();
+    sortedMapForUS.put(1, NEW_YORK_TZ + "&" + CHICAGO_TZ + "&" + LOS_ANGELES_TZ + "&" + DENVER_TZ);
+    sortedMapForUS.put(1201, NEW_YORK_TZ);
+    sortedMapForUS.put(1205, CHICAGO_TZ);
+    sortedMapForUS.put(1208292, LOS_ANGELES_TZ);
+    sortedMapForUS.put(1208234, DENVER_TZ);
+    sortedMapForUS.put(1541367, LOS_ANGELES_TZ);
+    sortedMapForUS.put(1423843, NEW_YORK_TZ);
+    sortedMapForUS.put(1402721, CHICAGO_TZ);
+    sortedMapForUS.put(1208888, DENVER_TZ);
+
+    prefixTimeZonesMapForUS.readPrefixTimeZonesMap(sortedMapForUS);
+
+    SortedMap<Integer, String> sortedMapForRU = new TreeMap<Integer, String>();
+    sortedMapForRU.put(7421, VLADIVOSTOK_TZ);
+    sortedMapForRU.put(7879, MOSCOW_TZ);
+    sortedMapForRU.put(7342, YEKATERINBURG_TZ);
+    sortedMapForRU.put(7395, IRKUTSK_TZ);
+
+    prefixTimeZonesMapForRU.readPrefixTimeZonesMap(sortedMapForRU);
+  }
+
+  static List<String> buildListOfTimeZones(String ... timezones) {
+    ArrayList<String> timezonesList = new ArrayList<String>(timezones.length);
+    for (String timezone : timezones) {
+      timezonesList.add(timezone);
+    }
+    return timezonesList;
+  }
+
+  private static SortedMap<Integer, String> createMapCandidate() {
+    SortedMap<Integer, String> sortedMap = new TreeMap<Integer, String>();
+    sortedMap.put(1212, NEW_YORK_TZ);
+    sortedMap.put(1213, NEW_YORK_TZ);
+    sortedMap.put(1214, NEW_YORK_TZ);
+    sortedMap.put(1480, CHICAGO_TZ);
+    return sortedMap;
+  }
+
+  public void testLookupTimeZonesForNumberCountryLevel_US() {
+    PhoneNumber number = new PhoneNumber();
+    number.setCountryCode(1).setNationalNumber(1000000000L);
+    assertEquals(buildListOfTimeZones(NEW_YORK_TZ, CHICAGO_TZ, LOS_ANGELES_TZ, DENVER_TZ),
+                 prefixTimeZonesMapForUS.lookupTimeZonesForNumber(number));
+  }
+
+  public void testLookupTimeZonesForNumber_ValidNumber_Chicago() {
+    PhoneNumber number = new PhoneNumber();
+    number.setCountryCode(1).setNationalNumber(2051235458L);
+    assertEquals(buildListOfTimeZones(CHICAGO_TZ),
+                 prefixTimeZonesMapForUS.lookupTimeZonesForNumber(number));
+  }
+
+  public void testLookupTimeZonesForNumber_LA() {
+    PhoneNumber number = new PhoneNumber();
+    number.setCountryCode(1).setNationalNumber(2082924565L);
+    assertEquals(buildListOfTimeZones(LOS_ANGELES_TZ),
+                 prefixTimeZonesMapForUS.lookupTimeZonesForNumber(number));
+  }
+
+  public void testLookupTimeZonesForNumber_NY() {
+    PhoneNumber number = new PhoneNumber();
+    number.setCountryCode(1).setNationalNumber(2016641234L);
+    assertEquals(buildListOfTimeZones(NEW_YORK_TZ),
+                 prefixTimeZonesMapForUS.lookupTimeZonesForNumber(number));
+  }
+
+  public void testLookupTimeZonesForNumber_CH() {
+    PhoneNumber number = new PhoneNumber();
+    number.setCountryCode(41).setNationalNumber(446681300L);
+    assertEquals(buildListOfTimeZones(),
+                 prefixTimeZonesMapForUS.lookupTimeZonesForNumber(number));
+  }
+
+  public void testLookupTimeZonesForNumber_RU() {
+    PhoneNumber number = new PhoneNumber();
+    number.setCountryCode(7).setNationalNumber(87945154L);
+    assertEquals(buildListOfTimeZones(MOSCOW_TZ),
+                 prefixTimeZonesMapForRU.lookupTimeZonesForNumber(number));
+
+    number.setNationalNumber(421548578L);
+    assertEquals(buildListOfTimeZones(VLADIVOSTOK_TZ),
+                 prefixTimeZonesMapForRU.lookupTimeZonesForNumber(number));
+
+    number.setNationalNumber(342457897L);
+    assertEquals(buildListOfTimeZones(YEKATERINBURG_TZ),
+                 prefixTimeZonesMapForRU.lookupTimeZonesForNumber(number));
+
+    // A mobile number
+    number.setNationalNumber(9342457897L);
+    assertEquals(buildListOfTimeZones(),
+                 prefixTimeZonesMapForRU.lookupTimeZonesForNumber(number));
+
+    // An invalid number (too short)
+    number.setNationalNumber(3951L);
+    assertEquals(buildListOfTimeZones(IRKUTSK_TZ),
+                 prefixTimeZonesMapForRU.lookupTimeZonesForNumber(number));
+  }
+
+  /**
+   * Creates a new PrefixTimeZonesMap serializing the provided map to a stream and then reading
+   * this stream. The resulting PrefixTimeZonesMap is expected to be strictly equal to the provided
+   * one from which it was generated.
+   */
+  private static PrefixTimeZonesMap createNewPrefixTimeZonesMap(
+      PrefixTimeZonesMap prefixTimeZonesMap) throws IOException {
+    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+    ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+    prefixTimeZonesMap.writeExternal(objectOutputStream);
+    objectOutputStream.flush();
+
+    PrefixTimeZonesMap newPrefixTimeZonesMap = new PrefixTimeZonesMap();
+    newPrefixTimeZonesMap.readExternal(
+        new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())));
+    return newPrefixTimeZonesMap;
+  }
+
+  public void testReadWriteExternal() throws IOException {
+    PrefixTimeZonesMap localPrefixTimeZonesMap = new PrefixTimeZonesMap();
+    localPrefixTimeZonesMap.readPrefixTimeZonesMap(createMapCandidate());
+
+    PrefixTimeZonesMap newPrefixTimeZonesMap = createNewPrefixTimeZonesMap(localPrefixTimeZonesMap);
+    assertEquals(localPrefixTimeZonesMap.toString(), newPrefixTimeZonesMap.toString());
+  }
+}
index a387e97..880b55d 100644 (file)
@@ -1,3 +1,11 @@
+Oct 2, 2013:
+* Code changes:
+  - Added PhoneNumberToTimeZonesMapper including unittests to the geocoder maven project.
+  - Added build rules for generating the binary time zones mapping file from the text file.
+  - Modified PhoneNumberParserServlet.java (the appengine demo) to incorporate time zone mapping.
+* Metadata changes:
+  - Added the time zone mapping file.
+
 Sep 30, 2013:
 * Code changes:
   - Added PhoneNumberToCarrierMapper including unittests.
diff --git a/resources/test/timezones/map_data.txt b/resources/test/timezones/map_data.txt
new file mode 100644 (file)
index 0000000..3a438a6
--- /dev/null
@@ -0,0 +1,24 @@
+# Copyright (C) 2012 The Libphonenumber Authors
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+1|America/New_York&America/Chicago&America/Winnipeg&America/Los_Angeles
+1201|America/New_York
+1212812|America/New_York
+1234|America/New_York
+1604|America/Winnipeg
+1617423|America/Chicago
+1650960|America/Los_Angeles
+1989|Ameriac/Los_Angeles
+612|Australia/Sydney
+82|Asia/Seoul
diff --git a/resources/timezones/map_data.txt b/resources/timezones/map_data.txt
new file mode 100644 (file)
index 0000000..c0798c1
--- /dev/null
@@ -0,0 +1,1996 @@
+# Copyright (C) 2012 The Libphonenumber Authors
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Generated from:
+# Internal statistics data (2013-06-12).
+
+# TODO: Solve these current issues:
+#   -Australian prefix 618 has almost as many establishments from
+#    Australia/Adelaide as from Australia/Perth. This is due to the short
+#    length of the phone prefix. Thus, a custom config should be used to
+#    get a longer prefix.
+#   -Add prefixes for America/Noronha and Pacific/Easter.
+
+# List of deleted prefixes from the original data:
+#   -Prefixes 9761, 9762, since they just indicate the carrier.
+#   -Prefix 9765, since it is just a wireless local loop prefix.
+#   -Prefix 5905. It is the only prefix for 590 country code, which has three
+#    timezones (America/Guadeloupe, America/Halifax and America/Marigot),
+#    whereas 5905 has only one (America/Guadeloupe).
+#    TODO: Investigate where the time zones America/Halifax and America/Marigot
+#          should be used.
+
+1|America/Anguilla&America/Antigua&America/Barbados&America/Cayman&America/Chicago&America/Denver&America/Dominica&America/Edmonton&America/Grand_Turk&America/Grenada&America/Halifax&America/Jamaica&America/Juneau&America/Los_Angeles&America/Lower_Princes&America/Montserrat&America/Nassau&America/New_York&America/Port_of_Spain&America/Puerto_Rico&America/St_Johns&America/St_Kitts&America/St_Lucia&America/St_Thomas&America/St_Vincent&America/Toronto&America/Tortola&America/Vancouver&America/Winnipeg&Atlantic/Bermuda&Pacific/Guam&Pacific/Honolulu&Pacific/Pago_Pago&Pacific/Saipan
+1201|America/New_York
+1202|America/New_York
+1203|America/New_York
+1204|America/Winnipeg
+1205|America/Chicago
+1206|America/Los_Angeles
+1207|America/New_York
+120822|America/Denver
+120823|America/Denver
+120824|America/Los_Angeles
+1208253|America/Denver
+1208255|America/Los_Angeles
+120826|America/Los_Angeles
+120827|America/Denver
+120828|America/Denver
+1208292|America/Los_Angeles
+1208297|America/Denver
+12083|America/Denver
+120841|America/Denver
+120842|America/Denver
+1208433|America/Denver
+1208436|America/Denver
+1208437|America/Los_Angeles
+1208438|America/Denver
+1208442|America/Denver
+1208448|America/Los_Angeles
+1208452|America/Denver
+1208453|America/Denver
+1208454|America/Denver
+1208455|America/Denver
+1208457|America/Los_Angeles
+1208459|America/Denver
+120846|America/Denver
+1208475|America/Denver
+1208476|America/Los_Angeles
+1208478|America/Denver
+120848|America/Denver
+120849|America/Denver
+12085|America/Denver
+120861|America/Los_Angeles
+1208622|America/Denver
+1208623|America/Los_Angeles
+1208624|America/Denver
+1208628|America/Los_Angeles
+1208629|America/Denver
+120863|America/Denver
+120864|America/Denver
+120865|America/Denver
+120866|America/Los_Angeles
+1208672|America/Denver
+1208676|America/Los_Angeles
+1208677|America/Denver
+1208678|America/Denver
+1208682|America/Los_Angeles
+1208683|America/Los_Angeles
+1208684|America/Denver
+1208686|America/Los_Angeles
+1208687|America/Los_Angeles
+120870|America/Denver
+120871|America/Denver
+120872|America/Denver
+120873|America/Denver
+1208743|America/Los_Angeles
+1208745|America/Denver
+1208746|America/Los_Angeles
+1208755|America/Los_Angeles
+1208756|America/Denver
+1208762|America/Los_Angeles
+1208765|America/Los_Angeles
+1208766|America/Denver
+1208769|America/Los_Angeles
+120877|America/Los_Angeles
+1208782|America/Denver
+1208783|America/Los_Angeles
+1208785|America/Denver
+1208787|America/Denver
+1208788|America/Denver
+1208794|America/Denver
+1208798|America/Los_Angeles
+1208799|America/Los_Angeles
+1208835|America/Los_Angeles
+1208837|America/Denver
+120884|America/Denver
+120885|America/Denver
+120886|America/Denver
+1208870|America/Denver
+1208875|America/Los_Angeles
+1208878|America/Denver
+1208879|America/Denver
+1208880|America/Denver
+1208882|America/Los_Angeles
+1208883|America/Los_Angeles
+1208884|America/Denver
+1208885|America/Los_Angeles
+1208886|America/Denver
+1208887|America/Denver
+1208888|America/Denver
+120889|America/Denver
+1208922|America/Denver
+1208926|America/Los_Angeles
+1208934|America/Denver
+1208935|America/Los_Angeles
+1208938|America/Denver
+1208939|America/Denver
+120894|America/Denver
+120896|America/Los_Angeles
+120898|America/Los_Angeles
+120899|America/Denver
+1209|America/Los_Angeles
+1210|America/Chicago
+1212|America/New_York
+1213|America/Los_Angeles
+1214|America/Chicago
+1215|America/New_York
+1216|America/New_York
+1217|America/Chicago
+1218|America/Chicago
+121922|America/Chicago
+121925|America/New_York
+121926|America/Chicago
+121927|America/New_York
+121928|America/Chicago
+12193|America/Chicago
+12194|America/Chicago
+12195|America/Chicago
+12196|America/Chicago
+12197|America/Chicago
+12198|America/Chicago
+12199|America/Chicago
+1224|America/Chicago
+1225|America/Chicago
+1226|America/Toronto
+1228|America/Chicago
+1229|America/New_York
+123|America/New_York
+1240|America/New_York
+1242|America/Nassau
+1246|America/Barbados
+1248|America/New_York
+125021|America/Vancouver
+1250242|America/Edmonton
+1250245|America/Vancouver
+1250246|America/Vancouver
+1250247|America/Vancouver
+1250248|America/Vancouver
+125025|America/Vancouver
+1250260|America/Vancouver
+1250262|America/Edmonton
+1250265|America/Vancouver
+125028|America/Vancouver
+125029|America/Vancouver
+125031|America/Vancouver
+125033|America/Vancouver
+125034|America/Edmonton
+125035|America/Vancouver
+125036|America/Vancouver
+125037|America/Vancouver
+125038|America/Vancouver
+125039|America/Vancouver
+1250412|America/Vancouver
+1250417|America/Edmonton
+125042|America/Edmonton
+125044|America/Vancouver
+125046|America/Vancouver
+125047|America/Vancouver
+1250480|America/Vancouver
+1250483|America/Vancouver
+1250487|America/Vancouver
+1250489|America/Edmonton
+125049|America/Vancouver
+12505|America/Vancouver
+12506|America/Vancouver
+125070|America/Vancouver
+125071|America/Vancouver
+125072|America/Vancouver
+125074|America/Vancouver
+125075|America/Vancouver
+125076|America/Vancouver
+125077|America/Vancouver
+125078|America/Edmonton
+12508|America/Vancouver
+12509|America/Vancouver
+1251|America/Chicago
+1252|America/New_York
+1253|America/Los_Angeles
+1254|America/Chicago
+1256|America/Chicago
+1260|America/New_York
+1262|America/Chicago
+1264|America/Anguilla
+1267|America/New_York
+1268|America/Antigua
+1269|America/New_York
+1270230|America/Chicago
+1270234|America/New_York
+1270236|America/Chicago
+1270237|America/Chicago
+127024|America/Chicago
+127025|America/Chicago
+127026|America/Chicago
+127027|America/Chicago
+127029|America/Chicago
+127033|America/Chicago
+127034|America/Chicago
+127035|America/New_York
+1270360|America/New_York
+1270362|America/Chicago
+1270365|America/Chicago
+1270369|America/New_York
+127038|America/Chicago
+127039|America/Chicago
+127041|America/Chicago
+127042|America/New_York
+127043|America/Chicago
+127044|America/Chicago
+127046|America/New_York
+127047|America/Chicago
+127048|America/Chicago
+12705|America/Chicago
+127062|America/Chicago
+127063|America/Chicago
+127064|America/Chicago
+127065|America/Chicago
+127066|America/Chicago
+127067|America/Chicago
+127068|America/Chicago
+1270691|America/Chicago
+1270692|America/New_York
+1270699|America/New_York
+127070|America/Chicago
+127072|America/Chicago
+127073|America/New_York
+127074|America/Chicago
+127075|America/Chicago
+1270761|America/Chicago
+1270762|America/Chicago
+1270763|America/New_York
+1270765|America/New_York
+1270766|America/New_York
+1270769|America/New_York
+127077|America/Chicago
+1270780|America/Chicago
+1270781|America/Chicago
+1270782|America/Chicago
+1270783|America/Chicago
+1270786|America/Chicago
+1270789|America/New_York
+127079|America/Chicago
+1270821|America/Chicago
+1270824|America/Chicago
+1270825|America/Chicago
+1270826|America/Chicago
+1270827|America/Chicago
+1270828|America/New_York
+127083|America/Chicago
+127084|America/Chicago
+127085|America/Chicago
+1270862|America/New_York
+1270864|America/Chicago
+1270866|America/Chicago
+1270877|America/New_York
+1270879|America/Chicago
+127088|America/Chicago
+127089|America/Chicago
+127090|America/Chicago
+127092|America/Chicago
+127093|America/Chicago
+127096|America/Chicago
+1270982|America/New_York
+1270988|America/Chicago
+1276|America/New_York
+1281|America/Chicago
+1284|America/Tortola
+1289|America/Toronto
+1301|America/New_York
+1302|America/New_York
+1303|America/Denver
+1304|America/New_York
+1305|America/New_York
+13062|America/Winnipeg
+13063|America/Winnipeg
+13064|America/Winnipeg
+13065|America/Winnipeg
+13066|America/Winnipeg
+13067|America/Winnipeg
+130682|America/Edmonton
+130683|America/Winnipeg
+130684|America/Winnipeg
+130686|America/Winnipeg
+130687|America/Winnipeg
+130688|America/Winnipeg
+130689|America/Edmonton
+13069|America/Winnipeg
+1307|America/Denver
+1308233|America/Chicago
+1308234|America/Chicago
+1308235|America/Denver
+1308236|America/Chicago
+1308237|America/Chicago
+130825|America/Denver
+130826|America/Denver
+130828|America/Denver
+1308324|America/Chicago
+1308327|America/Denver
+130834|America/Chicago
+130835|America/Denver
+130836|America/Chicago
+130838|America/Chicago
+130839|America/Chicago
+1308423|America/Denver
+1308425|America/Chicago
+130843|America/Denver
+130845|America/Chicago
+130846|America/Chicago
+13085|America/Chicago
+130862|America/Denver
+130863|America/Denver
+130866|America/Denver
+130869|America/Chicago
+130872|America/Chicago
+130874|America/Chicago
+130875|America/Chicago
+130876|America/Denver
+130877|America/Denver
+130878|America/Chicago
+130883|America/Chicago
+130886|America/Chicago
+1308872|America/Chicago
+1308874|America/Denver
+130888|America/Denver
+13089|America/Chicago
+1309|America/Chicago
+1310|America/Los_Angeles
+1312|America/Chicago
+1313|America/New_York
+1314|America/Chicago
+1315|America/New_York
+1316|America/Chicago
+1317|America/New_York
+1318|America/Chicago
+1319|America/Chicago
+1320|America/Chicago
+1321|America/New_York
+1323|America/Los_Angeles
+1325|America/Chicago
+1330|America/New_York
+1334|America/Chicago
+1336|America/New_York
+1337|America/Chicago
+1340|America/St_Thomas
+1345|America/Cayman
+1347|America/New_York
+135|America/New_York
+1360|America/Los_Angeles
+1361|America/Chicago
+1385|America/Denver
+1386|America/New_York
+1401|America/New_York
+14022|America/Chicago
+140232|America/Chicago
+140233|America/Chicago
+140234|America/Chicago
+140235|America/Chicago
+140236|America/Chicago
+1402370|America/Chicago
+1402371|America/Chicago
+1402372|America/Chicago
+1402373|America/Chicago
+1402374|America/Chicago
+1402375|America/Chicago
+1402376|America/Denver
+1402379|America/Chicago
+140238|America/Chicago
+140239|America/Chicago
+14024|America/Chicago
+14025|America/Chicago
+14026|America/Chicago
+14027|America/Chicago
+14028|America/Chicago
+14029|America/Chicago
+1403|America/Edmonton
+1404|America/New_York
+1405|America/Chicago
+1406|America/Denver
+1407|America/New_York
+1408|America/Los_Angeles
+1409|America/Chicago
+1410|America/New_York
+1412|America/New_York
+1413|America/New_York
+1414|America/Chicago
+1415|America/Los_Angeles
+1416|America/Toronto
+1417|America/Chicago
+1418|America/Toronto
+1419|America/New_York
+14232|America/New_York
+14233|America/New_York
+142342|America/New_York
+142343|America/New_York
+1423442|America/New_York
+1423447|America/Chicago
+142346|America/New_York
+142347|America/New_York
+142348|America/New_York
+142349|America/New_York
+14235|America/New_York
+142361|America/New_York
+142362|America/New_York
+142363|America/New_York
+142364|America/New_York
+1423652|America/New_York
+1423658|America/Chicago
+142366|America/New_York
+142369|America/New_York
+14237|America/New_York
+142382|America/New_York
+142383|America/Chicago
+142384|America/New_York
+142385|America/New_York
+142386|America/New_York
+142387|America/New_York
+142388|America/New_York
+142389|America/New_York
+142391|America/New_York
+142392|America/New_York
+142394|America/Chicago
+142395|America/New_York
+142396|America/New_York
+142397|America/New_York
+142398|America/New_York
+1425|America/Los_Angeles
+1432|America/Chicago
+1434|America/New_York
+1435|America/Denver
+1438|America/Toronto
+1440|America/New_York
+1441|Atlantic/Bermuda
+1443|America/New_York
+145|America/Toronto
+146|America/Chicago
+1473|America/Grenada
+1478|America/New_York
+1479|America/Chicago
+1480|America/Denver
+1484|America/New_York
+1501|America/Chicago
+1502|America/New_York
+1503|America/Los_Angeles
+1504|America/Chicago
+1505|America/Denver
+1506|America/Halifax
+1507|America/Chicago
+1508|America/New_York
+1509|America/Los_Angeles
+1510|America/Los_Angeles
+1512|America/Chicago
+1513|America/New_York
+1514|America/Toronto
+1515|America/Chicago
+1516|America/New_York
+1517|America/New_York
+1518|America/New_York
+1519|America/Toronto
+152|America/Denver
+153|America/Los_Angeles
+1540|America/New_York
+15412|America/Los_Angeles
+154130|America/Los_Angeles
+154131|America/Los_Angeles
+154132|America/Los_Angeles
+154133|America/Los_Angeles
+154134|America/Los_Angeles
+154135|America/Los_Angeles
+154136|America/Los_Angeles
+154137|America/Denver
+154138|America/Los_Angeles
+154139|America/Los_Angeles
+154140|America/Los_Angeles
+154141|America/Los_Angeles
+154142|America/Los_Angeles
+154143|America/Los_Angeles
+154144|America/Los_Angeles
+154145|America/Los_Angeles
+154146|America/Los_Angeles
+1541471|America/Los_Angeles
+1541472|America/Los_Angeles
+1541473|America/Denver
+1541474|America/Los_Angeles
+1541475|America/Los_Angeles
+1541476|America/Los_Angeles
+1541479|America/Los_Angeles
+154148|America/Los_Angeles
+154149|America/Los_Angeles
+15415|America/Los_Angeles
+15416|America/Los_Angeles
+15417|America/Los_Angeles
+154181|America/Los_Angeles
+154182|America/Los_Angeles
+154183|America/Los_Angeles
+154184|America/Los_Angeles
+154185|America/Los_Angeles
+154186|America/Los_Angeles
+154187|America/Los_Angeles
+1541881|America/Denver
+1541882|America/Los_Angeles
+1541883|America/Los_Angeles
+1541884|America/Los_Angeles
+1541885|America/Los_Angeles
+1541888|America/Los_Angeles
+1541889|America/Denver
+154189|America/Los_Angeles
+15419|America/Los_Angeles
+155|America/Los_Angeles
+1561|America/New_York
+1562|America/Los_Angeles
+1563|America/Chicago
+1570|America/New_York
+1571|America/New_York
+1573|America/Chicago
+15742|America/New_York
+15743|America/New_York
+15744|America/New_York
+15745|America/New_York
+15746|America/New_York
+157472|America/New_York
+157475|America/New_York
+1574772|America/Chicago
+1574773|America/New_York
+157478|America/New_York
+157482|America/New_York
+157483|America/New_York
+157484|America/New_York
+157485|America/New_York
+157486|America/New_York
+157487|America/New_York
+1574892|America/New_York
+1574893|America/New_York
+1574896|America/Chicago
+15749|America/New_York
+1575|America/Denver
+1580|America/Chicago
+1585|America/New_York
+1586|America/New_York
+1601|America/Chicago
+1602|America/Denver
+1603|America/New_York
+1604|America/Vancouver
+160521|America/Chicago
+1605223|America/Denver
+1605224|America/Chicago
+1605225|America/Chicago
+1605226|America/Chicago
+1605229|America/Chicago
+160523|America/Chicago
+160525|America/Chicago
+160526|America/Chicago
+1605271|America/Chicago
+1605274|America/Chicago
+1605275|America/Chicago
+1605279|America/Denver
+160529|America/Chicago
+160532|America/Chicago
+160533|America/Chicago
+1605341|America/Denver
+1605342|America/Denver
+1605343|America/Denver
+1605345|America/Chicago
+1605347|America/Denver
+1605348|America/Denver
+1605352|America/Chicago
+1605353|America/Chicago
+1605355|America/Denver
+1605356|America/Chicago
+1605357|America/Chicago
+160536|America/Chicago
+1605371|America/Chicago
+1605373|America/Chicago
+1605374|America/Denver
+1605384|America/Chicago
+1605387|America/Chicago
+1605388|America/Denver
+1605393|America/Denver
+1605394|America/Denver
+1605397|America/Chicago
+1605399|America/Denver
+160542|America/Chicago
+160543|America/Chicago
+160544|America/Chicago
+160545|America/Denver
+160547|America/Chicago
+160548|America/Chicago
+160549|America/Chicago
+160552|America/Chicago
+160553|America/Chicago
+160554|America/Chicago
+160557|America/Denver
+1605582|America/Chicago
+1605584|America/Denver
+1605589|America/Chicago
+160559|America/Chicago
+160562|America/Chicago
+1605642|America/Denver
+1605644|America/Denver
+1605647|America/Chicago
+1605649|America/Chicago
+160566|America/Chicago
+160567|America/Denver
+160568|America/Denver
+160569|America/Chicago
+160571|America/Denver
+1605720|America/Denver
+1605721|America/Denver
+1605722|America/Denver
+1605723|America/Denver
+1605724|America/Chicago
+1605725|America/Chicago
+160573|America/Chicago
+1605745|America/Denver
+1605747|America/Chicago
+160575|America/Chicago
+160576|America/Chicago
+160577|America/Chicago
+160578|America/Denver
+1605791|America/Denver
+1605796|America/Chicago
+160582|America/Denver
+1605835|America/Chicago
+1605837|America/Denver
+160584|America/Chicago
+1605852|America/Chicago
+1605853|America/Chicago
+1605854|America/Chicago
+1605856|America/Chicago
+1605859|America/Denver
+160586|America/Denver
+160587|America/Chicago
+160588|America/Chicago
+160589|America/Denver
+1605923|America/Denver
+1605925|America/Chicago
+1605928|America/Chicago
+160594|America/Chicago
+160596|America/Denver
+160597|America/Chicago
+160598|America/Chicago
+160599|America/Chicago
+16062|America/New_York
+160632|America/New_York
+160633|America/New_York
+160634|America/New_York
+160635|America/New_York
+160636|America/New_York
+160637|America/New_York
+160638|America/Chicago
+16064|America/New_York
+16065|America/New_York
+16066|America/New_York
+16067|America/New_York
+16068|America/New_York
+16069|America/New_York
+1607|America/New_York
+1608|America/Chicago
+1609|America/New_York
+1610|America/New_York
+1612|America/Chicago
+1613|America/Toronto
+1614|America/New_York
+1615|America/Chicago
+1616|America/New_York
+1617|America/New_York
+1618|America/Chicago
+1619|America/Los_Angeles
+16202|America/Chicago
+162032|America/Chicago
+162033|America/Chicago
+162034|America/Chicago
+162035|America/Chicago
+162036|America/Chicago
+1620375|America/Chicago
+1620376|America/Denver
+1620378|America/Chicago
+1620382|America/Chicago
+1620384|America/Denver
+162039|America/Chicago
+16204|America/Chicago
+16205|America/Chicago
+16206|America/Chicago
+16207|America/Chicago
+16208|America/Chicago
+16209|America/Chicago
+1623|America/Denver
+1626|America/Los_Angeles
+1630|America/Chicago
+1631|America/New_York
+1636|America/Chicago
+1641|America/Chicago
+1646|America/New_York
+1647|America/Toronto
+1649|America/Grand_Turk
+1650|America/Los_Angeles
+1651|America/Chicago
+1660|America/Chicago
+1661|America/Los_Angeles
+1662|America/Chicago
+1664|America/Montserrat
+1670|Pacific/Saipan
+1671|Pacific/Guam
+1678|America/New_York
+1682|America/Chicago
+1684|Pacific/Pago_Pago
+1701221|America/Chicago
+1701222|America/Chicago
+1701223|America/Chicago
+1701224|America/Chicago
+1701225|America/Denver
+1701227|America/Denver
+1701228|America/Chicago
+170123|America/Chicago
+170124|America/Chicago
+170125|America/Chicago
+170126|America/Chicago
+170127|America/Chicago
+170128|America/Chicago
+170129|America/Chicago
+17013|America/Chicago
+170140|America/Chicago
+170143|America/Chicago
+170144|America/Chicago
+1701452|America/Chicago
+1701454|America/Chicago
+1701456|America/Denver
+170146|America/Chicago
+170147|America/Chicago
+170148|America/Denver
+170149|America/Chicago
+170152|America/Denver
+170153|America/Chicago
+170154|America/Chicago
+170156|America/Denver
+1701572|America/Chicago
+1701575|America/Denver
+1701577|America/Chicago
+170158|America/Denver
+17016|America/Chicago
+170172|America/Chicago
+170173|America/Chicago
+1701742|America/Chicago
+1701746|America/Chicago
+1701748|America/Denver
+170175|America/Chicago
+1701764|America/Denver
+1701766|America/Chicago
+170177|America/Chicago
+170178|America/Chicago
+170179|America/Chicago
+170183|America/Chicago
+1701842|America/Denver
+1701843|America/Chicago
+1701845|America/Chicago
+1701852|America/Chicago
+1701854|America/Denver
+1701857|America/Chicago
+1701858|America/Chicago
+170187|America/Denver
+170188|America/Chicago
+17019|America/Chicago
+1702|America/Los_Angeles
+1703|America/New_York
+1704|America/New_York
+1705|America/Toronto
+1706|America/New_York
+1707|America/Los_Angeles
+1708|America/Chicago
+170922|America/St_Johns
+170923|America/St_Johns
+170925|America/St_Johns
+170927|America/St_Johns
+170928|America/Halifax
+17093|America/St_Johns
+17094|America/St_Johns
+17095|America/St_Johns
+17096|America/St_Johns
+17097|America/St_Johns
+170983|America/St_Johns
+1709895|America/St_Johns
+1709896|America/Halifax
+17099|America/Halifax
+1712|America/Chicago
+1713|America/Chicago
+1714|America/Los_Angeles
+1715|America/Chicago
+1716|America/New_York
+1717|America/New_York
+1718|America/New_York
+1719|America/Denver
+1720|America/Denver
+1721|America/Lower_Princes
+1724|America/New_York
+1727|America/New_York
+1731|America/Chicago
+1732|America/New_York
+1734|America/New_York
+174|America/New_York
+1754|America/New_York
+1757|America/New_York
+1758|America/St_Lucia
+1760|America/Los_Angeles
+1763|America/Chicago
+1765|America/New_York
+1767|America/Dominica
+1769|America/Chicago
+1770|America/New_York
+1772|America/New_York
+1773|America/Chicago
+1774|America/New_York
+1775|America/Los_Angeles
+1778|America/Vancouver
+1779|America/Chicago
+1780|America/Edmonton
+1781|America/New_York
+1784|America/St_Vincent
+17852|America/Chicago
+17853|America/Chicago
+17854|America/Chicago
+17855|America/Chicago
+17856|America/Chicago
+17857|America/Chicago
+178582|America/Chicago
+178583|America/Chicago
+178584|America/Chicago
+1785852|America/Denver
+1785856|America/Chicago
+178586|America/Chicago
+178587|America/Chicago
+178588|America/Chicago
+178589|America/Denver
+17859|America/Chicago
+1786|America/New_York
+1787|America/Puerto_Rico
+1801|America/Denver
+1802|America/New_York
+1803|America/New_York
+1804|America/New_York
+1805|America/Los_Angeles
+1806|America/Chicago
+1807223|America/Winnipeg
+1807229|America/Toronto
+180727|America/Winnipeg
+180728|America/Toronto
+18073|America/Toronto
+180746|America/Winnipeg
+180747|America/Toronto
+180748|America/Winnipeg
+180754|America/Winnipeg
+180757|America/Toronto
+180759|America/Toronto
+18076|America/Toronto
+180772|America/Winnipeg
+180773|America/Winnipeg
+180776|America/Toronto
+18078|America/Toronto
+1807934|America/Winnipeg
+1807937|America/Winnipeg
+1807939|America/Toronto
+1808|Pacific/Honolulu
+1809|America/Halifax
+1810|America/New_York
+18122|America/New_York
+181231|America/New_York
+181232|America/New_York
+181233|America/New_York
+181234|America/New_York
+181235|America/New_York
+181236|America/New_York
+181237|America/New_York
+1812384|America/New_York
+1812385|America/Chicago
+1812386|America/Chicago
+181240|America/Chicago
+1812421|America/Chicago
+1812422|America/Chicago
+1812423|America/Chicago
+1812424|America/Chicago
+1812425|America/Chicago
+1812426|America/Chicago
+1812427|America/New_York
+1812428|America/Chicago
+1812432|America/New_York
+1812435|America/Chicago
+1812437|America/Chicago
+1812438|America/New_York
+181244|America/New_York
+181245|America/Chicago
+1812462|America/New_York
+1812464|America/Chicago
+1812466|America/New_York
+1812471|America/Chicago
+1812473|America/Chicago
+1812474|America/Chicago
+1812475|America/Chicago
+1812476|America/Chicago
+1812477|America/Chicago
+1812478|America/New_York
+1812479|America/Chicago
+1812481|America/New_York
+1812482|America/New_York
+1812485|America/Chicago
+1812486|America/New_York
+181249|America/Chicago
+181252|America/New_York
+181253|America/New_York
+1812546|America/New_York
+1812547|America/Chicago
+181259|America/New_York
+181262|America/New_York
+181263|America/New_York
+181264|America/Chicago
+181265|America/New_York
+181266|America/New_York
+1812682|America/Chicago
+1812683|America/New_York
+1812689|America/New_York
+181272|America/New_York
+181273|America/New_York
+181274|America/Chicago
+1812752|America/New_York
+1812753|America/Chicago
+181276|America/Chicago
+181279|America/New_York
+181282|America/New_York
+181283|America/Chicago
+1812842|America/Chicago
+1812847|America/New_York
+1812849|America/New_York
+1812853|America/Chicago
+1812855|America/New_York
+1812858|America/Chicago
+1812865|America/New_York
+1812866|America/New_York
+1812867|America/Chicago
+1812874|America/Chicago
+1812875|America/New_York
+1812876|America/New_York
+1812877|America/New_York
+181288|America/New_York
+181289|America/Chicago
+181291|America/New_York
+1812923|America/New_York
+1812925|America/Chicago
+1812926|America/New_York
+1812932|America/New_York
+1812933|America/New_York
+1812934|America/New_York
+1812936|America/New_York
+1812937|America/Chicago
+1812939|America/New_York
+181294|America/New_York
+181295|America/New_York
+1812963|America/Chicago
+1812967|America/New_York
+1812985|America/Chicago
+1812988|America/New_York
+1813|America/New_York
+1814|America/New_York
+1815|America/Chicago
+1816|America/Chicago
+1817|America/Chicago
+1818|America/Los_Angeles
+1819|America/Toronto
+1828|America/New_York
+1829|America/Halifax
+1830|America/Chicago
+1831|America/Los_Angeles
+1832|America/Chicago
+1843|America/New_York
+1845|America/New_York
+1847|America/Chicago
+1849|America/Halifax
+1850210|America/New_York
+1850215|America/Chicago
+1850216|America/New_York
+1850219|America/New_York
+1850222|America/New_York
+1850223|America/New_York
+1850224|America/New_York
+1850226|America/Chicago
+1850227|America/Chicago
+1850229|America/Chicago
+185023|America/Chicago
+185024|America/Chicago
+1850251|America/New_York
+1850256|America/Chicago
+1850258|America/Chicago
+1850259|America/Chicago
+185026|America/Chicago
+185027|America/Chicago
+185028|America/Chicago
+185029|America/New_York
+1850301|America/Chicago
+1850309|America/New_York
+185031|America/Chicago
+1850325|America/New_York
+1850327|America/Chicago
+1850329|America/New_York
+185033|America/Chicago
+185036|America/Chicago
+185038|America/New_York
+1850391|America/New_York
+1850398|America/Chicago
+185040|America/New_York
+185041|America/Chicago
+1850421|America/New_York
+1850422|America/New_York
+1850423|America/Chicago
+1850424|America/Chicago
+1850425|America/New_York
+1850429|America/Chicago
+1850431|America/New_York
+1850432|America/Chicago
+1850433|America/Chicago
+1850434|America/Chicago
+1850435|America/Chicago
+1850436|America/Chicago
+1850437|America/Chicago
+1850438|America/Chicago
+1850439|America/Chicago
+185044|America/Chicago
+185045|America/Chicago
+185046|America/Chicago
+185047|America/Chicago
+1850481|America/Chicago
+1850482|America/Chicago
+1850484|America/Chicago
+1850488|America/New_York
+185049|America/Chicago
+1850505|America/Chicago
+1850508|America/New_York
+185051|America/New_York
+1850522|America/Chicago
+1850523|America/New_York
+1850526|America/Chicago
+1850527|America/Chicago
+1850535|America/Chicago
+1850537|America/Chicago
+1850539|America/New_York
+1850545|America/New_York
+1850547|America/Chicago
+185056|America/New_York
+185057|America/New_York
+1850580|America/New_York
+1850581|America/Chicago
+1850584|America/New_York
+1850585|America/Chicago
+1850587|America/Chicago
+1850588|America/Chicago
+1850592|America/Chicago
+1850593|America/Chicago
+1850595|America/Chicago
+1850597|America/New_York
+185060|America/Chicago
+1850622|America/Chicago
+1850623|America/Chicago
+1850626|America/Chicago
+1850627|America/New_York
+185063|America/Chicago
+1850640|America/Chicago
+1850643|America/New_York
+1850644|America/New_York
+1850650|America/Chicago
+1850651|America/Chicago
+1850653|America/New_York
+1850654|America/Chicago
+1850656|America/New_York
+1850663|America/New_York
+1850664|America/Chicago
+1850668|America/New_York
+1850670|America/New_York
+1850671|America/New_York
+1850674|America/Chicago
+1850675|America/Chicago
+1850678|America/Chicago
+1850681|America/New_York
+1850682|America/Chicago
+1850683|America/Chicago
+1850685|America/Chicago
+1850689|America/Chicago
+1850696|America/Chicago
+1850697|America/New_York
+1850722|America/Chicago
+1850727|America/New_York
+1850729|America/Chicago
+185074|America/Chicago
+185076|America/Chicago
+185078|America/Chicago
+185079|America/Chicago
+1850833|America/Chicago
+1850835|America/Chicago
+1850837|America/Chicago
+1850838|America/New_York
+185085|America/Chicago
+185086|America/Chicago
+1850871|America/Chicago
+1850872|America/Chicago
+1850874|America/Chicago
+1850875|America/New_York
+1850877|America/New_York
+1850878|America/New_York
+185088|America/Chicago
+1850891|America/New_York
+1850892|America/Chicago
+1850893|America/New_York
+1850894|America/New_York
+1850897|America/Chicago
+185091|America/Chicago
+185092|America/New_York
+185093|America/Chicago
+1850941|America/Chicago
+1850942|America/New_York
+1850944|America/Chicago
+185095|America/Chicago
+185096|America/Chicago
+185097|America/New_York
+185098|America/Chicago
+1850994|America/Chicago
+1850995|America/Chicago
+1850997|America/New_York
+1856|America/New_York
+1857|America/New_York
+1858|America/Los_Angeles
+1859|America/New_York
+1860|America/New_York
+1862|America/New_York
+1863|America/New_York
+1864|America/New_York
+1865|America/New_York
+18673|America/Vancouver
+18674|America/Vancouver
+18675|America/Vancouver
+186763|America/Vancouver
+186764|America/Winnipeg
+1867667|America/Vancouver
+1867668|America/Vancouver
+1867669|America/Edmonton
+186769|America/Edmonton
+18677|America/Edmonton
+18678|America/Edmonton
+186792|America/Edmonton
+186797|America/Toronto
+186799|America/Vancouver
+1868|America/Port_of_Spain
+1869|America/St_Kitts
+1870|America/Chicago
+1876|America/Jamaica
+1901|America/Chicago
+1902|America/Halifax
+1903|America/Chicago
+1904|America/New_York
+1905|America/Toronto
+190622|America/New_York
+190623|America/New_York
+190624|America/New_York
+190625|America/New_York
+190626|America/Chicago
+190629|America/New_York
+19063|America/New_York
+19064|America/New_York
+190652|America/New_York
+190656|America/Chicago
+190658|America/New_York
+19066|America/New_York
+190675|America/Chicago
+190677|America/Chicago
+190678|America/New_York
+190684|America/New_York
+190686|America/Chicago
+190687|America/Chicago
+190688|America/New_York
+19069|America/Chicago
+1907|America/Juneau
+1908|America/New_York
+1909|America/Los_Angeles
+1910|America/New_York
+1912|America/New_York
+1913|America/Chicago
+1914|America/New_York
+1915|America/Denver
+1916|America/Los_Angeles
+1917|America/New_York
+1918|America/Chicago
+1919|America/New_York
+1920|America/Chicago
+1925|America/Los_Angeles
+1928|America/Denver
+1931|America/Chicago
+1936|America/Chicago
+1937|America/New_York
+1939|America/Puerto_Rico
+1940|America/Chicago
+1941|America/New_York
+1949|America/Los_Angeles
+1951|America/Los_Angeles
+1952|America/Chicago
+1954|America/New_York
+1956|America/Chicago
+1970|America/Denver
+1971|America/Los_Angeles
+1972|America/Chicago
+1973|America/New_York
+1978|America/New_York
+1979|America/Chicago
+1980|America/New_York
+1985|America/Chicago
+1989|America/New_York
+20|Africa/Cairo
+211|Africa/Nairobi
+212|Atlantic/Canary
+213|Europe/Paris
+216|Africa/Tunis
+218|Europe/Paris
+220|Africa/Banjul
+221|Africa/Dakar
+222|Africa/Nouakchott
+223|Africa/Bamako
+224|Africa/Conakry
+225|Africa/Abidjan
+226|Africa/Ouagadougou
+227|Africa/Niamey
+228|Africa/Lome
+229|Africa/Porto-Novo
+230|Indian/Mauritius
+231|Atlantic/Reykjavik
+232|Africa/Freetown
+233|Africa/Accra
+234|Africa/Lagos
+235|Africa/Ndjamena
+236|Africa/Bangui
+237|Africa/Douala
+238|Atlantic/Cape_Verde
+239|Africa/Sao_Tome
+240|Africa/Malabo
+241|Africa/Libreville
+242|Africa/Brazzaville
+243|Africa/Kinshasa&Africa/Lubumbashi
+2431|Africa/Kinshasa
+2435|Africa/Kinshasa
+244|Africa/Luanda
+245|Atlantic/Reykjavik
+246|Indian/Chagos
+247|Atlantic/St_Helena
+248|Indian/Mahe
+249|Africa/Nairobi
+250|Africa/Kigali
+251|Africa/Addis_Ababa
+252|Africa/Mogadishu
+253|Africa/Djibouti
+254|Africa/Nairobi
+255|Africa/Dar_es_Salaam
+256|Africa/Kampala
+257|Africa/Bujumbura
+258|Africa/Maputo
+260|Africa/Lusaka
+261|Indian/Antananarivo
+262|Indian/Mayotte&Indian/Reunion
+262262|Indian/Reunion
+262269|Indian/Mayotte
+263|Africa/Harare
+264|Africa/Lagos
+265|Africa/Blantyre
+266|Africa/Maseru
+267|Africa/Gaborone
+268|Africa/Mbabane
+269|Indian/Comoro
+27|Africa/Johannesburg
+290|Atlantic/St_Helena
+291|Africa/Asmera
+297|America/Aruba
+298|Atlantic/Faeroe
+299|America/Godthab&America/Scoresbysund&America/Thule&Atlantic/Reykjavik
+2993|America/Godthab
+2998|America/Godthab
+2999|America/Godthab
+30|Europe/Athens
+31|Europe/Amsterdam
+32|Europe/Brussels
+33|Europe/Paris
+34|Atlantic/Canary&Europe/Madrid
+3481|Europe/Madrid
+34821|Europe/Madrid
+34822|Atlantic/Canary
+34823|Europe/Madrid
+34824|Europe/Madrid
+34825|Europe/Madrid
+34826|Europe/Madrid
+34827|Europe/Madrid
+34828|Atlantic/Canary
+3483|Europe/Madrid
+3484|Europe/Madrid
+3485|Europe/Madrid
+34860|Europe/Madrid
+34865|Europe/Madrid
+34868|Europe/Madrid
+34869|Atlantic/Canary&Europe/Madrid
+3487|Europe/Madrid
+3488|Europe/Madrid
+3491|Europe/Madrid
+34920|Europe/Madrid
+34921|Europe/Madrid
+34922|Atlantic/Canary
+34923|Europe/Madrid
+34924|Europe/Madrid
+34925|Europe/Madrid
+34926|Europe/Madrid
+34927|Europe/Madrid
+34928|Atlantic/Canary
+3493|Europe/Madrid
+3494|Europe/Madrid
+3495|Europe/Madrid
+3496|Europe/Madrid
+3497|Europe/Madrid
+3498|Europe/Madrid
+350|Europe/Gibraltar
+351|Atlantic/Azores&Atlantic/Canary
+35121|Atlantic/Canary
+35122|Atlantic/Canary
+35123|Atlantic/Canary
+35124|Atlantic/Canary
+35125|Atlantic/Canary
+35126|Atlantic/Canary
+35127|Atlantic/Canary
+35128|Atlantic/Canary
+351291|Atlantic/Canary
+351292|Atlantic/Azores
+351295|Atlantic/Azores
+351296|Atlantic/Azores
+352|Europe/Luxembourg
+353|Europe/Dublin
+354|Atlantic/Reykjavik
+355|Europe/Tirane
+356|Europe/Malta
+357|Asia/Nicosia
+358|Europe/Helsinki&Europe/Mariehamn
+35813|Europe/Helsinki
+35814|Europe/Helsinki
+35815|Europe/Helsinki
+35816|Europe/Helsinki
+35817|Europe/Helsinki
+35818|Europe/Mariehamn
+35819|Europe/Helsinki
+3582|Europe/Helsinki
+3583|Europe/Helsinki
+3585|Europe/Helsinki
+3586|Europe/Helsinki
+3588|Europe/Helsinki
+3589|Europe/Helsinki
+359|Europe/Sofia
+36|Europe/Budapest
+370|Europe/Bucharest
+371|Europe/Bucharest
+372|Europe/Bucharest
+373|Europe/Bucharest
+374|Asia/Yerevan
+375|Europe/Belarus
+376|Europe/Andorra
+377|Europe/Monaco
+378|Europe/San_Marino
+379|Europe/Vatican
+380|Europe/Bucharest
+381|Europe/Belgrade
+382|Europe/Podgorica
+385|Europe/Zagreb
+386|Europe/Ljubljana
+387|Europe/Sarajevo
+389|Europe/Skopje
+39|Europe/Rome
+40|Europe/Bucharest
+41|Europe/Zurich
+420|Europe/Prague
+421|Europe/Bratislava
+423|Europe/Vaduz
+43|Europe/Vienna
+44|Atlantic/Reykjavik&Europe/London
+4411|Europe/London
+4412|Europe/London
+44130|Europe/London
+44131|Europe/London
+44132|Europe/London
+44133|Europe/London
+44134|Europe/London
+44135|Europe/London
+44136|Europe/London
+44137|Europe/London
+441380|Europe/London
+441381|Europe/London
+441382|Europe/London
+441383|Europe/London
+441384|Europe/London
+441386|Europe/London
+441387|Europe/London
+4413873|Europe/London
+441388|Europe/London
+441389|Europe/London
+44139|Europe/London
+44140|Europe/London
+44141|Europe/London
+44142|Europe/London
+44143|Europe/London
+44144|Europe/London
+44145|Europe/London
+44146|Europe/London
+44147|Europe/London
+441480|Europe/London
+441481|Atlantic/Reykjavik
+441482|Europe/London
+441483|Europe/London
+441484|Europe/London
+441485|Europe/London
+441487|Europe/London
+441488|Europe/London
+441489|Europe/London
+44149|Europe/London
+44150|Europe/London
+44151|Europe/London
+441520|Europe/London
+441522|Europe/London
+441524|Europe/London
+4415242|Europe/London
+441525|Europe/London
+441526|Europe/London
+441527|Europe/London
+441528|Europe/London
+441529|Europe/London
+441530|Europe/London
+441531|Europe/London
+441534|Atlantic/Reykjavik
+441535|Europe/London
+441536|Europe/London
+441538|Europe/London
+441539|Europe/London
+4415394|Europe/London
+4415395|Europe/London
+4415396|Europe/London
+44154|Europe/London
+44155|Europe/London
+44156|Europe/London
+44157|Europe/London
+44158|Europe/London
+44159|Europe/London
+44160|Europe/London
+44161|Europe/London
+441620|Europe/London
+441621|Europe/London
+441622|Europe/London
+441623|Europe/London
+441624|Atlantic/Reykjavik
+441625|Europe/London
+441626|Europe/London
+441628|Europe/London
+441629|Europe/London
+44163|Europe/London
+44164|Europe/London
+44165|Europe/London
+44166|Europe/London
+44167|Europe/London
+44168|Europe/London
+441690|Europe/London
+441691|Europe/London
+441692|Europe/London
+441694|Europe/London
+441695|Europe/London
+441697|Europe/London
+4416973|Europe/London
+4416974|Europe/London
+4416977|Europe/London
+441698|Europe/London
+44170|Europe/London
+44172|Europe/London
+44173|Europe/London
+44174|Europe/London
+44175|Europe/London
+441760|Europe/London
+441761|Europe/London
+441763|Europe/London
+441764|Europe/London
+441765|Europe/London
+441766|Europe/London
+441767|Europe/London
+441768|Europe/London
+4417683|Europe/London
+4417684|Europe/London
+4417687|Europe/London
+441769|Europe/London
+44177|Europe/London
+44178|Europe/London
+44179|Europe/London
+4418|Europe/London
+44190|Europe/London
+44191|Europe/London
+44192|Europe/London
+44193|Europe/London
+441942|Europe/London
+441943|Europe/London
+441944|Europe/London
+441945|Europe/London
+441946|Europe/London
+4419467|Europe/London
+441947|Europe/London
+441948|Europe/London
+441949|Europe/London
+44195|Europe/London
+44196|Europe/London
+44197|Europe/London
+44198|Europe/London
+44199|Europe/London
+442|Europe/London
+45|Europe/Copenhagen
+46|Europe/Stockholm
+47|Europe/Oslo&Europe/Paris
+472|Europe/Oslo
+473|Europe/Oslo
+475|Europe/Oslo
+476|Europe/Oslo
+4770|Europe/Oslo
+4771|Europe/Oslo
+4772|Europe/Oslo
+4773|Europe/Oslo
+4774|Europe/Oslo
+4775|Europe/Oslo
+4776|Europe/Oslo
+4777|Europe/Oslo
+4778|Europe/Oslo
+4779|Europe/Paris
+48|Europe/Warsaw
+49|Europe/Berlin
+500|Atlantic/Stanley
+501|America/Belize
+502|America/Guatemala
+503|America/El_Salvador
+504|America/Tegucigalpa
+505|America/Chicago
+506|America/Costa_Rica
+507|America/Panama
+508|America/Miquelon
+509|America/Port-au-Prince
+51|America/Lima
+52|America/Hermosillo&America/Mexico_City&America/Tijuana
+522|America/Mexico_City
+52311|America/Hermosillo
+52312|America/Mexico_City
+52313|America/Mexico_City
+52314|America/Mexico_City
+52315|America/Mexico_City
+52316|America/Mexico_City
+52317|America/Mexico_City
+52319|America/Hermosillo
+52321|America/Mexico_City
+52322|America/Mexico_City
+52323|America/Hermosillo
+52324|America/Hermosillo
+52325|America/Hermosillo
+52326|America/Mexico_City
+52327|America/Hermosillo
+52328|America/Mexico_City
+52329|America/Hermosillo&America/Mexico_City
+5233|America/Mexico_City
+5234|America/Mexico_City
+5235|America/Mexico_City
+5237|America/Mexico_City
+52381|America/Mexico_City
+52382|America/Mexico_City
+52383|America/Mexico_City
+52384|America/Mexico_City
+52385|America/Mexico_City
+52386|America/Mexico_City
+52387|America/Mexico_City
+52388|America/Mexico_City
+52389|America/Hermosillo
+5239|America/Mexico_City
+524|America/Mexico_City
+525|America/Mexico_City
+52612|America/Hermosillo
+52613|America/Hermosillo
+52614|America/Hermosillo
+52615|America/Hermosillo
+52616|America/Hermosillo&America/Tijuana
+52618|America/Mexico_City
+5262|America/Hermosillo
+5263|America/Hermosillo
+52641|America/Hermosillo
+52642|America/Hermosillo
+52643|America/Hermosillo
+52644|America/Hermosillo
+52645|America/Hermosillo
+52646|America/Tijuana
+52647|America/Hermosillo
+52648|America/Hermosillo
+52649|America/Hermosillo&America/Mexico_City
+52651|America/Hermosillo
+52652|America/Hermosillo
+52653|America/Hermosillo
+52656|America/Hermosillo
+52658|America/Tijuana
+52659|America/Hermosillo
+52661|America/Tijuana
+52662|America/Hermosillo
+52664|America/Tijuana
+52665|America/Tijuana
+52667|America/Hermosillo
+52668|America/Hermosillo
+52669|America/Hermosillo
+52671|America/Mexico_City
+52672|America/Hermosillo
+52673|America/Hermosillo
+52674|America/Mexico_City
+52675|America/Mexico_City
+52676|America/Mexico_City
+52677|America/Mexico_City
+52686|America/Tijuana
+52687|America/Hermosillo
+5269|America/Hermosillo
+527|America/Mexico_City
+528|America/Mexico_City
+529|America/Mexico_City
+53|America/Havana
+54|America/Buenos_Aires
+55|America/Manaus&America/Noronha&America/Sao_Paulo
+551|America/Sao_Paulo
+552|America/Sao_Paulo
+553|America/Sao_Paulo
+554|America/Sao_Paulo
+555|America/Sao_Paulo
+5561|America/Sao_Paulo
+5562|America/Sao_Paulo
+5563|America/Sao_Paulo
+5564|America/Sao_Paulo
+5565|America/Manaus
+5566|America/Manaus
+5567|America/Manaus
+5568|America/Manaus
+5569|America/Manaus
+557|America/Sao_Paulo
+558|America/Sao_Paulo
+5591|America/Manaus
+5592|America/Manaus
+5593|America/Manaus
+5594|America/Manaus
+5595|America/Manaus
+5596|America/Sao_Paulo
+5597|America/Manaus
+5598|America/Sao_Paulo
+5599|America/Sao_Paulo
+56|America/Santiago&Pacific/Easter
+562|America/Santiago
+563|America/Santiago
+564|America/Santiago
+565|America/Santiago
+566|America/Santiago
+567|America/Santiago
+57|America/Bogota
+58|America/Caracas
+590|America/Guadeloupe&America/Halifax&America/Marigot
+591|America/La_Paz
+592|America/Guyana
+593|America/Guayaquil&Pacific/Galapagos
+5932|America/Guayaquil
+5933|America/Guayaquil
+5934|America/Guayaquil
+5935|America/Guayaquil&Pacific/Galapagos
+5936|America/Guayaquil
+5937|America/Guayaquil
+594|America/Cayenne
+595|America/Asuncion
+596|America/Martinique
+597|America/Paramaribo
+598|America/Montevideo
+599|America/Curacao&America/Kralendijk
+5993|America/Kralendijk
+5994|America/Kralendijk
+5997|America/Kralendijk
+5999|America/Curacao
+60|Asia/Kuching
+61|Antarctica/Macquarie&Australia/Adelaide&Australia/Eucla&Australia/Lord_Howe&Australia/Perth&Australia/Sydney&Indian/Christmas&Indian/Cocos
+612|Australia/Sydney
+613|Australia/Sydney
+617|Australia/Sydney
+618|Australia/Adelaide&Australia/Perth
+62|Asia/Jakarta&Asia/Jayapura&Asia/Makassar
+622|Asia/Jakarta
+6231|Asia/Jakarta
+62321|Asia/Jakarta
+62322|Asia/Jakarta
+62323|Asia/Jakarta
+62324|Asia/Jakarta
+62327|Asia/Jakarta&Asia/Makassar
+62328|Asia/Jakarta
+6233|Asia/Jakarta
+6234|Asia/Jakarta
+6235|Asia/Jakarta
+6236|Asia/Makassar
+62370|Asia/Makassar
+62371|Asia/Makassar
+62372|Asia/Makassar
+62373|Asia/Jakarta&Asia/Makassar
+62374|Asia/Jakarta&Asia/Makassar
+62376|Asia/Makassar
+62380|Asia/Makassar
+62381|Asia/Jakarta&Asia/Makassar
+62382|Asia/Makassar
+62384|Asia/Jakarta&Asia/Makassar
+62385|Asia/Makassar
+62386|Asia/Makassar
+62388|Asia/Jakarta&Asia/Makassar
+62389|Asia/Makassar
+62401|Asia/Makassar
+62402|Asia/Makassar
+62403|Asia/Makassar
+62405|Asia/Jakarta&Asia/Makassar
+62408|Asia/Makassar
+62411|Asia/Jakarta
+62413|Asia/Makassar
+62414|Asia/Jakarta&Asia/Makassar
+62417|Asia/Jakarta&Asia/Makassar
+62419|Asia/Jakarta
+62420|Asia/Jakarta
+62422|Asia/Makassar
+62423|Asia/Jakarta&Asia/Makassar
+62426|Asia/Makassar
+62428|Asia/Makassar
+6243|Asia/Makassar
+62451|Asia/Makassar
+62452|Asia/Makassar
+62453|Asia/Jakarta&Asia/Makassar
+62457|Asia/Makassar
+62458|Asia/Jakarta
+62461|Asia/Makassar
+62462|Asia/Jakarta
+62464|Asia/Makassar
+6247|Asia/Jakarta
+62481|Asia/Makassar
+62484|Asia/Makassar
+62485|Asia/Jakarta
+62511|Asia/Makassar
+62512|Asia/Makassar
+62513|Asia/Jakarta
+62516|Asia/Jakarta
+62517|Asia/Makassar
+62518|Asia/Jakarta&Asia/Makassar
+62519|Asia/Jakarta
+62525|Asia/Jakarta
+62526|Asia/Makassar
+62527|Asia/Makassar
+62528|Asia/Jakarta&Asia/Makassar
+6253|Asia/Jakarta
+6254|Asia/Makassar
+62551|Asia/Makassar
+62552|Asia/Jakarta
+62553|Asia/Makassar
+62554|Asia/Makassar
+62556|Asia/Jakarta&Asia/Makassar
+6256|Asia/Jakarta
+6261|Asia/Jakarta
+62620|Asia/Jakarta
+62621|Asia/Jakarta
+62622|Asia/Jakarta
+62623|Asia/Jakarta&Asia/Makassar
+62624|Asia/Jakarta
+62625|Asia/Jakarta
+62626|Asia/Jakarta
+62627|Asia/Jakarta
+62628|Asia/Jakarta
+62629|Asia/Jakarta
+6263|Asia/Jakarta
+62641|Asia/Jakarta
+62642|Asia/Jakarta&Asia/Makassar
+62643|Asia/Jakarta
+62644|Asia/Jakarta
+62645|Asia/Jakarta
+62646|Asia/Jakarta
+6265|Asia/Jakarta
+62711|Asia/Makassar
+62712|Asia/Makassar
+62713|Asia/Makassar
+62714|Asia/Makassar
+62716|Asia/Jakarta
+62717|Asia/Jakarta
+62718|Asia/Jakarta&Asia/Makassar
+62719|Asia/Jakarta
+6272|Asia/Jakarta
+62730|Asia/Makassar
+62731|Asia/Makassar
+62732|Asia/Jakarta
+62733|Asia/Makassar
+62734|Asia/Makassar
+62735|Asia/Makassar
+62736|Asia/Jakarta
+62737|Asia/Jakarta
+62738|Asia/Jakarta
+62739|Asia/Jakarta
+6274|Asia/Jakarta
+6275|Asia/Jakarta
+6276|Asia/Jakarta
+6277|Asia/Jakarta
+62901|Asia/Jayapura
+62902|Asia/Jakarta&Asia/Makassar
+62910|Asia/Jakarta
+62911|Asia/Jayapura
+62913|Asia/Jakarta&Asia/Jayapura
+62915|Asia/Jakarta&Asia/Makassar
+62916|Asia/Jakarta&Asia/Jayapura
+62917|Asia/Jakarta
+62918|Asia/Jakarta&Asia/Makassar
+62921|Asia/Jayapura
+62929|Asia/Jakarta
+62951|Asia/Jayapura
+62955|Asia/Jakarta&Asia/Jayapura
+62956|Asia/Jakarta&Asia/Jayapura
+62957|Asia/Jayapura
+62958|Asia/Jakarta
+62966|Asia/Jakarta
+62967|Asia/Jayapura
+6297|Asia/Jayapura
+62981|Asia/Jayapura
+62983|Asia/Jakarta
+62984|Asia/Jayapura
+62986|Asia/Jayapura
+63|Asia/Manila
+64|Pacific/Auckland&Pacific/Chatham
+643|Pacific/Auckland
+643305|Pacific/Chatham
+644|Pacific/Auckland
+646|Pacific/Auckland
+647|Pacific/Auckland
+649|Pacific/Auckland
+65|Asia/Singapore
+66|Asia/Bangkok
+670|Asia/Dili
+672|Pacific/Norfolk
+673|Asia/Brunei
+674|Pacific/Nauru
+675|Pacific/Port_Moresby
+676|Pacific/Tongatapu
+677|Pacific/Guadalcanal
+678|Pacific/Efate
+679|Pacific/Fiji
+680|Pacific/Palau
+681|Pacific/Wallis
+682|Pacific/Rarotonga
+683|Pacific/Niue
+685|Pacific/Apia
+686|Pacific/Enderbury&Pacific/Kiritimati&Pacific/Tarawa
+687|Pacific/Noumea
+688|Pacific/Funafuti
+689|Pacific/Gambier&Pacific/Marquesas&Pacific/Tahiti
+6894|Pacific/Tahiti
+6895|Pacific/Tahiti
+6896|Pacific/Tahiti
+6898|Pacific/Tahiti
+68990|Pacific/Tahiti
+68991|Pacific/Marquesas
+68992|Pacific/Marquesas
+68993|Pacific/Tahiti
+68994|Pacific/Tahiti
+68995|Pacific/Tahiti
+68996|Pacific/Tahiti
+68997|Pacific/Gambier
+68998|Pacific/Tahiti
+690|Pacific/Fakaofo
+691|Pacific/Kosrae&Pacific/Ponape&Pacific/Truk
+69132|Pacific/Ponape
+69133|Pacific/Truk
+69135|Pacific/Truk
+69137|Pacific/Kosrae
+6919|Pacific/Ponape
+692|Pacific/Majuro
+7|Asia/Almaty&Asia/Aqtobe&Asia/Irkutsk&Asia/Krasnoyarsk&Asia/Magadan&Asia/Novosibirsk&Asia/Omsk&Asia/Sakhalin&Asia/Vladivostok&Asia/Yakutsk&Asia/Yekaterinburg&Europe/Moscow&Europe/Volgograd
+7301|Asia/Irkutsk
+7302|Asia/Yakutsk
+7341|Europe/Moscow
+7342|Asia/Yekaterinburg
+7343|Asia/Yekaterinburg
+7345|Asia/Yekaterinburg
+7346|Asia/Yekaterinburg
+7347|Asia/Yekaterinburg
+7349|Asia/Yekaterinburg
+735|Asia/Yekaterinburg
+738|Asia/Omsk
+7390|Asia/Krasnoyarsk
+7391|Asia/Krasnoyarsk
+7394|Asia/Krasnoyarsk
+7395|Asia/Irkutsk
+7411|Asia/Yakutsk
+7413|Asia/Magadan
+7415|Asia/Magadan
+7416|Asia/Yakutsk
+7421|Asia/Vladivostok
+7423|Asia/Vladivostok
+7424|Asia/Vladivostok
+7426|Asia/Vladivostok
+7427|Asia/Magadan
+747|Europe/Moscow
+748|Europe/Moscow
+749|Europe/Moscow
+7710|Asia/Almaty
+7711|Asia/Aqtobe
+7712|Asia/Aqtobe
+7713|Asia/Aqtobe
+7714|Asia/Almaty
+7715|Asia/Almaty
+7716|Asia/Almaty
+7717|Asia/Almaty
+7718|Asia/Almaty
+7721|Asia/Almaty
+7722|Asia/Almaty
+7723|Asia/Almaty
+7724|Asia/Almaty
+7725|Asia/Almaty
+7726|Asia/Almaty
+7727|Asia/Almaty
+7728|Asia/Almaty
+7729|Asia/Aqtobe
+78|Europe/Moscow
+81|Asia/Tokyo
+82|Asia/Seoul
+84|Asia/Saigon
+850|Asia/Pyongyang
+852|Asia/Hong_Kong
+853|Asia/Shanghai
+855|Asia/Phnom_Penh
+856|Asia/Vientiane
+86|Asia/Shanghai
+880|Asia/Dhaka
+886|Asia/Taipei
+90|Europe/Bucharest
+91|Asia/Calcutta
+92|Asia/Karachi
+93|Asia/Kabul
+94|Asia/Colombo
+95|Asia/Rangoon
+960|Indian/Maldives
+961|Asia/Beirut
+962|Asia/Amman
+963|Asia/Damascus
+964|Asia/Baghdad
+965|Asia/Kuwait
+966|Asia/Riyadh
+967|Asia/Aden
+968|Asia/Muscat
+970|Europe/Bucharest
+971|Asia/Dubai
+972|Asia/Jerusalem
+973|Asia/Bahrain
+974|Asia/Qatar
+975|Asia/Thimphu
+976|Asia/Choibalsan&Asia/Hovd&Asia/Ulaanbaatar
+977|Asia/Katmandu
+98|Asia/Tehran
+992|Asia/Dushanbe
+993|Asia/Ashgabat
+994|Asia/Baku
+995|Asia/Tbilisi
+996|Asia/Bishkek
+998|Asia/Tashkent
index 5a039c8..061b7e8 100644 (file)
Binary files a/tools/java/cpp-build/target/cpp-build-1.0-SNAPSHOT-jar-with-dependencies.jar and b/tools/java/cpp-build/target/cpp-build-1.0-SNAPSHOT-jar-with-dependencies.jar differ
index f7eec1b..63b8f1b 100644 (file)
@@ -17,6 +17,7 @@
 package com.google.i18n.phonenumbers;
 
 import com.google.i18n.phonenumbers.buildtools.GeneratePhonePrefixDataEntryPoint;
+import com.google.i18n.phonenumbers.buildtools.GenerateTimeZonesMapDataEntryPoint;
 
 /**
  * Entry point class for Java and JavaScript build tools.
@@ -30,6 +31,7 @@ public class EntryPoint {
       new BuildMetadataJsonFromXml(),
       new BuildMetadataProtoFromXml(),
       new GeneratePhonePrefixDataEntryPoint(),
+      new GenerateTimeZonesMapDataEntryPoint(),
     }).start();
 
     System.exit(status ? 0 : 1);
diff --git a/tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapData.java b/tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapData.java
new file mode 100644 (file)
index 0000000..b07a187
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2012 The Libphonenumber Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.i18n.phonenumbers.buildtools;
+
+import com.google.i18n.phonenumbers.prefixmapper.PrefixTimeZonesMap;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A utility that generates the binary serialization of the prefix/time zones mappings from
+ * a human-readable text file.
+ *
+ * @author Walter Erquinigo
+ */
+public class GenerateTimeZonesMapData {
+  private final File inputTextFile;
+  private static final String MAPPING_DATA_FILE_NAME = "map_data";
+  // The IO Handler used to output the generated binary file.
+  private final AbstractPhonePrefixDataIOHandler ioHandler;
+
+  private static final Logger LOGGER = Logger.getLogger(GenerateTimeZonesMapData.class.getName());
+
+  public GenerateTimeZonesMapData(File inputTextFile, AbstractPhonePrefixDataIOHandler ioHandler)
+      throws IOException {
+    this.inputTextFile = inputTextFile;
+    if (!inputTextFile.isFile()) {
+      throw new IOException("The provided input text file does not exist.");
+    }
+    this.ioHandler = ioHandler;
+  }
+
+  /**
+   * Reads phone prefix data from the provided input stream and returns a SortedMap with the
+   * prefix to time zones mappings.
+   */
+  // @VisibleForTesting
+  static SortedMap<Integer, String> parseTextFile(InputStream input)
+      throws IOException, RuntimeException {
+    final SortedMap<Integer, String> timeZoneMap = new TreeMap<Integer, String>();
+    BufferedReader bufferedReader =
+        new BufferedReader(new InputStreamReader(
+            new BufferedInputStream(input), Charset.forName("UTF-8")));
+    int lineNumber = 1;
+
+    for (String line; (line = bufferedReader.readLine()) != null; lineNumber++) {
+      line = line.trim();
+      if (line.length() == 0 || line.startsWith("#")) {
+        continue;
+      }
+      int indexOfPipe = line.indexOf('|');
+      if (indexOfPipe == -1) {
+        throw new RuntimeException(String.format("line %d: malformatted data, expected '|'",
+                                                 lineNumber));
+      }
+      Integer prefix = Integer.parseInt(line.substring(0, indexOfPipe));
+      String timezones = line.substring(indexOfPipe + 1);
+      if (timezones.isEmpty()) {
+        throw new RuntimeException(String.format("line %d: missing time zones", lineNumber));
+      }
+      if (timeZoneMap.put(prefix, timezones) != null) {
+         throw new RuntimeException(String.format("duplicated prefix %d", prefix));
+      }
+    }
+    return timeZoneMap;
+  }
+
+  /**
+   * Writes the provided phone prefix/time zones map to the provided output stream.
+   *
+   * @throws IOException
+   */
+  // @VisibleForTesting
+  static void writeToBinaryFile(SortedMap<Integer, String> sortedMap, OutputStream output)
+      throws IOException {
+    // Build the corresponding PrefixTimeZonesMap and serialize it to the binary format.
+    PrefixTimeZonesMap prefixTimeZonesMap = new PrefixTimeZonesMap();
+    prefixTimeZonesMap.readPrefixTimeZonesMap(sortedMap);
+    ObjectOutputStream objectOutputStream = new ObjectOutputStream(output);
+    prefixTimeZonesMap.writeExternal(objectOutputStream);
+    objectOutputStream.flush();
+  }
+
+  /**
+   * Runs the prefix to time zones map data generator.
+   *
+   * @throws IOException
+   */
+  public void run() throws IOException {
+    FileInputStream fileInputStream = null;
+    FileOutputStream fileOutputStream = null;
+    try {
+      fileInputStream = new FileInputStream(inputTextFile);
+      SortedMap<Integer, String> mappings = parseTextFile(fileInputStream);
+      File outputBinaryFile = ioHandler.createFile(MAPPING_DATA_FILE_NAME);
+      try {
+        fileOutputStream = new FileOutputStream(outputBinaryFile);
+        writeToBinaryFile(mappings, fileOutputStream);
+        ioHandler.addFileToOutput(outputBinaryFile);
+      } finally {
+        ioHandler.closeFile(fileOutputStream);
+      }
+    } catch (RuntimeException e) {
+      LOGGER.log(Level.SEVERE,
+                 "Error processing file " + inputTextFile.getAbsolutePath());
+      throw e;
+    } catch (IOException e) {
+      LOGGER.log(Level.SEVERE, e.getMessage());
+    } finally {
+      ioHandler.closeFile(fileInputStream);
+      ioHandler.closeFile(fileOutputStream);
+      ioHandler.close();
+    }
+    LOGGER.log(Level.INFO, "Time zone data successfully generated.");
+  }
+}
diff --git a/tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapDataEntryPoint.java b/tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapDataEntryPoint.java
new file mode 100644 (file)
index 0000000..355c9fa
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2012 The Libphonenumber Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.i18n.phonenumbers.buildtools;
+
+import com.google.i18n.phonenumbers.Command;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Entry point class used to invoke the generation of the binary time zone data files.
+ *
+ * @author Walter Erquinigo
+ * @author Philippe Liard
+ */
+public class GenerateTimeZonesMapDataEntryPoint extends Command {
+  private static final Logger LOGGER = Logger.getLogger(GenerateTimeZonesMapData.class.getName());
+
+  @Override
+  public String getCommandName() {
+    return "GenerateTimeZonesMapData";
+  }
+
+  @Override
+  public boolean start() {
+    String[] args = getArgs();
+
+    if (args.length != 3) {
+      LOGGER.log(Level.SEVERE,
+                 "usage: GenerateTimeZonesMapData /path/to/input/text_file " +
+                 "/path/to/output/directory");
+      return false;
+    }
+    try {
+      GenerateTimeZonesMapData generateTimeZonesMapData = new GenerateTimeZonesMapData(
+          new File(args[1]), new PhonePrefixDataIOHandler(new File(args[2])));
+      generateTimeZonesMapData.run();
+    } catch (IOException e) {
+      LOGGER.log(Level.SEVERE, e.getMessage());
+      return false;
+    }
+    return true;
+  }
+}
index 1e971e4..305b87d 100644 (file)
Binary files a/tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar and b/tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar differ
diff --git a/tools/java/java-build/test/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapDataTest.java b/tools/java/java-build/test/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapDataTest.java
new file mode 100644 (file)
index 0000000..4ca397c
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2012 The Libphonenumber Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.i18n.phonenumbers.buildtools;
+
+import com.google.i18n.phonenumbers.prefixmapper.PrefixTimeZonesMap;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.util.Map;
+import java.util.SortedMap;
+
+/**
+ * Unittests for GenerateTimeZonesMapData.java
+ *
+ * @author Walter Erquinigo
+ */
+public class GenerateTimeZonesMapDataTest extends TestCase {
+  private static final String BRUSSELS_TZ = "Europe/Brussels";
+  private static final String PARIS_TZ = "Europe/Paris";
+  private static final String PARIS_BRUSSELS_LINES =
+        "322|" + BRUSSELS_TZ + "\n331|" + PARIS_TZ + "\n";
+
+  private static SortedMap<Integer, String> parseTextFileHelper(String input) throws IOException {
+    return GenerateTimeZonesMapData.parseTextFile(new ByteArrayInputStream(input.getBytes()));
+  }
+
+  public void testParseTextFile() throws IOException {
+    Map<Integer, String> result = parseTextFileHelper(PARIS_BRUSSELS_LINES);
+    assertEquals(2, result.size());
+    assertEquals(PARIS_TZ, result.get(331));
+    assertEquals(BRUSSELS_TZ, result.get(322));
+  }
+
+  public void testParseTextFileIgnoresComments() throws IOException {
+    Map<Integer, String> result = parseTextFileHelper("# Hello\n" + PARIS_BRUSSELS_LINES);
+    assertEquals(2, result.size());
+    assertEquals(PARIS_TZ, result.get(331));
+    assertEquals(BRUSSELS_TZ, result.get(322));
+  }
+
+  public void testParseTextFileIgnoresBlankLines() throws IOException {
+    Map<Integer, String> result = parseTextFileHelper("\n" + PARIS_BRUSSELS_LINES);
+    assertEquals(2, result.size());
+    assertEquals(PARIS_TZ, result.get(331));
+    assertEquals(BRUSSELS_TZ, result.get(322));
+  }
+
+  public void testParseTextFileIgnoresTrailingWhitespaces() throws IOException {
+    Map<Integer, String> result = parseTextFileHelper(
+        "331|" + PARIS_TZ + "\n322|" + BRUSSELS_TZ + "  \n");
+    assertEquals(2, result.size());
+    assertEquals(PARIS_TZ, result.get(331));
+    assertEquals(BRUSSELS_TZ, result.get(322));
+  }
+
+  public void testParseTextFileThrowsExceptionWithMalformattedData() throws IOException {
+    try {
+      parseTextFileHelper("331");
+      fail();
+    } catch (RuntimeException e) {
+      // Expected.
+    }
+  }
+
+  public void testParseTextFileThrowsExceptionWithMissingTimeZone() throws IOException {
+    try {
+      parseTextFileHelper("331|");
+      fail();
+    } catch (RuntimeException e) {
+      // Expected.
+    }
+  }
+
+  // Returns a String representing the input after serialization and deserialization by
+  // PrefixTimeZonesMap.
+  private static String convertDataHelper(String input) throws IOException {
+    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(input.getBytes());
+    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+
+    SortedMap<Integer, String> prefixTimeZonesMapping = parseTextFileHelper(input);
+    GenerateTimeZonesMapData.writeToBinaryFile(prefixTimeZonesMapping, byteArrayOutputStream);
+    // The byte array output stream now contains the corresponding serialized prefix to time zones
+    // map. Try to deserialize it and compare it with the initial input.
+    PrefixTimeZonesMap prefixTimeZonesMap = new PrefixTimeZonesMap();
+    prefixTimeZonesMap.readExternal(
+        new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())));
+
+    return prefixTimeZonesMap.toString();
+  }
+
+  public void testConvertData() throws IOException {
+    String input = PARIS_BRUSSELS_LINES;
+
+    String dataAfterDeserialization = convertDataHelper(input);
+    assertEquals(input, dataAfterDeserialization);
+  }
+
+  public void testConvertThrowsExceptionWithMissingTimeZone() throws IOException {
+    String input = PARIS_BRUSSELS_LINES + "3341|\n";
+
+    try {
+      String dataAfterDeserialization = convertDataHelper(input);
+    } catch (RuntimeException e) {
+      // Expected.
+    }
+  }
+
+  public void testConvertDataThrowsExceptionWithDuplicatedPrefixes() throws IOException {
+    String input = "331|" + PARIS_TZ + "\n331|" + BRUSSELS_TZ + "\n";
+
+    try {
+      convertDataHelper(input);
+      fail();
+    } catch (RuntimeException e) {
+      // Expected.
+    }
+  }
+}