blob: afbc0b2d808e088b2cc766bd5d935b162789830e [file] [log] [blame]
Narayan Kamatha77fadd2014-12-18 11:56:40 +00001/*
2 * Copyright (C) 2012 The Libphonenumber Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.i18n.phonenumbers;
18
19import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
20import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
21import com.google.i18n.phonenumbers.prefixmapper.PrefixTimeZonesMap;
22
23import java.io.IOException;
24import java.io.InputStream;
25import java.io.ObjectInputStream;
26import java.util.ArrayList;
27import java.util.Collections;
28import java.util.List;
29import java.util.logging.Level;
30import java.util.logging.Logger;
31
32/**
33 * An offline mapper from phone numbers to time zones.
34 */
35public class PhoneNumberToTimeZonesMapper {
36 private static PhoneNumberToTimeZonesMapper instance = null;
37 private static final String MAPPING_DATA_DIRECTORY =
38 "/com/google/i18n/phonenumbers/timezones/data/";
39 private static final String MAPPING_DATA_FILE_NAME = "map_data";
40 // This is defined by ICU as the unknown time zone.
41 private static final String UNKNOWN_TIMEZONE = "Etc/Unknown";
42 // A list with the ICU unknown time zone as single element.
43 // @VisibleForTesting
44 static final List<String> UNKNOWN_TIME_ZONE_LIST = new ArrayList<String>(1);
45 static {
46 UNKNOWN_TIME_ZONE_LIST.add(UNKNOWN_TIMEZONE);
47 }
48
49 private static final Logger LOGGER =
50 Logger.getLogger(PhoneNumberToTimeZonesMapper.class.getName());
51
52 private PrefixTimeZonesMap prefixTimeZonesMap = null;
53
54 // @VisibleForTesting
55 PhoneNumberToTimeZonesMapper(String prefixTimeZonesMapDataDirectory) {
56 this.prefixTimeZonesMap = loadPrefixTimeZonesMapFromFile(
57 prefixTimeZonesMapDataDirectory + MAPPING_DATA_FILE_NAME);
58 }
59
60 private PhoneNumberToTimeZonesMapper(PrefixTimeZonesMap prefixTimeZonesMap) {
61 this.prefixTimeZonesMap = prefixTimeZonesMap;
62 }
63
64 private static PrefixTimeZonesMap loadPrefixTimeZonesMapFromFile(String path) {
65 InputStream source = PhoneNumberToTimeZonesMapper.class.getResourceAsStream(path);
66 ObjectInputStream in = null;
67 PrefixTimeZonesMap map = new PrefixTimeZonesMap();
68 try {
69 in = new ObjectInputStream(source);
70 map.readExternal(in);
71 } catch (IOException e) {
72 LOGGER.log(Level.WARNING, e.toString());
73 } finally {
74 close(in);
75 }
76 return map;
77 }
78
79 private static void close(InputStream in) {
80 if (in != null) {
81 try {
82 in.close();
83 } catch (IOException e) {
84 LOGGER.log(Level.WARNING, e.toString());
85 }
86 }
87 }
88
89 /**
90 * Helper class used for lazy instantiation of a PhoneNumberToTimeZonesMapper. This also loads the
91 * map data in a thread-safe way.
92 */
93 private static class LazyHolder {
94 private static final PhoneNumberToTimeZonesMapper INSTANCE;
95 static {
96 PrefixTimeZonesMap map =
97 loadPrefixTimeZonesMapFromFile(MAPPING_DATA_DIRECTORY + MAPPING_DATA_FILE_NAME);
98 INSTANCE = new PhoneNumberToTimeZonesMapper(map);
99 }
100 }
101
102 /**
103 * Gets a {@link PhoneNumberToTimeZonesMapper} instance.
104 *
105 * <p> The {@link PhoneNumberToTimeZonesMapper} is implemented as a singleton. Therefore, calling
106 * this method multiple times will only result in one instance being created.
107 *
108 * @return a {@link PhoneNumberToTimeZonesMapper} instance
109 */
110 public static synchronized PhoneNumberToTimeZonesMapper getInstance() {
111 return LazyHolder.INSTANCE;
112 }
113
114 /**
115 * Returns a list of time zones to which a phone number belongs.
116 *
117 * <p>This method assumes the validity of the number passed in has already been checked, and that
118 * the number is geo-localizable. We consider fixed-line and mobile numbers possible candidates
119 * for geo-localization.
120 *
121 * @param number a valid phone number for which we want to get the time zones to which it belongs
122 * @return a list of the corresponding time zones or a single element list with the default
123 * unknown time zone if no other time zone was found or if the number was invalid
124 */
125 public List<String> getTimeZonesForGeographicalNumber(PhoneNumber number) {
126 return getTimeZonesForGeocodableNumber(number);
127 }
128
129 /**
130 * As per {@link #getTimeZonesForGeographicalNumber(PhoneNumber)} but explicitly checks
131 * the validity of the number passed in.
132 *
133 * @param number the phone number for which we want to get the time zones to which it belongs
134 * @return a list of the corresponding time zones or a single element list with the default
135 * unknown time zone if no other time zone was found or if the number was invalid
136 */
137 public List<String> getTimeZonesForNumber(PhoneNumber number) {
138 PhoneNumberType numberType = PhoneNumberUtil.getInstance().getNumberType(number);
139 if (numberType == PhoneNumberType.UNKNOWN) {
140 return UNKNOWN_TIME_ZONE_LIST;
141 } else if (!canBeGeocoded(numberType)) {
142 return getCountryLevelTimeZonesforNumber(number);
143 }
144 return getTimeZonesForGeographicalNumber(number);
145 }
146
147 /**
148 * A similar method is implemented as PhoneNumberUtil.isNumberGeographical, which performs a
149 * stricter check, as it determines if a number has a geographical association. Also, if new
150 * phone number types were added, we should check if this other method should be updated too.
151 * TODO: Remove duplication by completing the logic in the method in PhoneNumberUtil.
152 * For more information, see the comments in that method.
153 */
154 private boolean canBeGeocoded(PhoneNumberType numberType) {
155 return (numberType == PhoneNumberType.FIXED_LINE ||
156 numberType == PhoneNumberType.MOBILE ||
157 numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE);
158 }
159
160 /**
161 * Returns a String with the ICU unknown time zone.
162 */
163 public static String getUnknownTimeZone() {
164 return UNKNOWN_TIMEZONE;
165 }
166
167 /**
168 * Returns a list of time zones to which a geocodable phone number belongs.
169 *
170 * @param number the phone number for which we want to get the time zones to which it belongs
171 * @return the list of corresponding time zones or a single element list with the default
172 * unknown time zone if no other time zone was found or if the number was invalid
173 */
174 private List<String> getTimeZonesForGeocodableNumber(PhoneNumber number) {
175 List<String> timezones = prefixTimeZonesMap.lookupTimeZonesForNumber(number);
176 return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST
177 : timezones);
178 }
179
180 /**
181 * Returns the list of time zones corresponding to the country calling code of {@code number}.
182 *
183 * @param number the phone number to look up
184 * @return the list of corresponding time zones or a single element list with the default
185 * unknown time zone if no other time zone was found
186 */
187 private List<String> getCountryLevelTimeZonesforNumber(PhoneNumber number) {
188 List<String> timezones = prefixTimeZonesMap.lookupCountryLevelTimeZonesForNumber(number);
189 return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST
190 : timezones);
191 }
192}