From 23f12e8b5f22508d1de7f5a14bab128f029cac8d Mon Sep 17 00:00:00 2001 From: Cecilia Roes Date: Mon, 7 Oct 2013 12:27:55 +0000 Subject: [PATCH] JAVA: Added the phone number to timezones mapper, including tests, binary generation code and data --- java/build.xml | 29 +- .../PhoneNumberParserServlet.java | 11 + java/geocoder/pom.xml | 8 + .../PhoneNumberToTimeZonesMapper.java | 194 ++ .../i18n/phonenumbers/timezones/data/map_data | Bin 0 -> 17772 bytes .../PhoneNumberToTimeZonesMapperTest.java | 136 ++ .../timezones/testing_data/map_data | Bin 0 -> 306 bytes .../prefixmapper/PhonePrefixMap.java | 19 +- .../prefixmapper/PrefixTimeZonesMap.java | 128 ++ .../prefixmapper/PrefixTimeZonesMapTest.java | 178 ++ java/release_notes.txt | 8 + resources/test/timezones/map_data.txt | 24 + resources/timezones/map_data.txt | 1996 +++++++++++++++++ ...ild-1.0-SNAPSHOT-jar-with-dependencies.jar | Bin 517095 -> 517096 bytes .../google/i18n/phonenumbers/EntryPoint.java | 2 + .../buildtools/GenerateTimeZonesMapData.java | 143 ++ .../GenerateTimeZonesMapDataEntryPoint.java | 60 + ...ild-1.0-SNAPSHOT-jar-with-dependencies.jar | Bin 417956 -> 423591 bytes .../GenerateTimeZonesMapDataTest.java | 136 ++ 19 files changed, 3065 insertions(+), 7 deletions(-) create mode 100644 java/geocoder/src/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapper.java create mode 100644 java/geocoder/src/com/google/i18n/phonenumbers/timezones/data/map_data create mode 100644 java/geocoder/test/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapperTest.java create mode 100644 java/geocoder/test/com/google/i18n/phonenumbers/timezones/testing_data/map_data create mode 100644 java/internal/prefixmapper/src/com/google/i18n/phonenumbers/prefixmapper/PrefixTimeZonesMap.java create mode 100644 java/internal/prefixmapper/test/com/google/i18n/phonenumbers/prefixmapper/PrefixTimeZonesMapTest.java create mode 100644 resources/test/timezones/map_data.txt create mode 100644 resources/timezones/map_data.txt create mode 100644 tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapData.java create mode 100644 tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapDataEntryPoint.java create mode 100644 tools/java/java-build/test/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapDataTest.java diff --git a/java/build.xml b/java/build.xml index a0553cd77..18e30b26f 100644 --- a/java/build.xml +++ b/java/build.xml @@ -127,6 +127,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -153,7 +173,7 @@ + depends="build-phone-metadata,build-short-metadata,build-alternate-metadata,build-carrier-data,build-geo-data,build-timezones-data"> @@ -169,7 +189,7 @@ - + @@ -192,17 +212,19 @@ + + + depends="compile,build-test-metadata,build-carrier-test-data,build-geo-test-data,build-timezones-test-data"> @@ -222,6 +244,7 @@ + diff --git a/java/demo/src/com/google/phonenumbers/PhoneNumberParserServlet.java b/java/demo/src/com/google/phonenumbers/PhoneNumberParserServlet.java index 9344fbbaf..77fa2a96e 100644 --- a/java/demo/src/com/google/phonenumbers/PhoneNumberParserServlet.java +++ b/java/demo/src/com/google/phonenumbers/PhoneNumberParserServlet.java @@ -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(""); output.append(""); + output.append("
"); + output.append(""); + output.append(""); + appendLine( + "Time zone(s)", + PhoneNumberToTimeZonesMapper.getInstance().getTimeZonesForNumber(number).toString(), + output); + output.append("
PhoneNumberToTimeZonesMapper Results
"); + output.append("
"); + if (numberType == PhoneNumberType.MOBILE || numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE || numberType == PhoneNumberType.PAGER) { diff --git a/java/geocoder/pom.xml b/java/geocoder/pom.xml index 3f5635a52..72a212c7a 100644 --- a/java/geocoder/pom.xml +++ b/java/geocoder/pom.xml @@ -21,12 +21,20 @@ src/com/google/i18n/phonenumbers/geocoding/data com/google/i18n/phonenumbers/geocoding/data + + src/com/google/i18n/phonenumbers/timezones/data + com/google/i18n/phonenumbers/timezones/data + test/com/google/i18n/phonenumbers/geocoding/testing_data com/google/i18n/phonenumbers/geocoding/testing_data + + test/com/google/i18n/phonenumbers/timezones/testing_data + com/google/i18n/phonenumbers/timezones/testing_data + 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 index 000000000..d1d4c4486 --- /dev/null +++ b/java/geocoder/src/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapper.java @@ -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 UNKNOWN_TIME_ZONE_LIST = new ArrayList(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. + * + *

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. + * + *

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 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 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 getTimeZonesForGeocodableNumber(PhoneNumber number) { + List 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 getCountryLevelTimeZonesforNumber(PhoneNumber number) { + List 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 index 0000000000000000000000000000000000000000..aa574da0882beb804caab699bef6fad1c6e08da2 GIT binary patch literal 17772 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 buildListOfTimeZones(String ... timezones) { + ArrayList timezonesList = new ArrayList(timezones.length); + for (String timezone : timezones) { + timezonesList.add(timezone); + } + return timezonesList; + } + + private static List 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 index 0000000000000000000000000000000000000000..0150711ea0e3aa7efbd175b204bb9d18c8f28bd6 GIT binary patch literal 306 zcmZ4UmVvd3fq_wzk%57M1&En|m=%aYd=Q%%h(Y4)K+M4)?3kNcl$n^U?~`8~@0gdK znv+`0zz-HoPSkhK$V^U5&xfi43LvWzfT{9JEsu}PFUn?cMHf+n2|x{k^TRXq@-hoj z)8PVG4Th;=;BqX^Owu{lNvqj9^~{F|Yx>nF8`1 k%SMo2SuTM{HWyGBupb4g6Kd=Mib$;E2kMYC6kuQi0A4aoZvX%Q literal 0 HcmV?d00001 diff --git a/java/internal/prefixmapper/src/com/google/i18n/phonenumbers/prefixmapper/PhonePrefixMap.java b/java/internal/prefixmapper/src/com/google/i18n/phonenumbers/prefixmapper/PhonePrefixMap.java index 0d65e078f..fdab3ec4a 100644 --- a/java/internal/prefixmapper/src/com/google/i18n/phonenumbers/prefixmapper/PhonePrefixMap.java +++ b/java/internal/prefixmapper/src/com/google/i18n/phonenumbers/prefixmapper/PhonePrefixMap.java @@ -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 currentSetOfLengths = phonePrefixMapStorage.getPossibleLengths(); while (currentSetOfLengths.size() > 0) { @@ -172,6 +171,18 @@ public class PhonePrefixMap implements Externalizable { return null; } + /** + * 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 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 index 000000000..8a2ce029c --- /dev/null +++ b/java/internal/prefixmapper/src/com/google/i18n/phonenumbers/prefixmapper/PrefixTimeZonesMap.java @@ -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 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. + * + *

{@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 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(); + } + 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 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 lookupCountryLevelTimeZonesForNumber(PhoneNumber number) { + return lookupTimeZonesForNumber(number.getCountryCode()); + } + + /** + * Split {@code timezonesString} into all the time zones that are part of it. + */ + private List tokenizeRawOutputString(String timezonesString) { + StringTokenizer tokenizer = new StringTokenizer(timezonesString, + RAW_STRING_TIMEZONES_SEPARATOR); + LinkedList timezonesList = new LinkedList(); + 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 index 000000000..c6ac492ba --- /dev/null +++ b/java/internal/prefixmapper/test/com/google/i18n/phonenumbers/prefixmapper/PrefixTimeZonesMapTest.java @@ -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 sortedMapForUS = new TreeMap(); + 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 sortedMapForRU = new TreeMap(); + 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 buildListOfTimeZones(String ... timezones) { + ArrayList timezonesList = new ArrayList(timezones.length); + for (String timezone : timezones) { + timezonesList.add(timezone); + } + return timezonesList; + } + + private static SortedMap createMapCandidate() { + SortedMap sortedMap = new TreeMap(); + 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()); + } +} diff --git a/java/release_notes.txt b/java/release_notes.txt index a387e972a..880b55d00 100644 --- a/java/release_notes.txt +++ b/java/release_notes.txt @@ -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 index 000000000..3a438a6e4 --- /dev/null +++ b/resources/test/timezones/map_data.txt @@ -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 index 000000000..c0798c132 --- /dev/null +++ b/resources/timezones/map_data.txt @@ -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 diff --git a/tools/java/cpp-build/target/cpp-build-1.0-SNAPSHOT-jar-with-dependencies.jar b/tools/java/cpp-build/target/cpp-build-1.0-SNAPSHOT-jar-with-dependencies.jar index 5a039c8d91ef52b2cce459ccca1367a20fc04eb7..061b7e8b59d2d7640aafab7afdb4b7f9f3035f3b 100644 GIT binary patch delta 831 zcmYk5Ye-XJ7{}et_CBWb64_doH;AngFWJdNqeulSDOZ{rrnzvbl$X2|GBvlDP0YwI zo{A{S4;p@uP&aXwFeIrU5~^L?T_|BhA4F6H?VRUGbUyv~KmY&xKF|Abe$B-#&BcW& z^C?kC5cheT^7_LLP_CvtEv=^odRnNbDLpOH)8Yx5$PAQOoy`;&NFqXqfn;!AX3{`5 zI4~J|h)RMKJwQr@NUcxEix4E~ASvI;B~5q6iBN=^W|BmS!r5$2>C~6;5kXMUQ;L~i zCr6k{FGx&|X)-Io$gB6{s+fxyXNffivko7*5Q@db-=r`YHSG!+VWmlVOPW4W|OlYaSX-?89oT5~O_g_$7E5fhXHl;0Q%B)W)$*>P`a8E? zTdfZ#4s}LLWB)82z@pv zLkQTQ89`--MofLPLp3TV9ngd@=78H+QY=6nD$D}ZB2Z2?&RyE(1T`u?C$ytd>0%#} zd(kP^c0Ae5w!xVnx#2RxPd9rYrxbhOI)d2)rI?a>p$0+gg_{U}ywHN48@_GxXCGX{ fQj&jrz&=0QJi9$+)erUmEw6qNn#n;Ouz$;6#yLs( delta 825 zcmYk5ZAep57{|BWyYnpHW-zoDR6=W&pi?eQC=jv_rM+zBnp9#+bB3uadjY4&R*Q+C z^u@a{67fTcMPh3cwHKl?!PX3HbIxtf$dJOK!bHmMeXd03(~tl2|DSW7^Kd?mrp=G0 z#cPTvI!44Vr%8PyK7)E_Qd<}sBepRT8$;O`+Qvv9F=T_CYB#CbBX*LB&}1ih)BvXp z+Q|wJZdVH_k3-tjK}sY@mmK7F3{p}LDP7Me-Itw2LZfCLBnmo#%NCRjd7OP>k?1DH zPArogw`YWS#pv#SLQY9}x9bJ*rC=;DOAg0kG4l&KCPU5glk^AymYN`g3gsl1~bjfk&sd5O!BDredx{%GUgKaWQR9yiY zn_=tBaEQ0DPtBmGw5=-l#zx4NuH)NF9NF>S!fGv`Z=Q&;suUvcQqxOmSBhurwMfCf zV)=1Z^XY|2(y-|0^oI3|^JnxKal4|j-*93$z2%-kay7LuenFG0yPug?b`T8RPJhn1 zwqEVP>o;>ha@|Jd$7f^V(aMOeZs$-0aT&Ekk1o-yS@EyYit! z+bEW6io_BH^>5;=+6oLmb9b#3T*Z7(tkMU&5ez=qk1h54pcEm|4`zfmKb%DP?1y@U ziYYK*YH|vUsN@IW0)jID=drYX8fs8+Ov70OI>^QOLmPr%KqVT4MpVjYxOe0qba-Yh zo*m+@!JFGdphK7saU1fAJ`5EIBVj1PRB{BW5UdfXM)(?mi|8@Vu9?SYp$tn|(X|HK ZqTJ=V;jzD>Q2XEP>gJ%HD6PQ#lfTeQKnwr? diff --git a/tools/java/java-build/src/com/google/i18n/phonenumbers/EntryPoint.java b/tools/java/java-build/src/com/google/i18n/phonenumbers/EntryPoint.java index f7eec1b03..63b8f1b14 100644 --- a/tools/java/java-build/src/com/google/i18n/phonenumbers/EntryPoint.java +++ b/tools/java/java-build/src/com/google/i18n/phonenumbers/EntryPoint.java @@ -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 index 000000000..b07a187cf --- /dev/null +++ b/tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapData.java @@ -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 parseTextFile(InputStream input) + throws IOException, RuntimeException { + final SortedMap timeZoneMap = new TreeMap(); + 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 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 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 index 000000000..355c9fa5e --- /dev/null +++ b/tools/java/java-build/src/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapDataEntryPoint.java @@ -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; + } +} diff --git a/tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar b/tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar index 1e971e49feba4bf15cec2f1274794a5f2355cea3..305b87d1e59bd6368f76b81fad68a591e4779fda 100644 GIT binary patch delta 35506 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 G%jLgO`MCK2 delta 30009 zcmZu)2V9Qd+wXbq`>qg)ib{)&(6Wn4g`#bwPzl+k>=DTxp+v__k+Mn&PqwUVW$#La zkg~V;I_Em|`{#LopHHvnz0Nt`Yo6=u`=9g)>WbZ9g650U*AkP2>*1- z6WYT+-;C)$3Hq{D>|YmIJ7FgL|BT9cuL<}l8VaBdI^pXTI9?(kg2jJi%io5_QRrOYmNG}rf-85 zmKGpkhlCItx9BMC7dn5v4;;&R;2}4gILdUFe}SiZuXAboCY?-*M>x+nS#c-v(&$Eg zx^|D8uDg85p^Slh!**H>>D_zpUH|i4wp%*gp8cuv)wp+ai@weczWI0agNa=(C9k^~ zJa6Oovbpc)eEoT9Qt-?d*{x;meSDTI&NW@o>g}@Pxie2i%`;iBHP<)#yrAz6N*);Ijnc|)IOAfe*Q_YF^MTFZSEz5#ZM{IVG{{sl zvD6`|*SRObtJShx{WSM{8!P6oShTXrCZPUuTg^-3_xCVbU98ok?Y<+E-=59*(9|#6 zaY24WPop)iK1*AV`kdP4dv2CP%QeyIi$CiH*zbMTSSza0wnhUSGM?G$+~}zCd+5G{ zk%vd7XK4k8y6Nz^=chJ8krSCL&a&7A2{a3O&uA1;-v2@nv55cMW&Um~WT%S%0N!t?v}#=>>ojysaSFRLGCV%6nV+O6%$oq`m;dD)*1-a3B!)hpXYr)K}!b!6ze zJ6>0&$A)L`vP?0#IXE?I^2B)?BZ{V^&Zyt5&)%>%f?+^z8kzZn@;avC!zNPY0SS9PHP3cst@lS#ES#v-4hiE2?r@mxbQBch~$$ z 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

  • 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 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 index 000000000..4ca397cfe --- /dev/null +++ b/tools/java/java-build/test/com/google/i18n/phonenumbers/buildtools/GenerateTimeZonesMapDataTest.java @@ -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 parseTextFileHelper(String input) throws IOException { + return GenerateTimeZonesMapData.parseTextFile(new ByteArrayInputStream(input.getBytes())); + } + + public void testParseTextFile() throws IOException { + Map 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 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 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 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 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. + } + } +}