1 /* 2 * Copyright (C) 2019 The Android Open Source Project 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 android.timezone; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.icu.util.TimeZone; 22 23 import java.util.ArrayList; 24 import java.util.Collections; 25 import java.util.List; 26 import java.util.Objects; 27 28 /** 29 * Information about a country's time zones. 30 * 31 * @hide 32 */ 33 public final class CountryTimeZones { 34 35 /** 36 * A mapping to a time zone ID with some associated metadata. 37 * 38 * @hide 39 */ 40 public static final class TimeZoneMapping { 41 42 @NonNull 43 private com.android.i18n.timezone.CountryTimeZones.TimeZoneMapping mDelegate; 44 TimeZoneMapping(com.android.i18n.timezone.CountryTimeZones.TimeZoneMapping delegate)45 TimeZoneMapping(com.android.i18n.timezone.CountryTimeZones.TimeZoneMapping delegate) { 46 this.mDelegate = Objects.requireNonNull(delegate); 47 } 48 49 /** 50 * Returns the ID for this mapping. The ID is a tzdb time zone identifier like 51 * "America/Los_Angeles" that can be used with methods such as {@link 52 * TimeZone#getFrozenTimeZone(String)}. See {@link #getTimeZone()} which returns a frozen 53 * {@link TimeZone} object. 54 */ 55 @NonNull getTimeZoneId()56 public String getTimeZoneId() { 57 return mDelegate.getTimeZoneId(); 58 } 59 60 /** 61 * Returns a frozen {@link TimeZone} object for this mapping. 62 */ 63 @NonNull getTimeZone()64 public TimeZone getTimeZone() { 65 return mDelegate.getTimeZone(); 66 } 67 68 @Override equals(Object o)69 public boolean equals(Object o) { 70 if (this == o) { 71 return true; 72 } 73 if (o == null || getClass() != o.getClass()) { 74 return false; 75 } 76 TimeZoneMapping that = (TimeZoneMapping) o; 77 return this.mDelegate.equals(that.mDelegate); 78 } 79 80 @Override hashCode()81 public int hashCode() { 82 return this.mDelegate.hashCode(); 83 } 84 85 @Override toString()86 public String toString() { 87 return mDelegate.toString(); 88 } 89 } 90 91 /** 92 * The result of lookup up a time zone using offset information (and possibly more). 93 * 94 * @hide 95 */ 96 public static final class OffsetResult { 97 98 private final TimeZone mTimeZone; 99 private final boolean mIsOnlyMatch; 100 101 /** Creates an instance with the supplied information. */ OffsetResult(@onNull TimeZone timeZone, boolean isOnlyMatch)102 public OffsetResult(@NonNull TimeZone timeZone, boolean isOnlyMatch) { 103 mTimeZone = Objects.requireNonNull(timeZone); 104 mIsOnlyMatch = isOnlyMatch; 105 } 106 107 /** 108 * Returns a time zone that matches the supplied criteria. 109 */ 110 @NonNull getTimeZone()111 public TimeZone getTimeZone() { 112 return mTimeZone; 113 } 114 115 /** 116 * Returns {@code true} if there is only one matching time zone for the supplied criteria. 117 */ isOnlyMatch()118 public boolean isOnlyMatch() { 119 return mIsOnlyMatch; 120 } 121 122 @Override equals(Object o)123 public boolean equals(Object o) { 124 if (this == o) { 125 return true; 126 } 127 if (o == null || getClass() != o.getClass()) { 128 return false; 129 } 130 OffsetResult that = (OffsetResult) o; 131 return mIsOnlyMatch == that.mIsOnlyMatch 132 && mTimeZone.getID().equals(that.mTimeZone.getID()); 133 } 134 135 @Override hashCode()136 public int hashCode() { 137 return Objects.hash(mTimeZone, mIsOnlyMatch); 138 } 139 140 @Override toString()141 public String toString() { 142 return "OffsetResult{" 143 + "mTimeZone(ID)=" + mTimeZone.getID() 144 + ", mIsOnlyMatch=" + mIsOnlyMatch 145 + '}'; 146 } 147 } 148 149 @NonNull 150 private final com.android.i18n.timezone.CountryTimeZones mDelegate; 151 CountryTimeZones(com.android.i18n.timezone.CountryTimeZones delegate)152 CountryTimeZones(com.android.i18n.timezone.CountryTimeZones delegate) { 153 mDelegate = delegate; 154 } 155 156 /** 157 * Returns true if the ISO code for the country is a case-insensitive match for the one 158 * supplied. 159 */ matchesCountryCode(@onNull String countryIso)160 public boolean matchesCountryCode(@NonNull String countryIso) { 161 return mDelegate.matchesCountryCode(countryIso); 162 } 163 164 /** 165 * Returns the default time zone ID for the country. Can return {@code null} in cases when no 166 * data is available or the time zone ID was not recognized. 167 */ 168 @Nullable getDefaultTimeZoneId()169 public String getDefaultTimeZoneId() { 170 return mDelegate.getDefaultTimeZoneId(); 171 } 172 173 /** 174 * Returns the default time zone for the country. Can return {@code null} in cases when no data 175 * is available or the time zone ID was not recognized. 176 */ 177 @Nullable getDefaultTimeZone()178 public TimeZone getDefaultTimeZone() { 179 return mDelegate.getDefaultTimeZone(); 180 } 181 182 /** 183 * Qualifier for a country's default time zone. {@code true} indicates that the country's 184 * default time zone would be a good choice <em>generally</em> when there's no UTC offset 185 * information available. This will only be {@code true} in countries with multiple zones where 186 * a large majority of the population is covered by only one of them. 187 */ isDefaultTimeZoneBoosted()188 public boolean isDefaultTimeZoneBoosted() { 189 return mDelegate.isDefaultTimeZoneBoosted(); 190 } 191 192 /** 193 * Returns {@code true} if the country has at least one time zone that uses UTC at the given 194 * time. This is an efficient check when trying to validate received UTC offset information. 195 * For example, there are situations when a detected zero UTC offset cannot be distinguished 196 * from "no information available" or a corrupted signal. This method is useful because checking 197 * offset information for large countries is relatively expensive but it is generally only the 198 * countries close to the prime meridian that use UTC at <em>any</em> time of the year. 199 * 200 * @param whenMillis the time the offset information is for in milliseconds since the beginning 201 * of the Unix epoch 202 */ hasUtcZone(long whenMillis)203 public boolean hasUtcZone(long whenMillis) { 204 return mDelegate.hasUtcZone(whenMillis); 205 } 206 207 /** 208 * Returns a time zone for the country, if there is one, that matches the supplied properties. 209 * If there are multiple matches and the {@code bias} is one of them then it is returned, 210 * otherwise an arbitrary match is returned based on the {@link 211 * #getEffectiveTimeZoneMappingsAt(long)} ordering. 212 * 213 * @param whenMillis the UTC time to match against 214 * @param bias the time zone to prefer, can be {@code null} to indicate there is no preference 215 * @param totalOffsetMillis the offset from UTC at {@code whenMillis} 216 * @param isDst the Daylight Savings Time state at {@code whenMillis}. {@code true} means DST, 217 * {@code false} means not DST 218 * @return an {@link OffsetResult} with information about a matching zone, or {@code null} if 219 * there is no match 220 */ 221 @Nullable lookupByOffsetWithBias(long whenMillis, @Nullable TimeZone bias, int totalOffsetMillis, boolean isDst)222 public OffsetResult lookupByOffsetWithBias(long whenMillis, @Nullable TimeZone bias, 223 int totalOffsetMillis, boolean isDst) { 224 com.android.i18n.timezone.CountryTimeZones.OffsetResult delegateOffsetResult = 225 mDelegate.lookupByOffsetWithBias( 226 whenMillis, bias, totalOffsetMillis, isDst); 227 return delegateOffsetResult == null ? null : 228 new OffsetResult( 229 delegateOffsetResult.getTimeZone(), delegateOffsetResult.isOnlyMatch()); 230 } 231 232 /** 233 * Returns a time zone for the country, if there is one, that matches the supplied properties. 234 * If there are multiple matches and the {@code bias} is one of them then it is returned, 235 * otherwise an arbitrary match is returned based on the {@link 236 * #getEffectiveTimeZoneMappingsAt(long)} ordering. 237 * 238 * @param whenMillis the UTC time to match against 239 * @param bias the time zone to prefer, can be {@code null} to indicate there is no preference 240 * @param totalOffsetMillis the offset from UTC at {@code whenMillis} 241 * @return an {@link OffsetResult} with information about a matching zone, or {@code null} if 242 * there is no match 243 */ 244 @Nullable lookupByOffsetWithBias(long whenMillis, @Nullable TimeZone bias, int totalOffsetMillis)245 public OffsetResult lookupByOffsetWithBias(long whenMillis, @Nullable TimeZone bias, 246 int totalOffsetMillis) { 247 com.android.i18n.timezone.CountryTimeZones.OffsetResult delegateOffsetResult = 248 mDelegate.lookupByOffsetWithBias(whenMillis, bias, totalOffsetMillis); 249 return delegateOffsetResult == null ? null : 250 new OffsetResult( 251 delegateOffsetResult.getTimeZone(), delegateOffsetResult.isOnlyMatch()); 252 } 253 254 /** 255 * Returns an immutable, ordered list of time zone mappings for the country in an undefined but 256 * "priority" order, filtered so that only "effective" time zone IDs are returned. An 257 * "effective" time zone is one that differs from another time zone used in the country after 258 * {@code whenMillis}. The list can be empty if there were no zones configured or the configured 259 * zone IDs were not recognized. 260 */ 261 @NonNull getEffectiveTimeZoneMappingsAt(long whenMillis)262 public List<TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long whenMillis) { 263 List<com.android.i18n.timezone.CountryTimeZones.TimeZoneMapping> delegateList = 264 mDelegate.getEffectiveTimeZoneMappingsAt(whenMillis); 265 266 List<TimeZoneMapping> toReturn = new ArrayList<>(delegateList.size()); 267 for (com.android.i18n.timezone.CountryTimeZones.TimeZoneMapping delegateMapping 268 : delegateList) { 269 toReturn.add(new TimeZoneMapping(delegateMapping)); 270 } 271 return Collections.unmodifiableList(toReturn); 272 } 273 274 @Override equals(Object o)275 public boolean equals(Object o) { 276 if (this == o) { 277 return true; 278 } 279 if (o == null || getClass() != o.getClass()) { 280 return false; 281 } 282 CountryTimeZones that = (CountryTimeZones) o; 283 return mDelegate.equals(that.mDelegate); 284 } 285 286 @Override hashCode()287 public int hashCode() { 288 return Objects.hash(mDelegate); 289 } 290 291 @Override toString()292 public String toString() { 293 return mDelegate.toString(); 294 } 295 } 296