• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.google.i18n.phonenumbers;
18 
19 import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
20 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
21 import com.google.i18n.phonenumbers.prefixmapper.PrefixTimeZonesMap;
22 
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.ObjectInputStream;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.List;
29 import java.util.logging.Level;
30 import java.util.logging.Logger;
31 
32 /**
33  * An offline mapper from phone numbers to time zones.
34  */
35 public class PhoneNumberToTimeZonesMapper {
36   private static final String MAPPING_DATA_DIRECTORY =
37       "/com/google/i18n/phonenumbers/timezones/data/";
38   private static final String MAPPING_DATA_FILE_NAME = "map_data";
39   // This is defined by ICU as the unknown time zone.
40   private static final String UNKNOWN_TIMEZONE = "Etc/Unknown";
41   // A list with the ICU unknown time zone as single element.
42   // @VisibleForTesting
43   static final List<String> UNKNOWN_TIME_ZONE_LIST = new ArrayList<String>(1);
44   static {
45     UNKNOWN_TIME_ZONE_LIST.add(UNKNOWN_TIMEZONE);
46   }
47 
48   private static final Logger logger =
49       Logger.getLogger(PhoneNumberToTimeZonesMapper.class.getName());
50 
51   private PrefixTimeZonesMap prefixTimeZonesMap = null;
52 
53   // @VisibleForTesting
PhoneNumberToTimeZonesMapper(String prefixTimeZonesMapDataDirectory)54   PhoneNumberToTimeZonesMapper(String prefixTimeZonesMapDataDirectory) {
55     this.prefixTimeZonesMap = loadPrefixTimeZonesMapFromFile(
56         prefixTimeZonesMapDataDirectory + MAPPING_DATA_FILE_NAME);
57   }
58 
PhoneNumberToTimeZonesMapper(PrefixTimeZonesMap prefixTimeZonesMap)59   private PhoneNumberToTimeZonesMapper(PrefixTimeZonesMap prefixTimeZonesMap) {
60     this.prefixTimeZonesMap = prefixTimeZonesMap;
61   }
62 
loadPrefixTimeZonesMapFromFile(String path)63   private static PrefixTimeZonesMap loadPrefixTimeZonesMapFromFile(String path) {
64     InputStream source = PhoneNumberToTimeZonesMapper.class.getResourceAsStream(path);
65     ObjectInputStream in = null;
66     PrefixTimeZonesMap map = new PrefixTimeZonesMap();
67     try {
68       in = new ObjectInputStream(source);
69       map.readExternal(in);
70     } catch (IOException e) {
71       logger.log(Level.WARNING, e.toString());
72     } finally {
73       close(in);
74     }
75     return map;
76   }
77 
close(InputStream in)78   private static void close(InputStream in) {
79     if (in != null) {
80       try {
81         in.close();
82       } catch (IOException e) {
83         logger.log(Level.WARNING, e.toString());
84       }
85     }
86   }
87 
88   /**
89    * Helper class used for lazy instantiation of a PhoneNumberToTimeZonesMapper. This also loads the
90    * map data in a thread-safe way.
91    */
92   private static class LazyHolder {
93     private static final PhoneNumberToTimeZonesMapper INSTANCE;
94     static {
95       PrefixTimeZonesMap map =
96           loadPrefixTimeZonesMapFromFile(MAPPING_DATA_DIRECTORY + MAPPING_DATA_FILE_NAME);
97       INSTANCE = new PhoneNumberToTimeZonesMapper(map);
98     }
99   }
100 
101   /**
102    * Gets a {@link PhoneNumberToTimeZonesMapper} instance.
103    *
104    * <p> The {@link PhoneNumberToTimeZonesMapper} is implemented as a singleton. Therefore, calling
105    * this method multiple times will only result in one instance being created.
106    *
107    * @return  a {@link PhoneNumberToTimeZonesMapper} instance
108    */
getInstance()109   public static synchronized PhoneNumberToTimeZonesMapper getInstance() {
110     return LazyHolder.INSTANCE;
111   }
112 
113   /**
114    * Returns a list of time zones to which a phone number belongs.
115    *
116    * <p>This method assumes the validity of the number passed in has already been checked, and that
117    * the number is geo-localizable. We consider fixed-line and mobile numbers possible candidates
118    * for geo-localization.
119    *
120    * @param number  a valid phone number for which we want to get the time zones to which it belongs
121    * @return  a list of the corresponding time zones or a single element list with the default
122    *     unknown time zone if no other time zone was found or if the number was invalid
123    */
getTimeZonesForGeographicalNumber(PhoneNumber number)124   public List<String> getTimeZonesForGeographicalNumber(PhoneNumber number) {
125     return getTimeZonesForGeocodableNumber(number);
126   }
127 
128   /**
129    * As per {@link #getTimeZonesForGeographicalNumber(PhoneNumber)} but explicitly checks
130    * the validity of the number passed in.
131    *
132    * @param number  the phone number for which we want to get the time zones to which it belongs
133    * @return  a list of the corresponding time zones or a single element list with the default
134    *     unknown time zone if no other time zone was found or if the number was invalid
135    */
getTimeZonesForNumber(PhoneNumber number)136   public List<String> getTimeZonesForNumber(PhoneNumber number) {
137     PhoneNumberType numberType = PhoneNumberUtil.getInstance().getNumberType(number);
138     if (numberType == PhoneNumberType.UNKNOWN) {
139       return UNKNOWN_TIME_ZONE_LIST;
140     } else if (!PhoneNumberUtil.getInstance().isNumberGeographical(
141         numberType, number.getCountryCode())) {
142       return getCountryLevelTimeZonesforNumber(number);
143     }
144     return getTimeZonesForGeographicalNumber(number);
145   }
146 
147   /**
148    * Returns a String with the ICU unknown time zone.
149    */
getUnknownTimeZone()150   public static String getUnknownTimeZone() {
151     return UNKNOWN_TIMEZONE;
152   }
153 
154   /**
155    * Returns a list of time zones to which a geocodable phone number belongs.
156    *
157    * @param number  the phone number for which we want to get the time zones to which it belongs
158    * @return  the list of corresponding  time zones or a single element list with the default
159    *     unknown time zone if no other time zone was found or if the number was invalid
160    */
getTimeZonesForGeocodableNumber(PhoneNumber number)161   private List<String> getTimeZonesForGeocodableNumber(PhoneNumber number) {
162     List<String> timezones = prefixTimeZonesMap.lookupTimeZonesForNumber(number);
163     return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST
164                                                             : timezones);
165   }
166 
167   /**
168    * Returns the list of time zones corresponding to the country calling code of {@code number}.
169    *
170    * @param number  the phone number to look up
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
173    */
getCountryLevelTimeZonesforNumber(PhoneNumber number)174   private List<String> getCountryLevelTimeZonesforNumber(PhoneNumber number) {
175     List<String> timezones = prefixTimeZonesMap.lookupCountryLevelTimeZonesForNumber(number);
176     return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST
177                                                             : timezones);
178   }
179 }
180