summaryrefslogtreecommitdiff
path: root/geocoder/src/com/google/i18n/phonenumbers/PhoneNumberToTimeZonesMapper.java
blob: 217587ba9552f026bb846ff097df701511adc74c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/*
 * 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.
 */
public class PhoneNumberToTimeZonesMapper {
  private static final String MAPPING_DATA_DIRECTORY =
      "/com/google/i18n/phonenumbers/timezones/data/";
  private static final String MAPPING_DATA_FILE_NAME = "map_data";
  // This is defined by ICU as the unknown time zone.
  private static final String UNKNOWN_TIMEZONE = "Etc/Unknown";
  // A list with the ICU unknown time zone as single element.
  // @VisibleForTesting
  static final List<String> UNKNOWN_TIME_ZONE_LIST = new ArrayList<String>(1);
  static {
    UNKNOWN_TIME_ZONE_LIST.add(UNKNOWN_TIMEZONE);
  }

  private static final Logger logger =
      Logger.getLogger(PhoneNumberToTimeZonesMapper.class.getName());

  private PrefixTimeZonesMap prefixTimeZonesMap = null;

  // @VisibleForTesting
  PhoneNumberToTimeZonesMapper(String prefixTimeZonesMapDataDirectory) {
    this.prefixTimeZonesMap = loadPrefixTimeZonesMapFromFile(
        prefixTimeZonesMapDataDirectory + MAPPING_DATA_FILE_NAME);
  }

  private PhoneNumberToTimeZonesMapper(PrefixTimeZonesMap prefixTimeZonesMap) {
    this.prefixTimeZonesMap = prefixTimeZonesMap;
  }

  private static PrefixTimeZonesMap loadPrefixTimeZonesMapFromFile(String path) {
    InputStream source = PhoneNumberToTimeZonesMapper.class.getResourceAsStream(path);
    ObjectInputStream in = null;
    PrefixTimeZonesMap map = new PrefixTimeZonesMap();
    try {
      in = new ObjectInputStream(source);
      map.readExternal(in);
    } catch (IOException e) {
      logger.log(Level.WARNING, e.toString());
    } finally {
      close(in);
    }
    return map;
  }

  private static void close(InputStream in) {
    if (in != null) {
      try {
        in.close();
      } catch (IOException e) {
        logger.log(Level.WARNING, e.toString());
      }
    }
  }

  /**
   * Helper class used for lazy instantiation of a PhoneNumberToTimeZonesMapper. This also loads the
   * map data in a thread-safe way.
   */
  private static class LazyHolder {
    private static final PhoneNumberToTimeZonesMapper INSTANCE;
    static {
      PrefixTimeZonesMap map =
          loadPrefixTimeZonesMapFromFile(MAPPING_DATA_DIRECTORY + MAPPING_DATA_FILE_NAME);
      INSTANCE = new PhoneNumberToTimeZonesMapper(map);
    }
  }

  /**
   * Gets a {@link PhoneNumberToTimeZonesMapper} instance.
   *
   * <p> The {@link PhoneNumberToTimeZonesMapper} is implemented as a singleton. Therefore, calling
   * this method multiple times will only result in one instance being created.
   *
   * @return  a {@link PhoneNumberToTimeZonesMapper} instance
   */
  public static synchronized PhoneNumberToTimeZonesMapper getInstance() {
    return LazyHolder.INSTANCE;
  }

  /**
   * Returns a list of time zones to which a phone number belongs.
   *
   * <p>This method assumes the validity of the number passed in has already been checked, and that
   * the number is geo-localizable. We consider fixed-line and mobile numbers possible candidates
   * for geo-localization.
   *
   * @param number  a valid phone number for which we want to get the time zones to which it belongs
   * @return  a list of the corresponding time zones or a single element list with the default
   *     unknown time zone if no other time zone was found or if the number was invalid
   */
  public List<String> getTimeZonesForGeographicalNumber(PhoneNumber number) {
    return getTimeZonesForGeocodableNumber(number);
  }

  /**
   * As per {@link #getTimeZonesForGeographicalNumber(PhoneNumber)} but explicitly checks
   * the validity of the number passed in.
   *
   * @param number  the phone number for which we want to get the time zones to which it belongs
   * @return  a list of the corresponding time zones or a single element list with the default
   *     unknown time zone if no other time zone was found or if the number was invalid
   */
  public List<String> getTimeZonesForNumber(PhoneNumber number) {
    PhoneNumberType numberType = PhoneNumberUtil.getInstance().getNumberType(number);
    if (numberType == PhoneNumberType.UNKNOWN) {
      return UNKNOWN_TIME_ZONE_LIST;
    } else if (!PhoneNumberUtil.getInstance().isNumberGeographical(
        numberType, number.getCountryCode())) {
      return getCountryLevelTimeZonesforNumber(number);
    }
    return getTimeZonesForGeographicalNumber(number);
  }

  /**
   * Returns a String with the ICU unknown time zone.
   */
  public static String getUnknownTimeZone() {
    return UNKNOWN_TIMEZONE;
  }

  /**
   * Returns a list of time zones to which a geocodable phone number belongs.
   *
   * @param number  the phone number for which we want to get the time zones to which it belongs
   * @return  the list of corresponding  time zones or a single element list with the default
   *     unknown time zone if no other time zone was found or if the number was invalid
   */
  private List<String> getTimeZonesForGeocodableNumber(PhoneNumber number) {
    List<String> timezones = prefixTimeZonesMap.lookupTimeZonesForNumber(number);
    return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST
                                                            : timezones);
  }

  /**
   * Returns the list of time zones corresponding to the country calling code of {@code number}.
   *
   * @param number  the phone number to look up
   * @return  the list of corresponding time zones or a single element list with the default
   *     unknown time zone if no other time zone was found
   */
  private List<String> getCountryLevelTimeZonesforNumber(PhoneNumber number) {
    List<String> timezones = prefixTimeZonesMap.lookupCountryLevelTimeZonesForNumber(number);
    return Collections.unmodifiableList(timezones.isEmpty() ? UNKNOWN_TIME_ZONE_LIST
                                                            : timezones);
  }
}